Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
tutorial-hsv-range-tuner.cpp
1
2
3#include <iostream>
4
5#include <visp3/core/vpConfig.h>
6
7#if defined(HAVE_OPENCV_HIGHGUI) && defined(VISP_HAVE_DISPLAY)
8#include <vector>
9
10#include <opencv2/highgui/highgui.hpp>
11
12#include <visp3/core/vpArray2D.h>
13#include <visp3/core/vpHSV.h>
14#include <visp3/core/vpImageConvert.h>
15#include <visp3/core/vpImageTools.h>
16#include <visp3/core/vpIoTools.h>
17#include <visp3/gui/vpDisplayFactory.h>
18#include <visp3/io/vpImageIo.h>
19#include <visp3/sensor/vpRealSense2.h>
20
21VP_ATTRIBUTE_NO_DESTROY std::vector<int> hsv_values_trackbar(6);
22VP_ATTRIBUTE_NO_DESTROY const cv::String window_detection_name = "Object Detection";
23
24void set_trackbar_H_min(int val)
25{
26 cv::setTrackbarPos("Low H", window_detection_name, val);
27}
28void set_trackbar_H_max(int val)
29{
30 cv::setTrackbarPos("High H", window_detection_name, val);
31}
32void set_trackbar_S_min(int val)
33{
34 cv::setTrackbarPos("Low S", window_detection_name, val);
35}
36void set_trackbar_S_max(int val)
37{
38 cv::setTrackbarPos("High S", window_detection_name, val);
39}
40void set_trackbar_V_min(int val)
41{
42 cv::setTrackbarPos("Low V", window_detection_name, val);
43}
44void set_trackbar_V_max(int val)
45{
46 cv::setTrackbarPos("High V", window_detection_name, val);
47}
48static void on_low_H_thresh_trackbar(int, void *)
49{
50 hsv_values_trackbar[0] = std::min(hsv_values_trackbar[1]-1, hsv_values_trackbar[0]);
51 set_trackbar_H_min(hsv_values_trackbar[0]);
52}
53static void on_high_H_thresh_trackbar(int, void *)
54{
55 hsv_values_trackbar[1] = std::max(hsv_values_trackbar[1], hsv_values_trackbar[0]+1);
56 set_trackbar_H_max(hsv_values_trackbar[1]);
57}
58static void on_low_S_thresh_trackbar(int, void *)
59{
60 hsv_values_trackbar[2] = std::min(hsv_values_trackbar[3]-1, hsv_values_trackbar[2]);
61 set_trackbar_S_min(hsv_values_trackbar[2]);
62}
63static void on_high_S_thresh_trackbar(int, void *)
64{
65 hsv_values_trackbar[3] = std::max(hsv_values_trackbar[3], hsv_values_trackbar[2]+1);
66 set_trackbar_S_max(hsv_values_trackbar[3]);
67}
68static void on_low_V_thresh_trackbar(int, void *)
69{
70 hsv_values_trackbar[4] = std::min(hsv_values_trackbar[5]-1, hsv_values_trackbar[4]);
71 set_trackbar_V_min(hsv_values_trackbar[4]);
72}
73static void on_high_V_thresh_trackbar(int, void *)
74{
75 hsv_values_trackbar[5] = std::max(hsv_values_trackbar[5], hsv_values_trackbar[4]+1);
76 set_trackbar_V_max(hsv_values_trackbar[5]);
77}
78
79int main(int argc, const char *argv[])
80{
81#ifdef ENABLE_VISP_NAMESPACE
82 using namespace VISP_NAMESPACE_NAME;
83#endif
84
85 bool opt_save_img = false;
86 std::string opt_hsv_filename = "calib/hsv-thresholds.yml";
87 std::string opt_img_filename;
88 bool show_helper = false;
89 for (int i = 1; i < argc; i++) {
90 if ((std::string(argv[i]) == "--hsv-thresholds") && ((i+1) < argc)) {
91 opt_hsv_filename = std::string(argv[++i]);
92 }
93 else if ((std::string(argv[i]) == "--image") && ((i+1) < argc)) {
94 opt_img_filename = std::string(argv[++i]);
95 }
96 else if (std::string(argv[i]) == "--save-img") {
97 opt_save_img = true;
98 }
99 if (show_helper || std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") {
100 std::cout << "\nSYNOPSIS " << std::endl
101 << argv[0]
102 << " [--image <input image>]"
103 << " [--hsv-thresholds <output filename.yml>]"
104 << " [--save-img]"
105 << " [--help,-h]"
106 << std::endl;
107 std::cout << "\nOPTIONS " << std::endl
108 << " --image <input image>" << std::endl
109 << " Name of the input image filename." << std::endl
110 << " When this option is not set, we use librealsense to stream images from a Realsense camera. " << std::endl
111 << " Example: --image ballons.jpg" << std::endl
112 << std::endl
113 << " --hsv-thresholds <output filename.yml>" << std::endl
114 << " Name of the output filename with yaml extension that will contain HSV low/high thresholds." << std::endl
115 << " Default: " << opt_hsv_filename << std::endl
116 << std::endl
117 << " --save-img" << std::endl
118 << " Enable RGB, HSV and segmented image saving" << std::endl
119 << std::endl
120 << " --help, -h" << std::endl
121 << " Display this helper message." << std::endl
122 << std::endl;
123 return EXIT_SUCCESS;
124 }
125 }
126
127 bool use_realsense = false;
128#if defined(VISP_HAVE_REALSENSE2)
129 use_realsense = true;
130#endif
131 if (use_realsense) {
132 if (!opt_img_filename.empty()) {
133 use_realsense = false;
134 }
135 }
136 else if (opt_img_filename.empty()) {
137 std::cout << "Error: you should use --image <input image> option to specify an input image..." << std::endl;
138 return EXIT_FAILURE;
139 }
140
141 if (use_realsense) {
142 std::cout << "Use images from Realsense camera" << std::endl;
143 }
144
145 int max_value_H = 255;
146 int max_value = 255;
147
148 hsv_values_trackbar[0] = 0; // Low H
149 hsv_values_trackbar[1] = max_value_H; // High H
150 hsv_values_trackbar[2] = 0; // Low S
151 hsv_values_trackbar[3] = max_value; // High S
152 hsv_values_trackbar[4] = 0; // Low V
153 hsv_values_trackbar[5] = max_value; // High V
154
156 int width, height;
157
158#if defined(VISP_HAVE_REALSENSE2)
159 vpRealSense2 rs;
160#endif
161
162 if (use_realsense) {
163#if defined(VISP_HAVE_REALSENSE2)
164 width = 848; height = 480;
165 int fps = 60;
166 rs2::config config;
167 config.enable_stream(RS2_STREAM_COLOR, width, height, RS2_FORMAT_RGBA8, fps);
168 config.disable_stream(RS2_STREAM_DEPTH);
169 config.disable_stream(RS2_STREAM_INFRARED, 1);
170 config.disable_stream(RS2_STREAM_INFRARED, 2);
171 rs.open(config);
172 rs.acquire(I);
173#endif
174 }
175 else {
176 try {
177 vpImageIo::read(I, opt_img_filename);
178 }
179 catch (const vpException &e) {
180 std::cout << e.getStringMessage() << std::endl;
181 return EXIT_FAILURE;
182 }
183 width = I.getWidth();
184 height = I.getHeight();
185 }
186
187 cv::namedWindow(window_detection_name);
188
189 vpArray2D<int> hsv_values(hsv_values_trackbar, static_cast<unsigned int>(hsv_values_trackbar.size()), 1);
190 vpArray2D<int> hsv_values_prev;
191 if (vpArray2D<int>::loadYAML(opt_hsv_filename, hsv_values)) {
192 std::cout << "Load hsv values from " << opt_hsv_filename << " previous tuning " << std::endl;
193 std::cout << hsv_values.t() << std::endl;
194 hsv_values_prev = hsv_values;
195 for (size_t i = 0; i < hsv_values.size(); ++i) {
196 hsv_values_trackbar[i] = hsv_values.data[i];
197 }
198 }
199
200 // Trackbars to set thresholds for HSV values
201 cv::createTrackbar("Low H", window_detection_name, &hsv_values_trackbar[0], max_value_H, on_low_H_thresh_trackbar);
202 cv::createTrackbar("High H", window_detection_name, &hsv_values_trackbar[1], max_value_H, on_high_H_thresh_trackbar);
203 cv::createTrackbar("Low S", window_detection_name, &hsv_values_trackbar[2], max_value, on_low_S_thresh_trackbar);
204 cv::createTrackbar("High S", window_detection_name, &hsv_values_trackbar[3], max_value, on_high_S_thresh_trackbar);
205 cv::createTrackbar("Low V", window_detection_name, &hsv_values_trackbar[4], max_value, on_low_V_thresh_trackbar);
206 cv::createTrackbar("High V", window_detection_name, &hsv_values_trackbar[5], max_value, on_high_V_thresh_trackbar);
207
208 vpImage<unsigned char> mask(height, width);
209 vpImage<vpRGBa> I_segmented(height, width);
210
211#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
213 std::shared_ptr<vpDisplay> d_I = vpDisplayFactory::createDisplay(I, 0, 0, "Current frame");
214 std::shared_ptr<vpDisplay> d_I_segmented = vpDisplayFactory::createDisplay(I_segmented, I.getWidth()+75, 0, "Segmented frame");
215#else
216 vpImage<unsigned char> H(height, width);
217 vpImage<unsigned char> S(height, width);
218 vpImage<unsigned char> V(height, width);
219 vpDisplay *d_I = vpDisplayFactory::allocateDisplay(I, 0, 0, "Current frame");
220 vpDisplay *d_I_segmented = vpDisplayFactory::allocateDisplay(I_segmented, I.getWidth()+75, 0, "Segmented frame");
221#endif
222 bool quit = false;
223
224 while (!quit) {
225 if (use_realsense) {
226#if defined(VISP_HAVE_REALSENSE2)
227 rs.acquire(I);
228#endif
229 }
230 else {
231 vpImageIo::read(I, opt_img_filename);
232 }
233
234#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
236 vpImageTools::inRange(Ihsv, hsv_values_trackbar, mask);
237#else
238 vpImageConvert::RGBaToHSV(reinterpret_cast<unsigned char *>(I.bitmap),
239 reinterpret_cast<unsigned char *>(H.bitmap),
240 reinterpret_cast<unsigned char *>(S.bitmap),
241 reinterpret_cast<unsigned char *>(V.bitmap), I.getSize());
242
243 vpImageTools::inRange(reinterpret_cast<unsigned char *>(H.bitmap),
244 reinterpret_cast<unsigned char *>(S.bitmap),
245 reinterpret_cast<unsigned char *>(V.bitmap),
246 hsv_values_trackbar,
247 reinterpret_cast<unsigned char *>(mask.bitmap),
248 mask.getSize());
249#endif
250
251 vpImageTools::inMask(I, mask, I_segmented);
252
254 vpDisplay::display(I_segmented);
255 vpDisplay::displayText(I, 20, 20, "Left click to learn HSV value...", vpColor::red);
256 vpDisplay::displayText(I, 40, 20, "Middle click to get HSV value...", vpColor::red);
257 vpDisplay::displayText(I, 60, 20, "Right click to quit...", vpColor::red);
258 vpImagePoint ip;
260 if (vpDisplay::getClick(I, ip, button, false)) {
261 if (button == vpMouseButton::button3) {
262 quit = true;
263 }
264 else if (button == vpMouseButton::button2) {
265 unsigned int i = static_cast<unsigned int>(ip.get_i());
266 unsigned int j = static_cast<unsigned int>(ip.get_j());
267#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
268 int h = static_cast<int>(Ihsv[i][j].H);
269 int s = static_cast<int>(Ihsv[i][j].S);
270 int v = static_cast<int>(Ihsv[i][j].V);
271#else
272 int h = static_cast<int>(H[i][j]);
273 int s = static_cast<int>(S[i][j]);
274 int v = static_cast<int>(V[i][j]);
275#endif
276 std::cout << "RGB[" << i << "][" << j << "]: " << static_cast<int>(I[i][j].R) << " " << static_cast<int>(I[i][j].G)
277 << " " << static_cast<int>(I[i][j].B) << " -> HSV: " << h << " " << s << " " << v << std::endl;
278 }
279 else if (button == vpMouseButton::button1) {
280 unsigned int i = static_cast<unsigned int>(ip.get_i());
281 unsigned int j = static_cast<unsigned int>(ip.get_j());
282#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
283 int h = static_cast<int>(Ihsv[i][j].H);
284 int s = static_cast<int>(Ihsv[i][j].S);
285 int v = static_cast<int>(Ihsv[i][j].V);
286#else
287 int h = static_cast<int>(H[i][j]);
288 int s = static_cast<int>(S[i][j]);
289 int v = static_cast<int>(V[i][j]);
290#endif
291 int offset = 30;
292 hsv_values_trackbar[0] = std::max(0, h - offset);
293 hsv_values_trackbar[1] = std::min(max_value_H, h + offset);
294 hsv_values_trackbar[2] = std::max(0, s - offset);
295 hsv_values_trackbar[3] = std::min(max_value, s + offset);
296 hsv_values_trackbar[4] = std::max(0, v - offset);
297 hsv_values_trackbar[5] = std::min(max_value, v + offset);
298 std::cout << "HSV learned: " << h << " " << s << " " << v << std::endl;
299 set_trackbar_H_min(hsv_values_trackbar[0]);
300 set_trackbar_H_max(hsv_values_trackbar[1]);
301 set_trackbar_S_min(hsv_values_trackbar[2]);
302 set_trackbar_S_max(hsv_values_trackbar[3]);
303 set_trackbar_V_min(hsv_values_trackbar[4]);
304 set_trackbar_V_max(hsv_values_trackbar[5]);
305 }
306 }
307 if (quit) {
308 std::string parent = vpIoTools::getParent(opt_hsv_filename);
309 if (vpIoTools::checkDirectory(parent) == false) {
310 std::cout << "Create directory: " << parent << std::endl;
312 }
313
314 vpArray2D<int> hsv_values_new(hsv_values_trackbar, static_cast<unsigned int>(hsv_values_trackbar.size()), 1);
315 if (hsv_values_new != hsv_values_prev) {
316 if (vpIoTools::checkFilename(opt_hsv_filename)) {
317 std::string hsv_filename_backup(opt_hsv_filename + std::string(".previous"));
318 std::cout << "Create a backup of the previous calibration in " << hsv_filename_backup << std::endl;
319 vpIoTools::copy(opt_hsv_filename, hsv_filename_backup);
320 }
321
322 std::cout << "Save new calibration in: " << opt_hsv_filename << std::endl;
323 std::string header = std::string("# File created ") + vpTime::getDateTime();
324 vpArray2D<int>::saveYAML(opt_hsv_filename, hsv_values_new, header.c_str());
325 }
326 if (opt_save_img) {
327 std::string path_img(parent + "/images-visp");
328 if (vpIoTools::checkDirectory(path_img) == false) {
329 std::cout << "Create directory: " << path_img << std::endl;
330 vpIoTools::makeDirectory(path_img);
331 }
332
333 std::cout << "Save images in path_img folder..." << std::endl;
334 vpImageIo::write(I, path_img + "/I.png");
335 vpImageIo::write(I_segmented, path_img + "/I-HSV-segmented.png");
336#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
337 vpImage<vpRGBa> I_HSV;
338 vpImageConvert::merge(&H, &S, &V, nullptr, I_HSV);
339 vpImageIo::write(I_HSV, path_img + "/I-HSV.png");
340#endif
341 }
342 break;
343 }
345 vpDisplay::flush(I_segmented);
346 cv::waitKey(10); // To display trackbar
347 }
348#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
349 if (d_I != nullptr) {
350 delete d_I;
351 }
352
353 if (d_I_segmented != nullptr) {
354 delete d_I_segmented;
355 }
356#endif
357 return EXIT_SUCCESS;
358}
359
360#else
361int main()
362{
363#if !defined(HAVE_OPENCV_HIGHGUI)
364 std::cout << "This tutorial needs OpenCV highgui module as 3rd party." << std::endl;
365#endif
366#if !defined(VISP_HAVE_X11)
367 std::cout << "This tutorial needs X11 3rd party enabled." << std::endl;
368#endif
369 std::cout << "Install missing 3rd parties, configure and rebuild ViSP." << std::endl;
370 return EXIT_SUCCESS;
371}
372#endif
Implementation of a generic 2D array used as base class for matrices and vectors.
Definition vpArray2D.h:146
static bool loadYAML(const std::string &filename, vpArray2D< Type > &A, char *header=nullptr)
Definition vpArray2D.h:874
static bool saveYAML(const std::string &filename, const vpArray2D< Type > &A, const char *header="")
Definition vpArray2D.h:1061
static const vpColor red
Definition vpColor.h:198
Class that defines generic functionalities for display.
Definition vpDisplay.h:171
static bool getClick(const vpImage< unsigned char > &I, bool blocking=true)
static void display(const vpImage< unsigned char > &I)
static void flush(const vpImage< unsigned char > &I)
static void displayText(const vpImage< unsigned char > &I, const vpImagePoint &ip, const std::string &s, const vpColor &color)
error that can be emitted by ViSP classes.
Definition vpException.h:60
static void merge(const vpImage< unsigned char > *R, const vpImage< unsigned char > *G, const vpImage< unsigned char > *B, const vpImage< unsigned char > *a, vpImage< vpRGBa > &RGBa)
static void RGBaToHSV(const unsigned char *rgba, double *hue, double *saturation, double *value, unsigned int size)
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
static void read(vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
static void write(const vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
double get_j() const
double get_i() const
static int inMask(const vpImage< vpRGBa > &I, const vpImage< bool > &mask, vpImage< vpRGBa > &I_mask)
static int inRange(const unsigned char *hue, const unsigned char *saturation, const unsigned char *value, const vpColVector &hsv_range, unsigned char *mask, unsigned int size)
Definition of the vpImage class member functions.
Definition vpImage.h:131
static bool checkFilename(const std::string &filename)
static bool copy(const std::string &src, const std::string &dst)
static bool checkDirectory(const std::string &dirname)
static void makeDirectory(const std::string &dirname)
static std::string getParent(const std::string &pathname)
void acquire(vpImage< unsigned char > &grey, double *ts=nullptr)
bool open(const rs2::config &cfg=rs2::config())
std::shared_ptr< vpDisplay > createDisplay()
Return a smart pointer vpDisplay specialization if a GUI library is available or nullptr otherwise.
vpDisplay * allocateDisplay()
Return a newly allocated vpDisplay specialization if a GUI library is available or nullptr otherwise.
VISP_EXPORT std::string getDateTime(const std::string &format="%Y/%m/%d %H:%M:%S")