PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkPlusOpenCVCaptureVideoSource.cxx
Go to the documentation of this file.
1 /*=Plus=header=begin======================================================
2 Program: Plus
3 Copyright (c) Laboratory for Percutaneous Surgery. All rights reserved.
4 See License.txt for details.
5 =========================================================Plus=header=end*/
6 
7 // Local includes
8 #include "PlusConfigure.h"
10 #include "vtkPlusChannel.h"
11 #include "vtkPlusDataSource.h"
12 
13 // VTK includes
14 #include <vtkImageData.h>
15 #include <vtkObjectFactory.h>
16 
17 // OpenCV includes
18 #if CV_MAJOR_VERSION > 3
19  #include <opencv2/calib3d.hpp>
20 #endif
21 #include <opencv2/imgproc.hpp>
22 #include <opencv2/highgui.hpp>
23 
24 //----------------------------------------------------------------------------
25 
27 
28 //----------------------------------------------------------------------------
30  : VideoURL("")
31  , FourCC("")
32  , RequestedCaptureAPI(cv::CAP_ANY)
33  , DeviceIndex(-1)
34  , Capture(nullptr)
35  , Frame(nullptr)
36  , UndistortedFrame(nullptr)
37  , CameraMatrix(nullptr)
38  , DistortionCoefficients(nullptr)
39  , AutofocusEnabled(false)
40  , AutoexposureEnabled(false)
41 {
42  this->FrameSize = { 0, 0, 0 };
44  this->StartThreadForInternalUpdates = true;
45 }
46 
47 //----------------------------------------------------------------------------
49 {
50 }
51 
52 //----------------------------------------------------------------------------
53 void vtkPlusOpenCVCaptureVideoSource::PrintSelf(ostream& os, vtkIndent indent)
54 {
55  this->Superclass::PrintSelf(os, indent);
56 
57  os << indent << "VideoURL: " << this->VideoURL << std::endl;
58  os << indent << "DeviceIndex: " << this->DeviceIndex << std::endl;
59  os << indent << "RequestedCaptureAPI: " << vtkPlusOpenCVCaptureVideoSource::StringFromCaptureAPI(this->RequestedCaptureAPI) << std::endl;
60 
61  if (this->CameraMatrix != nullptr)
62  {
63  os << indent << "CamerMatrix: " << *this->CameraMatrix << std::endl;
64  }
65  if (this->DistortionCoefficients != nullptr)
66  {
67  os << indent << "DistortionCoefficients: " << *this->DistortionCoefficients << std::endl;
68  }
69 }
70 
71 //-----------------------------------------------------------------------------
73 {
74  LOG_TRACE("vtkPlusOpenCVCaptureVideoSource::ReadConfiguration");
75  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement);
76 
77  XML_READ_STRING_ATTRIBUTE_OPTIONAL(VideoURL, deviceConfig);
78  std::string captureApi;
79  XML_READ_STRING_ATTRIBUTE_NONMEMBER_OPTIONAL(CaptureAPI, captureApi, deviceConfig);
80  if (!captureApi.empty())
81  {
82  this->RequestedCaptureAPI = CaptureAPIFromString(captureApi);
83  }
84 
85  XML_READ_SCALAR_ATTRIBUTE_OPTIONAL(int, DeviceIndex, deviceConfig);
86  int frameSize[3] = { 0, 0, 1 };
87  XML_READ_VECTOR_ATTRIBUTE_NONMEMBER_OPTIONAL(int, 2, FrameSize, frameSize, deviceConfig);
88  if (deviceConfig->GetAttribute("FrameSize") != NULL)
89  {
90  std::copy(std::begin(frameSize), std::end(frameSize), this->FrameSize.begin());
91  }
92 
93  XML_READ_STRING_ATTRIBUTE_OPTIONAL(FourCC, deviceConfig);
94  if (!this->FourCC.empty() && this->FourCC.length() != 4)
95  {
96  LOG_WARNING("Unable to parse desired FourCC string.");
97  this->FourCC = "";
98  }
99 
100  std::vector<double> camMat;
101  camMat.resize(9);
102  XML_READ_STD_ARRAY_ATTRIBUTE_NONMEMBER_EXACT_OPTIONAL(double, CameraMatrix, 9, camMat, deviceConfig);
103  if (deviceConfig->GetAttribute("CameraMatrix") != NULL)
104  {
105  this->CameraMatrix = std::make_shared<cv::Mat>(3, 3, CV_64F);
106  memcpy(this->CameraMatrix->data, camMat.data(), sizeof(double) * 9);
107  }
108 
109  std::vector<double> distCoeffs(8, std::numeric_limits<double>::infinity());
110  XML_READ_STD_ARRAY_ATTRIBUTE_NONMEMBER_OPTIONAL(double, DistortionCoefficients, 8, distCoeffs, deviceConfig);
111  if (deviceConfig->GetAttribute("DistortionCoefficients") != NULL)
112  {
113  std::vector<double>::difference_type count = std::count_if(distCoeffs.begin(), distCoeffs.end(), [this](const double & val) {return val != std::numeric_limits<double>::infinity(); });
114  this->DistortionCoefficients = std::make_shared<cv::Mat>(count, 1, CV_64F);
115  // Assumes all values are front filled
116  for (std::vector<double>::difference_type i = 0; i < count; ++i)
117  {
118  this->DistortionCoefficients->at<double>(i, 0) = distCoeffs[i];
119  }
120  }
121 
122  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(AutofocusEnabled, deviceConfig);
123  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(AutoexposureEnabled, deviceConfig);
124 
125  return PLUS_SUCCESS;
126 }
127 
128 //-----------------------------------------------------------------------------
130 {
131  LOG_TRACE("vtkPlusOpenCVCaptureVideoSource::WriteConfiguration");
132  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfigElement);
133 
134  XML_WRITE_STRING_ATTRIBUTE_REMOVE_IF_EMPTY(VideoURL, deviceConfig);
135  if (this->RequestedCaptureAPI != cv::CAP_ANY)
136  {
137  deviceConfig->SetAttribute("CaptureAPI", StringFromCaptureAPI(this->RequestedCaptureAPI).c_str());
138  }
139  if (this->DeviceIndex >= 0)
140  {
141  deviceConfig->SetIntAttribute("DeviceIndex", this->DeviceIndex);
142  }
143  if (this->CameraMatrix != nullptr)
144  {
145  deviceConfig->SetVectorAttribute("CameraMatrix", 9, this->CameraMatrix->ptr<double>(0));
146  }
147  if (this->DistortionCoefficients != nullptr)
148  {
149  deviceConfig->SetVectorAttribute("DistortionCoefficients", this->DistortionCoefficients->rows, this->DistortionCoefficients->ptr<double>(0));
150  }
151 
152  XML_WRITE_STRING_ATTRIBUTE_IF_NOT_EMPTY(FourCC, deviceConfig);
153 
154  XML_WRITE_BOOL_ATTRIBUTE(AutofocusEnabled, deviceConfig);
155  XML_WRITE_BOOL_ATTRIBUTE(AutoexposureEnabled, deviceConfig);
156 
157  return PLUS_SUCCESS;
158 }
159 
160 //----------------------------------------------------------------------------
162 {
163  if (freeze)
164  {
165  this->Disconnect();
166  }
167  else
168  {
169  this->Connect();
170  }
171  return PLUS_SUCCESS;
172 }
173 
174 //----------------------------------------------------------------------------
176 {
177  if (!this->VideoURL.empty())
178  {
179  this->Capture = std::make_shared<cv::VideoCapture>(this->VideoURL, this->RequestedCaptureAPI);
180  }
181  else if (this->DeviceIndex >= 0)
182  {
183  this->Capture = std::make_shared<cv::VideoCapture>(this->DeviceIndex + this->RequestedCaptureAPI);
184  }
185  else
186  {
187  LOG_ERROR("No device identification method defined. Please add either \"VideoURL\" or \"DeviceIndex\" attribute to configuration.");
188  return PLUS_FAIL;
189  }
190 
191  if (!this->Capture->isOpened())
192  {
193  LOG_ERROR("Unable to open OpenCV video device.");
194  return PLUS_FAIL;
195  }
196 
197  if (this->RequestedCaptureAPI != cv::CAP_GSTREAMER)
198  {
199  if (!this->Capture->set(cv::CAP_PROP_FPS, this->AcquisitionRate))
200  {
201  LOG_WARNING("Unable to set requested acquisition rate: " << this->AcquisitionRate);
202  }
203  if (!this->FourCC.empty() && !this->Capture->set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc(this->FourCC[0], this->FourCC[1], this->FourCC[2], this->FourCC[3])))
204  {
205  LOG_WARNING("Unable to set requested fourCC code: " << this->FourCC);
206  }
207  if (this->FrameSize[1] != 0)
208  {
209  if (!this->Capture->set(cv::CAP_PROP_FRAME_HEIGHT, this->FrameSize[1]))
210  {
211  LOG_ERROR("Unable to set the requested height of the capture device.");
212  }
213  if (!this->Capture->set(cv::CAP_PROP_FRAME_WIDTH, this->FrameSize[0]))
214  {
215  LOG_ERROR("Unable to set the requested width of the capture device.");
216  }
217  }
218  if (!this->Capture->set(cv::CAP_PROP_AUTOFOCUS, this->AutofocusEnabled ? 1 : 0))
219  {
220  if (this->AutofocusEnabled)
221  {
222  LOG_WARNING("Could not set the autofocus property.");
223  }
224  }
225  if (!this->Capture->set(cv::CAP_PROP_AUTO_EXPOSURE, this->AutoexposureEnabled ? 1 : 0))
226  {
227  if (this->AutoexposureEnabled)
228  {
229  LOG_WARNING("Could not set the autoexposure property.");
230  }
231  }
232  }
233 
234  this->FrameSize[0] = cvRound(this->Capture->get(cv::CAP_PROP_FRAME_WIDTH));
235  this->FrameSize[1] = cvRound(this->Capture->get(cv::CAP_PROP_FRAME_HEIGHT));
236  this->AcquisitionRate = cvRound(this->Capture->get(cv::CAP_PROP_FPS));
237 
238  this->Frame = std::make_shared<cv::Mat>(this->FrameSize[1], this->FrameSize[0], CV_8UC3);
239 
240  if (this->CameraMatrix != nullptr && this->DistortionCoefficients != nullptr)
241  {
242  this->UndistortedFrame = std::make_shared<cv::Mat>(this->FrameSize[1], this->FrameSize[0], CV_8UC3);
243  }
244  else
245  {
246  this->UndistortedFrame = this->Frame;
247  }
248 
249  if (!this->Capture->isOpened())
250  {
251  if (!this->VideoURL.empty())
252  {
253  LOG_ERROR("Unable to open device at URL: " << this->VideoURL);
254  }
255  else
256  {
257  LOG_ERROR("Unable to open device at device index: " << this->DeviceIndex);
258  }
259  return PLUS_FAIL;
260  }
261 
262  return PLUS_SUCCESS;
263 }
264 
265 //----------------------------------------------------------------------------
267 {
268  this->Capture = nullptr; // automatically closes resources/connections
269  this->Frame = nullptr;
270  this->UndistortedFrame = nullptr;
271 
272  return PLUS_SUCCESS;
273 }
274 
275 //----------------------------------------------------------------------------
277 {
278  LOG_TRACE("vtkPlusOpenCVCaptureVideoSource::InternalUpdate");
279 
280  if (!this->Capture->isOpened())
281  {
282  // No need to update if we're not able to read data
283  return PLUS_SUCCESS;
284  }
285 
286  // Capture one frame from the OpenCV capture device
287  if (!this->Capture->read(*this->Frame))
288  {
289  LOG_ERROR("Unable to receive frame");
290  return PLUS_FAIL;
291  }
292 
293  if (this->CameraMatrix != nullptr && this->DistortionCoefficients != nullptr)
294  {
295  cv::undistort(*this->Frame, *this->UndistortedFrame, *this->CameraMatrix, *this->DistortionCoefficients);
296  }
297 
298  // BGR -> RGB color
299  cv::cvtColor(*this->UndistortedFrame, *this->UndistortedFrame, cv::COLOR_BGR2RGB);
300 
301  vtkPlusDataSource* aSource(nullptr);
302  if (this->GetFirstActiveOutputVideoSource(aSource) == PLUS_FAIL || aSource == nullptr)
303  {
304  LOG_ERROR("Unable to grab a video source. Skipping frame.");
305  return PLUS_FAIL;
306  }
307 
308  if (aSource->GetNumberOfItems() == 0)
309  {
310  // Init the buffer with the metadata from the first frame
311  aSource->SetImageType(US_IMG_RGB_COLOR);
312  aSource->SetPixelType(VTK_UNSIGNED_CHAR);
313  aSource->SetNumberOfScalarComponents(3);
314  aSource->SetInputFrameSize(this->UndistortedFrame->cols, this->UndistortedFrame->rows, 1);
315  }
316 
317  // Add the frame to the stream buffer
318  FrameSizeType frameSize = { static_cast<unsigned int>(this->UndistortedFrame->cols), static_cast<unsigned int>(this->UndistortedFrame->rows), 1 };
319  if (aSource->AddItem(this->UndistortedFrame->data, aSource->GetInputImageOrientation(), frameSize, VTK_UNSIGNED_CHAR, 3, US_IMG_RGB_COLOR, 0, this->FrameNumber) == PLUS_FAIL)
320  {
321  return PLUS_FAIL;
322  }
323 
324  this->FrameNumber++;
325 
326  return PLUS_SUCCESS;
327 }
328 
329 //----------------------------------------------------------------------------
331 {
332  if (this->OutputChannels.size() > 1)
333  {
334  LOG_WARNING("vtkPlusOpenCVCaptureVideoSource is expecting one output channel and there are " << this->OutputChannels.size() << " channels. First output channel will be used.");
335  }
336 
337  if (this->OutputChannels.empty())
338  {
339  LOG_ERROR("No output channels defined for vtkPlusOpenCVCaptureVideoSource. Cannot proceed.");
340  this->CorrectlyConfigured = false;
341  return PLUS_FAIL;
342  }
343 
344  if ((this->CameraMatrix != nullptr) != (this->DistortionCoefficients != nullptr)) // XOR
345  {
346  LOG_WARNING("Only one of CameraMatrix or DistortionCoefficients defined in config file, cannot perform undistortion.");
347  }
348 
349  if (this->VideoURL.empty() && this->DeviceIndex < 0)
350  {
351  LOG_ERROR("No device identification method defined. Please add either \"VideoURL\" or \"DeviceIndex\" attribute to configuration.");
352  return PLUS_FAIL;
353  }
354 
355  if (this->DeviceIndex >= 0 && this->RequestedCaptureAPI == cv::CAP_FFMPEG)
356  {
357  LOG_ERROR("Cannot index FFMPEG devices by DeviceIndex. VideoURL must be used instead.");
358  return PLUS_FAIL;
359  }
360 
361  return PLUS_SUCCESS;
362 }
363 
364 //----------------------------------------------------------------------------
365 cv::VideoCaptureAPIs vtkPlusOpenCVCaptureVideoSource::CaptureAPIFromString(const std::string& apiString)
366 {
367  if (apiString.compare("CAP_ANY") == 0)
368  {
369  return cv::CAP_ANY;
370  }
371  else if (apiString.compare("CAP_VFW") == 0)
372  {
373  return cv::CAP_VFW;
374  }
375  else if (apiString.compare("CAP_V4L") == 0)
376  {
377  return cv::CAP_V4L;
378  }
379  else if (apiString.compare("CAP_V4L2") == 0)
380  {
381  return cv::CAP_V4L2;
382  }
383  else if (apiString.compare("CAP_FIREWIRE") == 0)
384  {
385  return cv::CAP_FIREWIRE;
386  }
387  else if (apiString.compare("CAP_FIREWARE") == 0)
388  {
389  return cv::CAP_FIREWARE;
390  }
391  else if (apiString.compare("CAP_IEEE1394") == 0)
392  {
393  return cv::CAP_IEEE1394;
394  }
395  else if (apiString.compare("CAP_DC1394") == 0)
396  {
397  return cv::CAP_DC1394;
398  }
399  else if (apiString.compare("CAP_CMU1394") == 0)
400  {
401  return cv::CAP_CMU1394;
402  }
403  else if (apiString.compare("CAP_QT") == 0)
404  {
405  return cv::CAP_QT;
406  }
407  else if (apiString.compare("CAP_UNICAP") == 0)
408  {
409  return cv::CAP_UNICAP;
410  }
411  else if (apiString.compare("CAP_DSHOW") == 0)
412  {
413  return cv::CAP_DSHOW;
414  }
415  else if (apiString.compare("CAP_PVAPI") == 0)
416  {
417  return cv::CAP_PVAPI;
418  }
419  else if (apiString.compare("CAP_OPENNI") == 0)
420  {
421  return cv::CAP_OPENNI;
422  }
423  else if (apiString.compare("CAP_OPENNI_ASUS") == 0)
424  {
425  return cv::CAP_OPENNI_ASUS;
426  }
427  else if (apiString.compare("CAP_ANDROID") == 0)
428  {
429  return cv::CAP_ANDROID;
430  }
431  else if (apiString.compare("CAP_XIAPI") == 0)
432  {
433  return cv::CAP_XIAPI;
434  }
435  else if (apiString.compare("CAP_AVFOUNDATION") == 0)
436  {
437  return cv::CAP_AVFOUNDATION;
438  }
439  else if (apiString.compare("CAP_GIGANETIX") == 0)
440  {
441  return cv::CAP_GIGANETIX;
442  }
443  else if (apiString.compare("CAP_MSMF") == 0)
444  {
445  return cv::CAP_MSMF;
446  }
447  else if (apiString.compare("CAP_WINRT") == 0)
448  {
449  return cv::CAP_WINRT;
450  }
451  else if (apiString.compare("CAP_INTELPERC") == 0)
452  {
453  return cv::CAP_INTELPERC;
454  }
455  else if (apiString.compare("CAP_OPENNI2") == 0)
456  {
457  return cv::CAP_OPENNI2;
458  }
459  else if (apiString.compare("CAP_OPENNI2_ASUS") == 0)
460  {
461  return cv::CAP_OPENNI2_ASUS;
462  }
463  else if (apiString.compare("CAP_GPHOTO2") == 0)
464  {
465  return cv::CAP_GPHOTO2;
466  }
467  else if (apiString.compare("CAP_GSTREAMER") == 0)
468  {
469  return cv::CAP_GSTREAMER;
470  }
471  else if (apiString.compare("CAP_FFMPEG") == 0)
472  {
473  return cv::CAP_FFMPEG;
474  }
475  else if (apiString.compare("CAP_IMAGES") == 0)
476  {
477  return cv::CAP_IMAGES;
478  }
479  else if (apiString.compare("CAP_ARAVIS") == 0)
480  {
481  return cv::CAP_ARAVIS;
482  }
483 
484  LOG_WARNING("Unable to match requested API " << apiString << ". Defaulting to CAP_ANY");
485  return cv::CAP_ANY;
486 }
487 
488 #define _StringFromEnum(x) std::string(#x)
489 //----------------------------------------------------------------------------
490 std::string vtkPlusOpenCVCaptureVideoSource::StringFromCaptureAPI(cv::VideoCaptureAPIs api)
491 {
492  switch (api)
493  {
494  case cv::CAP_ANY:
495  return _StringFromEnum(CAP_ANY);
496  case cv::CAP_VFW:
497  return _StringFromEnum(CAP_VFW);
498  case cv::CAP_FIREWIRE:
499  return _StringFromEnum(CAP_FIREWIRE);
500  case cv::CAP_QT:
501  return _StringFromEnum(CAP_QT);
502  case cv::CAP_UNICAP:
503  return _StringFromEnum(CAP_UNICAP);
504  case cv::CAP_DSHOW:
505  return _StringFromEnum(CAP_DSHOW);
506  case cv::CAP_PVAPI:
507  return _StringFromEnum(CAP_PVAPI);
508  case cv::CAP_OPENNI:
509  return _StringFromEnum(CAP_OPENNI);
510  case cv::CAP_OPENNI_ASUS:
511  return _StringFromEnum(CAP_OPENNI_ASUS);
512  case cv::CAP_ANDROID:
513  return _StringFromEnum(CAP_ANDROID);
514  case cv::CAP_XIAPI:
515  return _StringFromEnum(CAP_XIAPI);
516  case cv::CAP_AVFOUNDATION:
517  return _StringFromEnum(CAP_AVFOUNDATION);
518  case cv::CAP_GIGANETIX:
519  return _StringFromEnum(CAP_GIGANETIX);
520  case cv::CAP_MSMF:
521  return _StringFromEnum(CAP_MSMF);
522  case cv::CAP_WINRT:
523  return _StringFromEnum(CAP_WINRT);
524  case cv::CAP_INTELPERC:
525  return _StringFromEnum(CAP_INTELPERC);
526  case cv::CAP_OPENNI2:
527  return _StringFromEnum(CAP_OPENNI2);
528  case cv::CAP_OPENNI2_ASUS:
529  return _StringFromEnum(CAP_OPENNI2_ASUS);
530  case cv::CAP_GPHOTO2:
531  return _StringFromEnum(CAP_GPHOTO2);
532  case cv::CAP_GSTREAMER:
533  return _StringFromEnum(CAP_GSTREAMER);
534  case cv::CAP_FFMPEG:
535  return _StringFromEnum(CAP_FFMPEG);
536  case cv::CAP_IMAGES:
537  return _StringFromEnum(CAP_IMAGES);
538  case cv::CAP_ARAVIS:
539  return _StringFromEnum(CAP_ARAVIS);
540  default:
541  return "CAP_ANY";
542  }
543 }
544 #undef _StringFromEnum
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfigElement)
std::shared_ptr< cv::VideoCapture > Capture
igsioStatus PlusStatus
Definition: PlusCommon.h:40
vtkStandardNewMacro(vtkPlusOpenCVCaptureVideoSource)
PlusStatus SetInputFrameSize(unsigned int x, unsigned int y, unsigned int z)
virtual PlusStatus AddItem(vtkImageData *frame, US_IMAGE_ORIENTATION usImageOrientation, US_IMAGE_TYPE imageType, long frameNumber, double unfilteredTimestamp=UNDEFINED_TIMESTAMP, double filteredTimestamp=UNDEFINED_TIMESTAMP, const igsioFieldMapType *customFields=NULL)
Class for interfacing an OpenCVC capture device and recording frames into a Plus buffer.
bool RequireImageOrientationInConfiguration
PlusStatus SetImageType(US_IMAGE_TYPE imageType)
for i
double AcquisitionRate
#define PLUS_FAIL
Definition: PlusCommon.h:43
PlusStatus SetPixelType(igsioCommon::VTKScalarPixelType pixelType)
virtual PlusStatus ReadConfiguration(vtkXMLDataElement *config)
virtual PlusStatus Disconnect()
std::shared_ptr< cv::Mat > DistortionCoefficients
PlusStatus GetFirstActiveOutputVideoSource(vtkPlusDataSource *&aVideoSource)
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
unsigned long FrameNumber
static std::string StringFromCaptureAPI(cv::VideoCaptureAPIs api)
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
virtual PlusStatus Connect()
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement)
virtual US_IMAGE_ORIENTATION GetInputImageOrientation()
Phidget_ChannelClass uint32_t * count
Definition: phidget22.h:1321
bool StartThreadForInternalUpdates
ChannelContainer OutputChannels
PlusStatus SetNumberOfScalarComponents(unsigned int numberOfScalarComponents)
virtual PlusStatus WriteConfiguration(vtkXMLDataElement *config)
static cv::VideoCaptureAPIs CaptureAPIFromString(const std::string &apiString)
#define _StringFromEnum(x)
virtual int GetNumberOfItems()
bool CorrectlyConfigured
Interface to a 3D positioning tool, video source, or generalized data stream.