YARP
Yet Another Robot Platform
ServerFrameGrabberDual.cpp
Go to the documentation of this file.
1 /*
2  * SPDX-FileCopyrightText: 2006-2021 Istituto Italiano di Tecnologia (IIT)
3  * SPDX-FileCopyrightText: 2006-2010 RobotCub Consortium
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
8 
9 #include <yarp/os/Log.h>
10 #include <yarp/dev/PolyDriver.h>
11 #include <yarp/os/LogStream.h>
12 #include <yarp/os/ResourceFinder.h>
13 #include <yarp/sig/Vector.h>
14 #include <yarp/sig/ImageUtils.h>
15 #include <yarp/os/PortablePair.h>
16 
18 
19 #include <cstring>
20 #include <algorithm> // std::for_each
21 
22 using namespace yarp::os;
23 using namespace yarp::dev;
24 using namespace yarp::sig;
25 
26 namespace {
27 YARP_LOG_COMPONENT(SERVERGRABBER, "yarp.device.grabberDual")
28 }
29 
30 
31 // **********ServerGrabberResponder**********
32 
34  left(_left)
35 {
36 }
37 
39 {
40  if(_server)
41  {
42  server=_server;
43  return true;
44  }
45  yCError(SERVERGRABBER) << "ServerGrabberResponder: invalid server pointer";
46  return false;
47 }
48 
50  if(server)
51  {
52  if(server->respond(command,reply,left,false))
53  {
54  return true;
55  }
56  else
57  {
58  return DeviceResponder::respond(command, reply);
59  }
60  } else {
61  return false;
62  }
63 }
64 
65 // **********ServerGrabber**********
66 
69 {
70 }
71 
73 {
74  if (param.active) {
75  close();
76  }
77 }
78 
80  if (!param.active) {
81  return false;
82  }
83  stopThread();
84 
85  param.active = false;
86  pImg.interrupt();
87  pImg.close();
88  rpcPort.interrupt();
89  rpcPort.close();
90 
91  if(responder){
92  delete responder;
93  responder=nullptr;
94  }
95 
96  if(param.split)
97  {
98  pImg2.interrupt();
99  pImg2.close();
100  }
101 
102 
103  if(param.twoCameras)
104  {
105  rpcPort2.interrupt();
106  rpcPort2.close();
107  }
108 
109  cleanUp();
110  if(poly)
111  {
112  poly->close();
113  delete poly;
114  poly=nullptr;
115  }
116 
117  if(responder2)
118  {
119  delete responder2;
120  responder2=nullptr;
121  }
122 
123  if(isSubdeviceOwned && poly2)
124  {
125  poly2->close();
126  delete poly2;
127  poly2=nullptr;
128  }
129  isSubdeviceOwned=false;
130  if (p2!=nullptr) {
131  delete p2;
132  p2 =nullptr;
133  }
134  return true;
135 }
136 
138 
139  yCWarning(SERVERGRABBER) << "The 'grabberDual' device is deprecated in favour of 'frameGrabber_nws_yarp' (and eventually 'frameGrabberCropper').";
140  yCWarning(SERVERGRABBER) << "The old device is no longer supported, and it will be deprecated in YARP 3.6 and removed in YARP 4.";
141  yCWarning(SERVERGRABBER) << "Please update your scripts.";
142 
143  if (param.active) {
144  yCError(SERVERGRABBER, "Did you just try to open the same ServerGrabber twice?");
145  return false;
146  }
147 
148  if(!fromConfig(config))
149  {
150  yCError(SERVERGRABBER) << "Device ServerGrabber failed to open, check previous log for error messages.";
151  return false;
152  }
153 
154  if(!initialize_YARP(config))
155  {
156  yCError(SERVERGRABBER) <<"Error initializing YARP ports";
157  return false;
158  }
159 
160  if(isSubdeviceOwned){
161  if(! openAndAttachSubDevice(config))
162  {
163  yCError(SERVERGRABBER, "Error while opening subdevice");
164  return false;
165  }
166  }
167  else
168  {
169  yCInfo(SERVERGRABBER) << "Running, waiting for attach...";
170  if (!openDeferredAttach(config)) {
171  return false;
172  }
173  }
174 
175 
176  param.active = true;
177 // //ASK/TODO update usage and see if we need to add DeviceResponder as dependency
178 // DeviceResponder::makeUsage();
179 // addUsage("[set] [bri] $fBrightness", "set brightness");
180 // addUsage("[set] [expo] $fExposure", "set exposure");
181 // addUsage("[set] [shar] $fSharpness", "set sharpness");
182 // addUsage("[set] [whit] $fBlue $fRed", "set white balance");
183 // addUsage("[set] [hue] $fHue", "set hue");
184 // addUsage("[set] [satu] $fSaturation", "set saturation");
185 // addUsage("[set] [gamm] $fGamma", "set gamma");
186 // addUsage("[set] [shut] $fShutter", "set shutter");
187 // addUsage("[set] [gain] $fGain", "set gain");
188 // addUsage("[set] [iris] $fIris", "set iris");
189 
190 // addUsage("[get] [bri]", "get brightness");
191 // addUsage("[get] [expo]", "get exposure");
192 // addUsage("[get] [shar]", "get sharpness");
193 // addUsage("[get] [whit]", "get white balance");
194 // addUsage("[get] [hue]", "get hue");
195 // addUsage("[get] [satu]", "get saturation");
196 // addUsage("[get] [gamm]", "get gamma");
197 // addUsage("[get] [shut]", "get shutter");
198 // addUsage("[get] [gain]", "get gain");
199 // addUsage("[get] [iris]", "get iris");
200 
201 // addUsage("[get] [w]", "get width of image");
202 // addUsage("[get] [h]", "get height of image");
203 
204 
205  return true;
206 }
207 
209 {
210  if (config.check("period", "refresh period(in ms) of the broadcasted values through yarp ports")
211  && config.find("period").isInt32()) {
212  period = config.find("period").asInt32() / 1000.0;
213  } else {
214  yCInfo(SERVERGRABBER) << "Period parameter not found, using default of" << DEFAULT_THREAD_PERIOD << "s";
215  }
216  if((config.check("subdevice")) && (config.check("left_config") || config.check("right_config")))
217  {
218  yCError(SERVERGRABBER) << "Found both 'subdevice' and 'left_config/right_config' parameters...";
219  return false;
220  }
221  if (!config.check("subdevice", "name of the subdevice to use as a data source")
222  && config.check("left_config", "name of the ini file containing the configuration of one of two subdevices to use as a data source")
223  && config.check("right_config", "name of the ini file containing the configuration of one of two subdevices to use as a data source")) {
224  param.twoCameras = true;
225  }
226  if (config.check("twoCameras", "if true ServerGrabber will open and handle two devices, if false only one")) { //extra conf parameter for the yarprobotinterface
227  param.twoCameras = config.find("twoCameras").asBool();
228  }
229  if (config.check("split", "set 'true' to split the streaming on two different ports")) {
230  param.split = config.find("split").asBool();
231  }
232  if(config.check("capabilities","two capabilities supported, COLOR and RAW respectively for rgb and raw streaming"))
233  {
234  if (config.find("capabilities").asString() == "COLOR") {
235  param.cap=COLOR;
236  } else if (config.find("capabilities").asString() == "RAW") {
237  param.cap = RAW;
238  }
239  } else {
240  yCWarning(SERVERGRABBER) << "'capabilities' parameter not found or misspelled, the option available are COLOR(default) and RAW, using default";
241  }
242  param.canDrop = !config.check("no_drop","if present, use strict policy for sending data");
243  param.addStamp = config.check("stamp","if present, add timestamps to data");
244 
245  param.singleThreaded =
246  config.check("single_threaded",
247  "if present, operate in single threaded mode");
248  //TODO audio part
249  std::string rootName;
250  rootName = config.check("name",Value("/grabber"),
251  "name of port to send data on").asString();
252  if (!param.twoCameras && param.split) {
253  param.splitterMode = true;
254  }
255 
256  responder = new ServerGrabberResponder(true);
257  if (!responder->configure(this)) {
258  return false;
259  }
260  if(param.twoCameras)
261  {
262  responder2 = new ServerGrabberResponder(false);
263  if (!responder2->configure(this)) {
264  return false;
265  }
266 
267  rpcPort_Name = rootName + "/left/rpc";
268  rpcPort2_Name = rootName + "/right/rpc";
269  if(param.split)
270  {
271  pImg_Name = rootName + "/left";
272  pImg2_Name = rootName + "/right";
273  } else {
274  pImg_Name = rootName;
275  }
276 
277  // check if we need to create subdevice or if they are
278  // passed later on thorugh attachAll()
279  if(config.check("left_config") && config.check("right_config"))
280  {
281  isSubdeviceOwned=true;
282  }
283  else
284  {
285  isSubdeviceOwned=false;
286  }
287  }
288  else
289  {
290  if(param.split)
291  {
292  responder2 = new ServerGrabberResponder(false);
293  if (!responder2->configure(this)) {
294  return false;
295  }
296  pImg_Name = rootName + "/left";
297  pImg2_Name = rootName + "/right";
298  }
299  else
300  {
301  pImg_Name = rootName;
302  }
303  rpcPort_Name = rootName + "/rpc";
304  if(config.check("subdevice"))
305  {
306  isSubdeviceOwned=true;
307  }
308  else
309  {
310  isSubdeviceOwned=false;
311  }
312  }
313 
314 
315  return true;
316 }
317 
319 {
320  // Open ports
321  bool bRet;
322  bRet = true;
323  if(!rpcPort.open(rpcPort_Name))
324  {
325  yCError(SERVERGRABBER) << "Unable to open rpc Port" << rpcPort_Name.c_str();
326  bRet = false;
327  }
328  rpcPort.setReader(*responder);
329 
330  pImg.promiseType(Type::byName("yarp/image"));
331  pImg.setWriteOnly();
332  if(!pImg.open(pImg_Name))
333  {
334  yCError(SERVERGRABBER) << "Unable to open image streaming Port" << pImg_Name.c_str();
335  bRet = false;
336  }
337  pImg.setReader(*responder);
338 
339  if(param.twoCameras)
340  {
341  if(!rpcPort2.open(rpcPort2_Name))
342  {
343  yCError(SERVERGRABBER) << "Unable to open rpc Port" << rpcPort2_Name.c_str();
344  bRet = false;
345  }
346  rpcPort2.setReader(*responder2);
347  }
348  if(param.split)
349  {
350  pImg2.promiseType(Type::byName("yarp/image"));
351  pImg2.setWriteOnly();
352 
353  if(!pImg2.open(pImg2_Name))
354  {
355  yCError(SERVERGRABBER) << "Unable to open image streaming Port" << pImg2_Name.c_str();
356  bRet = false;
357  }
358  pImg2.setReader(*responder2);
359  }
360 
361  return bRet;
362 }
363 
365  yarp::os::Bottle& response, bool left, bool both=false) {
366  int code = cmd.get(0).asVocab32();
367  Bottle response2;
368  switch (code)
369  {
371  {
372  switch (cmd.get(1).asVocab32())
373  {
374  case VOCAB_GET:
375  {
376  switch (cmd.get(2).asVocab32())
377  {
378  case VOCAB_CROP:
379  {
380  response.clear();
381  // If the device driver support it, use the device implementation, because it may be more efficient.
382  // If not, acquire the whole image and crop it here before sending it.
383 
384  Bottle *list = cmd.get(4).asList();
385  int nPoints = list->size() /2; // divided by 2 because each pixel is identified by 2 numbers (u,v)
386 
388  vertices.resize(nPoints);
389 
390  for(int i=0; i<nPoints; i++)
391  {
392  vertices[i].first = list->get(i*2).asInt32();
393  vertices[i].second = list->get(i*2 +1).asInt32();
394  }
395 
396  ImageOf< PixelRgb > cropped;
397 
398  // Choose the interface and eventual offset depending on case.
399 
400  /* HW/SW configurations here: (1a, 1b, 2a, 2b), for each one the user can request a crop on left or right image
401  * 1) single HW camera as a source
402  * 1a) split false: a single image to handle
403  * 1b) split true : 2 images, I have to handle left or right image. If user request a crop in the right side,
404  * of the image, then add an offset
405  *
406  * 2) two HW sources
407  * 2a) split true: choose appropriate image source based on left/right request
408  * 2b) split false: choose appropriate image source based on crop position. Crop request have to belong to a
409  * single frame, either left or right. Example: 2 cameras with 320x240 pixels each placed
410  * one after the other, generates a single stitched image of 640x240.
411  * Anyway a crop request like (200,100)(400,200) shall be rejected, even if it could be
412  * considered as a part of the image resulting from the stitch.
413  * Right now the decision is took based on the first point of vector 'vertices', since all
414  * points are expected to belong to the same frame (left/right)
415  *
416  */
417 
418  // Default values here are valid for cases 1a and `left` side of 2a
419  IFrameGrabberImage *imageInterface = fgImage;
420 
421  if(param.twoCameras == false) // a single HW source of images
422  {
423  imageInterface = fgImage;
424  if(left == false) // if left is false, implicitly split is true
425  {
426  std::for_each(vertices.begin(), vertices.end(), [=](auto &pt) { pt.first += imageInterface->width() / 2; }); // 1b
427  }
428 
429  }
430  else
431  {
432  if(param.split) // 2a, right image
433  {
434  if(left == false)
435  {
436  imageInterface = fgImage2;
437  // no offset
438  }
439  }
440  else
441  {
442  if(vertices[0].first >= fgImage->width()) // 2b, right image
443  {
444  imageInterface = fgImage2;
445  std::for_each(vertices.begin(), vertices.end(), [=](auto &pt) { pt.first -= fgImage->width(); });
446  }
447  }
448 
449  }
450 
451  if(imageInterface != nullptr)
452  {
453  if(imageInterface->getImageCrop((cropType_id_t) cmd.get(3).asVocab32(), vertices, cropped) )
454  {
455  // use the device output
456  }
457  else
458  {
459  // In case the device has not yet implemented this feature, do it here (less efficient)
460  if(cmd.get(3).asVocab32() == YARP_CROP_RECT)
461  {
462  if(nPoints != 2)
463  {
464  response.addString("GetImageCrop failed: RECT mode requires 2 vertices.");
465  yCError(SERVERGRABBER) << "GetImageCrop failed: RECT mode requires 2 vertices, got " << nPoints;
466  return false;
467  }
468  ImageOf< PixelRgb > full;
469  imageInterface->getImage(full);
470 
471  if(!utils::cropRect(full, vertices[0], vertices[1], cropped))
472  {
473  response.addString("GetImageCrop failed: utils::cropRect error.");
474  yCError(SERVERGRABBER, "GetImageCrop failed: utils::cropRect error: (%d, %d) (%d, %d)",
475  vertices[0].first,
476  vertices[0].second,
477  vertices[1].first,
478  vertices[1].second);
479  return false;
480  }
481  }
482  else if(cmd.get(3).asVocab32() == YARP_CROP_LIST)
483  {
484  response.addString("List type not yet implemented");
485  }
486  else
487  {
488  response.addString("Crop type unknown");
489  }
490  }
491  }
492 
493  response.addVocab32(VOCAB_CROP);
494  response.addVocab32(VOCAB_IS);
495  response.addInt32(cropped.width()); // Actual width of image in pixels, to check everything is ok
496  response.addInt32(cropped.height()); // Actual height of image in pixels, to check everything is ok
497 
498  response.add(Value(cropped.getRawImage(), cropped.getRawImageSize()));
499  return true;
500  } break;
501  } break;
502 
503  } break;
504 
505  case VOCAB_SET: // Nothing to do here yet
506  default:
507  {
508  yCError(SERVERGRABBER) << "FrameGrabberImage interface received an unknown command " << cmd.toString();
509  } break;
510 
511  }
512 
513  } break;
514 
515  // first check if requests are coming from new iFrameGrabberControl2 interface and process them
517  {
518  if(param.twoCameras)
519  {
520  bool ret;
521  if(both){
522  ret=ifgCtrl_Responder.respond(cmd, response);
523  ret&=ifgCtrl2_Responder.respond(cmd, response2);
524  if(!ret || (response!=response2))
525  {
526  response.clear();
527  response.addVocab32(VOCAB_FAILED);
528  ret=false;
529  yCWarning(SERVERGRABBER) << "Response different among cameras or failed";
530  }
531  }
532  else
533  {
534  if(left)
535  {
536  ret=ifgCtrl_Responder.respond(cmd, response);
537  }
538  else
539  {
540  ret=ifgCtrl2_Responder.respond(cmd, response);
541  }
542  }
543  return ret;
544  } else {
545  return ifgCtrl_Responder.respond(cmd, response);
546  }
547  } break;
548 
550  {
551  if(param.twoCameras)
552  {
553  bool ret;
554  ret=rgbParser.respond(cmd,response);
555  ret&=rgbParser2.respond(cmd,response2);
556  if(ret)
557  {
558  switch (cmd.get(2).asVocab32())
559  {
560  //Only the intrinsic parameters are allowed to be different among the two cameras
561  // so we give both responses appending one to the other.
563  {
564  Bottle& newResponse = response.addList();
565  newResponse.append(*response2.get(3).asList());
566  break;
567  }
568  //In general if the two response are different we send a FAIL vocab
569  default:
570  {
571  if(response!=response2)
572  {
573  response.clear();
574  response.addVocab32(VOCAB_FAILED);
575  ret=false;
576  yCWarning(SERVERGRABBER) << "Response different among cameras or failed";
577  }
578  break;
579  }
580  }
581  }
582 
583  return ret;
584  } else {
585  return rgbParser.respond(cmd, response);
586  }
587  } break;
589  // DC1394 COMMANDS
592  {
593  if(param.twoCameras)
594  {
595  bool ret;
596  if(both)
597  {
598  ret=ifgCtrl_DC1394_Responder.respond(cmd, response);
599  ret&=ifgCtrl2_DC1394_Responder.respond(cmd, response2);
600  if(!ret || (response!=response2))
601  {
602  response.clear();
603  response.addString("command not recognized");
604  ret=false;
605  yCWarning(SERVERGRABBER) << "Responses different among cameras or failed";
606 
607  }
608  }
609  else
610  {
611  if(left)
612  {
613  ret=ifgCtrl_DC1394_Responder.respond(cmd, response);
614  }
615  else
616  {
617  ret=ifgCtrl2_DC1394_Responder.respond(cmd, response);
618  }
619  }
620  return ret;
621  } else {
622  return ifgCtrl_DC1394_Responder.respond(cmd, response);
623  }
624  } break;
625  }
626  yCError(SERVERGRABBER) << "Command not recognized" << cmd.toString();
627  return false;
628 }
629 
630 bool ServerGrabber::attachAll(const PolyDriverList &device2attach)
631 {
632  bool ret=false;
633  if(param.twoCameras)
634  {
635  bool leftDone=false;//for avoiding double left
636  bool rightDone=false;//for avoiding double right
637  if (device2attach.size() != 2)
638  {
639  yCError(SERVERGRABBER, "Expected two devices to be attached");
640  return false;
641  }
642  for(int i=0;i<device2attach.size();i++)
643  {
644  yarp::dev::PolyDriver * Idevice2attach = device2attach[i]->poly;
645  if (!Idevice2attach->isValid())
646  {
647  yCError(SERVERGRABBER) << "Device " << device2attach[i]->key << " to attach to is not valid ... cannot proceed";
648  return false;
649  }
650  if(device2attach[i]->key == "LEFT" && !leftDone)
651  {
652  leftDone |= Idevice2attach->view(rgbVis_p);
653  leftDone |= Idevice2attach->view(fgImage);
654  leftDone |= Idevice2attach->view(fgImageRaw);
655  leftDone |= Idevice2attach->view(fgCtrl);
656  leftDone |= Idevice2attach->view(fgCtrl_DC1394);
657  }
658  else if(device2attach[i]->key == "RIGHT" && !rightDone)
659  {
660  rightDone |= Idevice2attach->view(rgbVis_p2);
661  rightDone |= Idevice2attach->view(fgImage2);
662  rightDone |= Idevice2attach->view(fgImageRaw2);
663  rightDone |= Idevice2attach->view(fgCtrl2);
664  rightDone |= Idevice2attach->view(fgCtrl2_DC1394);
665  }
666  else
667  {
668  yCError(SERVERGRABBER) << "Failed to attach. The two targets must be LEFT RIGHT and devices must implement"
669  " either IFrameGrabberImage or IFrameGrabberImageRaw";
670  return false;
671 
672  }
673  }
674  switch(param.cap)
675  {
676  case COLOR :
677  {
678  if((fgImage==nullptr) || (fgImage2==nullptr))
679  {
680  yCError(SERVERGRABBER) << "Capability \"COLOR\" required not supported";
681  return false;
682  }
683  }
684  break;
685  case RAW :
686  {
687  if((fgImageRaw==nullptr) || (fgImageRaw2==nullptr))
688  {
689  yCError(SERVERGRABBER) << "Capability \"RAW\" required not supported";
690  return false;
691  }
692  }
693  }
694  if((rgbVis_p == nullptr) || (rgbVis_p2 == nullptr))
695  {
696  yCWarning(SERVERGRABBER) << "Targets has not IVisualParamInterface, some features cannot be available";
697  }
698  //Configuring parsers
699  if(rgbVis_p != nullptr && rgbVis_p2 != nullptr)
700  {
701  if(!(rgbParser.configure(rgbVis_p)) || !(rgbParser2.configure(rgbVis_p2)))
702  {
703  yCError(SERVERGRABBER) << "Error configuring interfaces for parsers";
704  return false;
705  }
706  }
707  if(fgCtrl != nullptr && fgCtrl2 != nullptr)
708  {
709  if(!(ifgCtrl_Responder.configure(fgCtrl)) || !(ifgCtrl2_Responder.configure(fgCtrl2)))
710  {
711  yCError(SERVERGRABBER) << "Error configuring interfaces for parsers";
712  return false;
713  }
714  }
715  if(fgCtrl_DC1394 != nullptr && fgCtrl2_DC1394 != nullptr)
716  {
717  if(!(ifgCtrl_DC1394_Responder.configure(fgCtrl_DC1394)) || !(ifgCtrl2_DC1394_Responder.configure(fgCtrl2_DC1394)))
718  {
719  yCError(SERVERGRABBER) << "Error configuring interfaces for parsers";
720  return false;
721  }
722  }
723  }
724  else{
725  if (device2attach.size() != 1)
726  {
727  yCError(SERVERGRABBER, "Expected one device to be attached");
728  return false;
729  }
730  yarp::dev::PolyDriver * Idevice2attach = device2attach[0]->poly;
731  Idevice2attach->view(rgbVis_p);
732  Idevice2attach->view(fgImage);
733  Idevice2attach->view(fgImageRaw);
734  Idevice2attach->view(fgCtrl);
735  Idevice2attach->view(fgCtrl_DC1394);
736  switch(param.cap){
737  case COLOR :
738  {
739  if(fgImage==nullptr)
740  {
741  yCError(SERVERGRABBER) << "Capability \"COLOR\" required not supported";
742  return false;
743  }
744  }
745  break;
746  case RAW :
747  {
748  if(fgImageRaw==nullptr)
749  {
750  yCError(SERVERGRABBER) << "Capability \"RAW\" required not supported";
751  return false;
752  }
753  }
754  }
755 
756  if (!Idevice2attach->isValid())
757  {
758  yCError(SERVERGRABBER) << "Device " << device2attach[0]->key << " to attach to is not valid ... cannot proceed";
759  return false;
760  }
761 
762  if(rgbVis_p == nullptr)
763  {
764  yCWarning(SERVERGRABBER) << "Targets has not IVisualParamInterface, some features cannot be available";
765  }
766 
767  //Configuring parsers
768  if(rgbVis_p != nullptr)
769  {
770  if(!(rgbParser.configure(rgbVis_p)))
771  {
772  yCError(SERVERGRABBER) << "Error configuring interfaces for parsers";
773  return false;
774  }
775  }
776  if(fgCtrl != nullptr)
777  {
778  if(!(ifgCtrl_Responder.configure(fgCtrl)))
779  {
780  yCError(SERVERGRABBER) << "Error configuring interfaces for parsers";
781  return false;
782  }
783  }
784 
785  if(fgCtrl_DC1394 != nullptr)
786  {
787  if(!(ifgCtrl_DC1394_Responder.configure(fgCtrl_DC1394)))
788  {
789  yCError(SERVERGRABBER) << "Error configuring interfaces for parsers";
790  return false;
791  }
792  }
793  }
794 
795  PeriodicThread::setPeriod(period);
796  ret = PeriodicThread::start();
797 
798  return ret;
799 }
801 {
802  //check if we already instantiated a subdevice previously
803  if (isSubdeviceOwned) {
804  return false;
805  }
806  stopThread();
807  return true;
808 
809 }
811 {
814  }
815 
816  rgbVis_p = nullptr;
817  rgbVis_p2 = nullptr;
818  fgImage = nullptr;
819  fgImage2 = nullptr;
820  fgImageRaw = nullptr;
821  fgImageRaw2 = nullptr;
822  fgCtrl = nullptr;
823  fgCtrl2 = nullptr;
824  fgCtrl_DC1394 = nullptr;
825  fgCtrl2_DC1394 = nullptr;
826 }
827 
829 {
830  flex_i.setPixelCode(_img.getPixelCode());
831  flex_i.setQuantum(_img.getQuantum());
832  flex_i.setTopIsLowIndex(_img.topIsLowIndex());
833  flex_i.setExternal(_img.getRawImage(), _img.width(),_img.height());
834 
835 }
836 
838 {
839  if(param.twoCameras)
840  {
841  yCError(SERVERGRABBER) << "Server grabber configured for two cameras, but only one provided";
842  return false;
843  }
844  PolyDriverList plist;
845  if(poly)
846  {
847  PolyDriverDescriptor p(poly,"poly");
848  plist.push(p);
849  return attachAll(plist);
850  }
851  else
852  {
853  yCError(SERVERGRABBER) << "Invalid device to be attached";
854  return false;
855  }
856  return true;
857 }
859 {
860  return detachAll();
861 }
862 
863 bool ServerGrabber::openDeferredAttach(Searchable &prop){
864  // I dunno what to do here now...
865  isSubdeviceOwned = false;
866  return true;
867 }
868 
869 bool ServerGrabber::openAndAttachSubDevice(Searchable &prop){
870  PolyDriverList plist;
871  if(param.twoCameras){
872  Property p,p2;
873  poly = new PolyDriver;
874  poly2 = new PolyDriver;
875  std::string file, file2;
876  if(!prop.check("left_config") || !prop.check("right_config"))
877  {
878  yCError(SERVERGRABBER) << "Missing 'left_config' or 'right_config' filename... ";
879  return false;
880  }
881  ResourceFinder rf, rf2;
882  if(prop.check("context"))
883  {
884  rf.setDefaultContext(prop.find("context").asString().c_str());
885  rf2.setDefaultContext(prop.find("context").asString().c_str());
886  }
887  file=prop.find("left_config").toString();
888  file2=prop.find("right_config").toString();
889  p.fromConfigFile(rf.findFileByName(file));
890  p2.fromConfigFile(rf2.findFileByName(file2));
891  if(p.isNull() || p2.isNull())
892  {
893  yCError(SERVERGRABBER) << "Unable to find files specified in 'left_config' and/or 'right_config'";
894  return false;
895  }
896 // p.fromString(prop.findGroup("LEFT").toString().c_str());
897 // p2.fromString(prop.findGroup("RIGHT").toString().c_str());
898  if(param.cap==COLOR){
899  p.put("pixelType", VOCAB_PIXEL_RGB);
900  p2.put("pixelType", VOCAB_PIXEL_RGB);
901  }
902  else
903  {
904  p.put("pixelType", VOCAB_PIXEL_MONO);
905  p2.put("pixelType", VOCAB_PIXEL_MONO);
906  }
907  if(p.find("height").asInt32() != p2.find("height").asInt32() ||
908  p.find("width").asInt32() != p2.find("width").asInt32())
909  {
910  yCError(SERVERGRABBER) << "Error in the configuration file, the two images have to have the same dimensions";
911  return false;
912  }
913  //COSA FA? Serve? Guardarci
914  //p.setMonitor(prop.getMonitor(), "subdevice"); // pass on any monitoring
915  // if errors occurred during open, quit here.
916  poly->open(p);
917  poly2->open(p2);
918 
919  if (!(poly->isValid()) || !(poly2->isValid()))
920  {
921  yCError(SERVERGRABBER, "Opening devices... FAILED");
922  return false;
923  }
924  PolyDriverDescriptor pd(poly,"LEFT");
925  PolyDriverDescriptor pd2(poly2,"RIGHT");
926  plist.push(pd);
927  plist.push(pd2);
928  //The thread is started by attachAll()
929  if (!attachAll(plist)) {
930  return false;
931  }
932  }
933  else
934  {
935  Property p;
936  poly = new PolyDriver;
937  p.fromString(prop.toString());
938  if(param.cap==COLOR){
939  p.put("pixelType", VOCAB_PIXEL_RGB);
940  }
941  else
942  {
943  p.put("pixelType", VOCAB_PIXEL_MONO);
944  }
945  p.setMonitor(prop.getMonitor(), "subdevice"); // pass on any monitoring
946  p.unput("device");
947  p.put("device",prop.find("subdevice").asString()); // subdevice was already checked before
948 
949  // if errors occurred during open, quit here.
950  poly->open(p);
951 
952  if (!(poly->isValid()))
953  {
954  yCError(SERVERGRABBER, "opening subdevice... FAILED");
955  return false;
956  }
957  PolyDriverDescriptor pd(poly,"poly");
958  plist.push(pd);
959  //The thread is started by attachAll()
960  if (!attachAll(plist)) {
961  return false;
962  }
963  }
964  isSubdeviceOwned = true;
965  return true;
966 }
967 
969 {
970  if(param.twoCameras)
971  {
972  if(param.cap==COLOR)
973  {
974  img= new ImageOf<PixelRgb>;
975  img->resize(fgImage->width(),fgImage->height());
976  img2= new ImageOf<PixelRgb>;
977  img2->resize(fgImage2->width(),fgImage2->height());
978  }
979  else
980  {
981  img_Raw= new ImageOf<PixelMono>;
982  img_Raw->resize(fgImageRaw->width(),fgImageRaw->height());
983  img2_Raw= new ImageOf<PixelMono>;
984  img2_Raw->resize(fgImageRaw2->width(),fgImageRaw2->height());
985  }
986  }
987  else
988  {
989  if(param.cap==COLOR)
990  {
991  img= new ImageOf<PixelRgb>;
992  if(param.splitterMode)
993  {
994  img->resize(fgImage->width()/2,fgImage->height());
995 
996  img2= new ImageOf<PixelRgb>;
997  img2->resize(fgImage->width()/2,fgImage->height());
998  }
999  else
1000  {
1001  img->resize(fgImage->width(),fgImage->height());
1002  }
1003  }
1004  else
1005  {
1006  img_Raw= new ImageOf<PixelMono>;
1007  if(param.splitterMode)
1008  {
1009  img_Raw->resize(fgImageRaw->width()/2,fgImageRaw->height());
1010 
1011  img2_Raw= new ImageOf<PixelMono>;
1012  img2_Raw->resize(fgImageRaw->width()/2,fgImageRaw->height());
1013  }
1014  else
1015  {
1016  img_Raw->resize(fgImageRaw->width(), fgImageRaw->height());
1017  }
1018  }
1019  }
1020  return true;
1021 }
1022 
1024 
1025 
1026 
1027 }
1028 
1030 {
1031  if(param.twoCameras)
1032  {
1033  if(param.split)
1034  {
1035  FlexImage& flex_i=pImg.prepare();
1036  FlexImage& flex_i2=pImg2.prepare();
1037  if(param.cap==COLOR)
1038  {
1039  if(fgImage!=nullptr && fgImage2 !=nullptr)
1040  {
1041  fgImage->getImage(*img);
1042  setupFlexImage(*img,flex_i);
1043  fgImage2->getImage(*img2);
1044  setupFlexImage(*img2,flex_i2);
1045  } else {
1046  yCError(SERVERGRABBER) << "Image not captured.. check hardware configuration";
1047  }
1048  }
1049  if(param.cap==RAW)
1050  {
1051  if(fgImageRaw!=nullptr && fgImageRaw2 !=nullptr)
1052  {
1053  fgImageRaw->getImage(*img_Raw);
1054  setupFlexImage(*img_Raw,flex_i);
1055  fgImageRaw2->getImage(*img2_Raw);
1056  setupFlexImage(*img2_Raw,flex_i2);
1057  } else {
1058  yCError(SERVERGRABBER) << "Image not captured.. check hardware configuration";
1059  }
1060  }
1061  Stamp s = Stamp(count,Time::now());
1062  pImg.setStrict(!param.canDrop);
1063  pImg.setEnvelope(s);
1064  pImg.write();
1065  pImg2.setStrict(!param.canDrop);
1066  Stamp s2 = Stamp(count2,Time::now());
1067  pImg2.setEnvelope(s2);
1068  pImg2.write();
1069  count++;
1070  count2++;
1071 
1072  }
1073  else
1074  {
1075  FlexImage& flex_i=pImg.prepare();
1076  if(param.cap==COLOR)
1077  {
1078  if(fgImage!=nullptr && fgImage2 !=nullptr)
1079  {
1080  flex_i.setPixelCode(VOCAB_PIXEL_RGB);
1081  flex_i.resize(fgImage->width()*2,fgImage->height());
1082  fgImage->getImage(*img);
1083  fgImage2->getImage(*img2);
1084 
1085  bool ok = utils::horzConcat(*img, *img2, flex_i);
1086  if (!ok)
1087  {
1088  yCError(SERVERGRABBER) << "Failed to concatenate images";
1089  return;
1090  }
1091  } else {
1092  yCError(SERVERGRABBER) << "Image not captured.. check hardware configuration";
1093  }
1094  }
1095  if(param.cap==RAW)
1096  {
1097  if(fgImageRaw!=nullptr && fgImageRaw2 !=nullptr)
1098  {
1100  flex_i.resize(fgImageRaw->width()*2,fgImageRaw->height());
1101  fgImageRaw->getImage(*img_Raw);
1102  fgImageRaw2->getImage(*img2_Raw);
1103  bool ok = utils::horzConcat(*img_Raw, *img2_Raw, flex_i);
1104  if (!ok)
1105  {
1106  yCError(SERVERGRABBER) << "Failed to concatenate images";
1107  return;
1108  }
1109  } else {
1110  yCError(SERVERGRABBER) << "Image not captured.. check hardware configuration";
1111  }
1112  }
1113 
1114  Stamp s = Stamp(count,Time::now());
1115  pImg.setStrict(!param.canDrop);
1116  pImg.setEnvelope(s);
1117  pImg.write();
1118  count++;
1119  }
1120  }
1121  else
1122  {
1123  if(param.splitterMode)
1124  {
1125  FlexImage& flex_i=pImg.prepare();
1126  FlexImage& flex_i2=pImg2.prepare();
1127 
1128  if(param.cap==COLOR)
1129  {
1130  if(fgImage!=nullptr)
1131  {
1133  fgImage->getImage(inputImage);
1134 
1135  bool ok = utils::vertSplit(inputImage,*img,*img2);
1136  if (!ok)
1137  {
1138  yCError(SERVERGRABBER) << "Failed to split the image";
1139  return;
1140  }
1141 
1142  setupFlexImage(*img,flex_i);
1143  setupFlexImage(*img2,flex_i2);
1144  } else {
1145  yCError(SERVERGRABBER) << "Image not captured.. check hardware configuration";
1146  }
1147  }
1148  if(param.cap==RAW)
1149  {
1150  if(fgImageRaw!=nullptr)
1151  {
1153  fgImageRaw->getImage(inputImage);
1154 
1155  bool ok = utils::vertSplit(inputImage,*img_Raw,*img2_Raw);
1156  if (!ok)
1157  {
1158  yCError(SERVERGRABBER) << "Failed to split the image";
1159  return;
1160  }
1161 
1162  setupFlexImage(*img_Raw,flex_i);
1163  setupFlexImage(*img2_Raw,flex_i2);
1164 
1165  } else {
1166  yCError(SERVERGRABBER) << "Image not captured.. check hardware configuration";
1167  }
1168  }
1169  Stamp s = Stamp(count,Time::now());
1170  pImg.setStrict(!param.canDrop);
1171  pImg.setEnvelope(s);
1172  pImg.write();
1173  pImg2.setStrict(!param.canDrop);
1174  Stamp s2 = Stamp(count2,Time::now());
1175  pImg2.setEnvelope(s2);
1176  pImg2.write();
1177  count++;
1178  count2++;
1179  }
1180  else
1181  {
1182  FlexImage& flex_i=pImg.prepare();
1183 
1184  if(param.cap==COLOR)
1185  {
1186  if(fgImage!=nullptr)
1187  {
1188  fgImage->getImage(*img);
1189  setupFlexImage(*img,flex_i);
1190  } else {
1191  yCError(SERVERGRABBER) << "Image not captured.. check hardware configuration";
1192  }
1193  }
1194  if(param.cap==RAW)
1195  {
1196  if(fgImageRaw!=nullptr)
1197  {
1198  fgImageRaw->getImage(*img_Raw);
1199  setupFlexImage(*img_Raw,flex_i);
1200  } else {
1201  yCError(SERVERGRABBER) << "Image not captured.. check hardware configuration";
1202  }
1203  }
1204  Stamp s = Stamp(count,Time::now());
1205  pImg.setStrict(!param.canDrop);
1206  pImg.setEnvelope(s);
1207  pImg.write();
1208  count++;
1209  }
1210  }
1211 }
1212 
1214 {
1215  dest.setPixelCode(src.getPixelCode());
1216  dest.setQuantum(src.getQuantum());
1217  dest.setTopIsLowIndex(src.topIsLowIndex());
1218  dest.setExternal(src.getRawImage(), src.width(), src.height());
1219 }
1221 {
1222  if(param.cap==COLOR)
1223  {
1224  if(img!=nullptr)
1225  {
1226  delete img;
1227  img=nullptr;
1228  }
1229  if(img2!=nullptr)
1230  {
1231  delete img2;
1232  img2=nullptr;
1233  }
1234  }
1235  else
1236  {
1237  if(img_Raw!=nullptr)
1238  {
1239  delete img_Raw;
1240  img_Raw=nullptr;
1241  }
1242  if(img2_Raw!=nullptr)
1243  {
1244  delete img2_Raw;
1245  img2_Raw=nullptr;
1246  }
1247  }
1248 }
constexpr yarp::conf::vocab32_t VOCAB_RGB_VISUAL_PARAMS
Definition: CameraVocabs.h:18
constexpr yarp::conf::vocab32_t VOCAB_FRAMEGRABBER_CONTROL_DC1394
Definition: CameraVocabs.h:40
constexpr yarp::conf::vocab32_t VOCAB_FRAMEGRABBER_IMAGE
Definition: CameraVocabs.h:16
constexpr yarp::conf::vocab32_t VOCAB_FRAMEGRABBER_CONTROL
Definition: CameraVocabs.h:39
constexpr yarp::conf::vocab32_t VOCAB_CROP
Definition: CameraVocabs.h:38
constexpr yarp::conf::vocab32_t VOCAB_INTRINSIC_PARAM
Definition: CameraVocabs.h:106
constexpr yarp::conf::vocab32_t VOCAB_IS
Definition: GenericVocabs.h:14
constexpr yarp::conf::vocab32_t VOCAB_GET
Definition: GenericVocabs.h:13
constexpr yarp::conf::vocab32_t VOCAB_FAILED
Definition: GenericVocabs.h:16
constexpr yarp::conf::vocab32_t VOCAB_SET
Definition: GenericVocabs.h:12
cropType_id_t
@ YARP_CROP_LIST
@ YARP_CROP_RECT
bool ret
constexpr double DEFAULT_THREAD_PERIOD
contains the definition of a Vector type
ServerGrabberResponder(bool _left=false)
bool configure(ServerGrabber *_server)
bool respond(const yarp::os::Bottle &command, yarp::os::Bottle &reply) override
Respond to a message.
grabberDual deprecated: A Network grabber for camera devices.
void threadRelease() override
Release method.
bool attach(yarp::dev::PolyDriver *poly) override
Attach to another object.
bool detach() override
Detach the object (you must have first called attach).
bool open(yarp::os::Searchable &config) override
Configure with a set of options.
bool close() override
Close the DeviceDriver.
void setupFlexImage(const yarp::sig::Image &img, yarp::sig::FlexImage &flex_i)
void shallowCopyImages(const yarp::sig::FlexImage &src, yarp::sig::FlexImage &dest)
void run() override
Loop function.
bool detachAll() override
Detach the object (you must have first called attach).
bool initialize_YARP(yarp::os::Searchable &params)
bool attachAll(const yarp::dev::PolyDriverList &device2attach) override
Attach to a list of objects.
bool respond(const yarp::os::Bottle &command, yarp::os::Bottle &reply, bool left, bool both)
bool fromConfig(yarp::os::Searchable &config)
bool threadInit() override
Initialization method.
bool view(T *&x)
Get an interface to the device driver.
Definition: DeviceDriver.h:74
virtual int width() const =0
Return the width of each frame.
virtual int height() const =0
Return the height of each frame.
virtual bool getImageCrop(cropType_id_t cropType, yarp::sig::VectorOf< std::pair< int, int >> vertices, ImageType &image)
Get a crop of the image from the frame grabber.
virtual bool getImage(ImageType &image)=0
Get an image from the frame grabber.
void push(PolyDriver *p, const char *k)
A container for a device driver.
Definition: PolyDriver.h:24
bool close() override
Close the DeviceDriver.
Definition: PolyDriver.cpp:173
bool isValid() const
Check if device is valid.
Definition: PolyDriver.cpp:196
bool open(const std::string &txt)
Construct and configure a device by its common name.
Definition: PolyDriver.cpp:140
A simple collection of objects that can be described and transmitted in a portable way.
Definition: Bottle.h:74
void add(const Value &value)
Add a Value to the bottle, at the end of the list.
Definition: Bottle.cpp:336
void addVocab32(yarp::conf::vocab32_t x)
Places a vocabulary item in the bottle, at the end of the list.
Definition: Bottle.cpp:164
void append(const Bottle &alt)
Append the content of the given bottle to the current list.
Definition: Bottle.cpp:380
Bottle & addList()
Places an empty nested list in the bottle, at the end of the list.
Definition: Bottle.cpp:182
size_type size() const
Gets the number of elements in the bottle.
Definition: Bottle.cpp:251
Value & get(size_type index) const
Reads a Value v from a certain part of the list.
Definition: Bottle.cpp:246
void clear()
Empties the bottle of any objects it contains.
Definition: Bottle.cpp:121
void addInt32(std::int32_t x)
Places a 32-bit integer in the bottle, at the end of the list.
Definition: Bottle.cpp:140
void addString(const char *str)
Places a string in the bottle, at the end of the list.
Definition: Bottle.cpp:170
std::string toString() const override
Gives a human-readable textual representation of the bottle.
Definition: Bottle.cpp:211
void promiseType(const Type &typ) override
Commit the port to a particular type of data.
void close() override
Stop port activity.
bool setEnvelope(PortWriter &envelope) override
Set an envelope (e.g., a timestamp) to the next message which will be sent.
void setReader(PortReader &reader) override
Set an external reader for port data.
bool open(const std::string &name) override
Start port operation, with a specific name, with automatically-chosen network parameters.
void interrupt() override
Interrupt any current reads or writes attached to the port.
void setStrict(bool strict=true) override
Call this to strictly keep all messages, or allow old ones to be quietly dropped.
void write(bool forceStrict=false)
Write the current object being returned by BufferedPort::prepare.
T & prepare()
Access the object which will be transmitted by the next call to yarp::os::BufferedPort::write.
void setWriteOnly()
Shorthand for setInputMode(false), setOutputMode(true), setRpcMode(false)
Definition: Contactable.cpp:26
An abstraction for a periodic thread.
bool isRunning() const
Returns true when the thread is started, false otherwise.
void stop()
Call this to stop the thread, this call blocks until the thread is terminated (and releaseThread() ca...
void setReader(PortReader &reader) override
Set an external reader for port data.
Definition: Port.cpp:502
void interrupt() override
Interrupt any current reads or writes attached to the port.
Definition: Port.cpp:374
void close() override
Stop port activity.
Definition: Port.cpp:354
bool open(const std::string &name) override
Start port operation, with a specific name, with automatically-chosen network parameters.
Definition: Port.cpp:79
A class for storing options and configuration information.
Definition: Property.h:34
Value & find(const std::string &key) const override
Gets a value corresponding to a given keyword.
Definition: Property.cpp:1051
void fromString(const std::string &txt, bool wipe=true)
Interprets a string as a list of properties.
Definition: Property.cpp:1063
bool fromConfigFile(const std::string &fname, bool wipe=true)
Interprets a file as a list of properties.
Definition: Property.cpp:1098
void put(const std::string &key, const std::string &value)
Associate the given key with the given string.
Definition: Property.cpp:1015
void unput(const std::string &key)
Remove the association from the given key to a value, if present.
Definition: Property.cpp:1046
Helper class for finding config files and other external resources.
bool setDefaultContext(const std::string &contextName)
Sets the context for the current ResourceFinder object.
std::string findFileByName(const std::string &name)
Find the full path to a file.
A base class for nested structures that can be searched.
Definition: Searchable.h:66
virtual bool isNull() const
Checks if the object is invalid.
Definition: Searchable.cpp:107
virtual Value & find(const std::string &key) const =0
Gets a value corresponding to a given keyword.
virtual bool check(const std::string &key) const =0
Check if there exists a property of the given name.
virtual std::string toString() const =0
Return a standard text representation of the content of the object.
An abstraction for a time stamp and/or sequence number.
Definition: Stamp.h:22
A single value (typically within a Bottle).
Definition: Value.h:45
virtual yarp::conf::vocab32_t asVocab32() const
Get vocabulary identifier as an integer.
Definition: Value.cpp:228
virtual bool asBool() const
Get boolean value.
Definition: Value.cpp:186
virtual std::int32_t asInt32() const
Get 32-bit integer value.
Definition: Value.cpp:204
virtual Bottle * asList() const
Get list value.
Definition: Value.cpp:240
std::string toString() const override
Return a standard text representation of the content of the object.
Definition: Value.cpp:356
virtual bool isInt32() const
Checks if value is a 32-bit integer.
Definition: Value.cpp:132
virtual std::string asString() const
Get string value.
Definition: Value.cpp:234
bool respond(const yarp::os::Bottle &cmd, yarp::os::Bottle &response) override
Respond to a message.
bool respond(const yarp::os::Bottle &cmd, yarp::os::Bottle &response) override
Respond to a message.
bool configure(yarp::dev::IFrameGrabberControls *interface)
bool configure(yarp::dev::IRgbVisualParams *interface)
bool respond(const yarp::os::Bottle &cmd, yarp::os::Bottle &response) override
Respond to a message.
Image class with user control of representation details.
Definition: Image.h:414
void setQuantum(size_t imgQuantum)
Definition: Image.h:429
void setPixelCode(int imgPixelCode)
Definition: Image.h:417
Base class for storing images.
Definition: Image.h:82
bool topIsLowIndex() const
Definition: Image.h:353
size_t width() const
Gets width of image in pixels.
Definition: Image.h:166
void setExternal(const void *data, size_t imgWidth, size_t imgHeight)
Use this to wrap an external image.
Definition: Image.cpp:903
unsigned char * getRawImage() const
Access to the internal image buffer.
Definition: Image.cpp:541
size_t getRawImageSize() const
Access to the internal buffer size information (this is how much memory has been allocated for the im...
Definition: Image.cpp:550
void resize(size_t imgWidth, size_t imgHeight)
Reallocate an image to be of a desired size, throwing away its current contents.
Definition: Image.cpp:452
void setTopIsLowIndex(bool flag)
control whether image has origin at top left (default) or bottom left.
Definition: Image.cpp:511
size_t getQuantum() const
The size of a row is constrained to be a multiple of the "quantum".
Definition: Image.h:199
size_t height() const
Gets height of image in pixels.
Definition: Image.h:172
virtual int getPixelCode() const
Gets pixel type identifier.
Definition: Image.cpp:440
Provides:
Definition: Vector.h:119
void resize(size_t size) override
Resize the vector.
Definition: Vector.h:222
iterator begin() noexcept
Returns an iterator to the beginning of the VectorOf.
Definition: Vector.h:455
iterator end() noexcept
Returns an iterator to the end of the VectorOf.
Definition: Vector.h:462
#define yCInfo(component,...)
Definition: LogComponent.h:132
#define yCError(component,...)
Definition: LogComponent.h:154
#define yCWarning(component,...)
Definition: LogComponent.h:143
#define YARP_LOG_COMPONENT(name,...)
Definition: LogComponent.h:77
@ VOCAB_PIXEL_MONO
Definition: Image.h:45
@ VOCAB_PIXEL_RGB
Definition: Image.h:47
An interface for the device drivers.
double now()
Return the current time in seconds, relative to an arbitrary starting point.
Definition: Time.cpp:121
An interface to the operating system, including Port based communication.
bool vertSplit(const yarp::sig::Image &inImg, yarp::sig::Image &outImgL, yarp::sig::Image &outImgR)
Split vertically an image in two images of the same size.
Definition: ImageUtils.cpp:23
bool cropRect(const yarp::sig::Image &inImg, const std::pair< unsigned int, unsigned int > &vertex1, const std::pair< unsigned int, unsigned int > &vertex2, yarp::sig::Image &outImg)
Crop a rectangle area out of an image given two opposite vertices.
Definition: ImageUtils.cpp:113
bool horzConcat(const yarp::sig::Image &inImgL, const yarp::sig::Image &inImgR, yarp::sig::Image &outImg)
Concatenate horizontally two images of the same size in one with double width.
Definition: ImageUtils.cpp:67
Signal processing.
Definition: Image.h:22