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