1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4
5
6 /// \file
7 /// Implementation of ImageBufAlgo algorithms related to OpenCV.
8 /// These are nonfunctional if OpenCV is not found at build time.
9
10 #include <OpenImageIO/platform.h>
11
12 #ifdef USE_OPENCV
13 # include <opencv2/core/version.hpp>
14 # ifdef CV_VERSION_EPOCH
15 # define OIIO_OPENCV_VERSION \
16 (10000 * CV_VERSION_EPOCH + 100 * CV_VERSION_MAJOR \
17 + CV_VERSION_MINOR)
18 # else
19 # define OIIO_OPENCV_VERSION \
20 (10000 * CV_VERSION_MAJOR + 100 * CV_VERSION_MINOR \
21 + CV_VERSION_REVISION)
22 # endif
23 # if OIIO_GNUC_VERSION >= 110000 && OIIO_CPLUSPLUS_VERSION >= 20
24 // Suppress gcc 11 / C++20 errors about opencv 4 headers
25 # pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion"
26 # endif
27 # include <opencv2/opencv.hpp>
28 # if OIIO_OPENCV_VERSION >= 40000
29 # include <opencv2/core/core_c.h>
30 # include <opencv2/imgproc/imgproc_c.h>
31 # endif
32 #endif
33
34 #include <algorithm>
35 #include <iostream>
36 #include <map>
37 #include <vector>
38
39 #include <OpenImageIO/dassert.h>
40 #include <OpenImageIO/imagebuf.h>
41 #include <OpenImageIO/imagebufalgo.h>
42 #include <OpenImageIO/imagebufalgo_util.h>
43 #include <OpenImageIO/sysutil.h>
44 #include <OpenImageIO/thread.h>
45
46 #include "imageio_pvt.h"
47
48 // using namespace cv;
49
50 OIIO_NAMESPACE_BEGIN
51
52
53 namespace ImageBufAlgo {
54
55 // Note: DEPRECATED(2.0)
56 ImageBuf
from_IplImage(const IplImage * ipl,TypeDesc convert)57 from_IplImage(const IplImage* ipl, TypeDesc convert)
58 {
59 pvt::LoggedTimer logtime("IBA::from_IplImage");
60 ImageBuf dst;
61 if (!ipl) {
62 dst.errorf("Passed NULL source IplImage");
63 return dst;
64 }
65 #ifdef USE_OPENCV
66 TypeDesc srcformat;
67 switch (ipl->depth) {
68 case int(IPL_DEPTH_8U): srcformat = TypeDesc::UINT8; break;
69 case int(IPL_DEPTH_8S): srcformat = TypeDesc::INT8; break;
70 case int(IPL_DEPTH_16U): srcformat = TypeDesc::UINT16; break;
71 case int(IPL_DEPTH_16S): srcformat = TypeDesc::INT16; break;
72 case int(IPL_DEPTH_32F): srcformat = TypeDesc::FLOAT; break;
73 case int(IPL_DEPTH_64F): srcformat = TypeDesc::DOUBLE; break;
74 default:
75 dst.errorf("Unsupported IplImage depth %d", (int)ipl->depth);
76 return dst;
77 }
78
79 TypeDesc dstformat = (convert != TypeDesc::UNKNOWN) ? convert : srcformat;
80 ImageSpec spec(ipl->width, ipl->height, ipl->nChannels, dstformat);
81 // N.B. The OpenCV headers say that ipl->alphaChannel,
82 // ipl->colorModel, and ipl->channelSeq are ignored by OpenCV.
83
84 if (ipl->dataOrder != IPL_DATA_ORDER_PIXEL) {
85 // We don't handle separate color channels, and OpenCV doesn't either
86 dst.errorf("Unsupported IplImage data order %d", (int)ipl->dataOrder);
87 return dst;
88 }
89
90 dst.reset(dst.name(), spec);
91 size_t pixelsize = srcformat.size() * spec.nchannels;
92 // Account for the origin in the line step size, to end up with the
93 // standard OIIO origin-at-upper-left:
94 size_t linestep = ipl->origin ? -ipl->widthStep : ipl->widthStep;
95 // Block copy and convert
96 convert_image(spec.nchannels, spec.width, spec.height, 1, ipl->imageData,
97 srcformat, pixelsize, linestep, 0, dst.pixeladdr(0, 0),
98 dstformat, spec.pixel_bytes(), spec.scanline_bytes(), 0);
99 // FIXME - honor dataOrder. I'm not sure if it is ever used by
100 // OpenCV. Fix when it becomes a problem.
101
102 // OpenCV uses BGR ordering
103 // FIXME: what do they do with alpha?
104 if (spec.nchannels >= 3) {
105 float pixel[4];
106 for (int y = 0; y < spec.height; ++y) {
107 for (int x = 0; x < spec.width; ++x) {
108 dst.getpixel(x, y, pixel, 4);
109 float tmp = pixel[0];
110 pixel[0] = pixel[2];
111 pixel[2] = tmp;
112 dst.setpixel(x, y, pixel, 4);
113 }
114 }
115 }
116 // FIXME -- the copy and channel swap should happen all as one loop,
117 // probably templated by type.
118
119 #else
120 dst.errorf(
121 "fromIplImage not supported -- no OpenCV support at compile time");
122 #endif
123
124 return dst;
125 }
126
127
128
129 // Note: DEPRECATED(2.0)
130 IplImage*
to_IplImage(const ImageBuf & src)131 to_IplImage(const ImageBuf& src)
132 {
133 pvt::LoggedTimer logtime("IBA::to_IplImage");
134 #ifdef USE_OPENCV
135 ImageBuf tmp = src;
136 ImageSpec spec = tmp.spec();
137
138 // Make sure the image buffer is initialized.
139 if (!tmp.initialized() && !tmp.read(tmp.subimage(), tmp.miplevel(), true)) {
140 OIIO_DASSERT(0 && "Could not initialize ImageBuf.");
141 return NULL;
142 }
143
144 int dstFormat;
145 TypeDesc dstSpecFormat;
146 if (spec.format == TypeDesc(TypeDesc::UINT8)) {
147 dstFormat = IPL_DEPTH_8U;
148 dstSpecFormat = spec.format;
149 } else if (spec.format == TypeDesc(TypeDesc::INT8)) {
150 dstFormat = IPL_DEPTH_8S;
151 dstSpecFormat = spec.format;
152 } else if (spec.format == TypeDesc(TypeDesc::UINT16)) {
153 dstFormat = IPL_DEPTH_16U;
154 dstSpecFormat = spec.format;
155 } else if (spec.format == TypeDesc(TypeDesc::INT16)) {
156 dstFormat = IPL_DEPTH_16S;
157 dstSpecFormat = spec.format;
158 } else if (spec.format == TypeDesc(TypeDesc::HALF)) {
159 dstFormat = IPL_DEPTH_32F;
160 // OpenCV does not support half types. Switch to float instead.
161 dstSpecFormat = TypeDesc(TypeDesc::FLOAT);
162 } else if (spec.format == TypeDesc(TypeDesc::FLOAT)) {
163 dstFormat = IPL_DEPTH_32F;
164 dstSpecFormat = spec.format;
165 } else if (spec.format == TypeDesc(TypeDesc::DOUBLE)) {
166 dstFormat = IPL_DEPTH_64F;
167 dstSpecFormat = spec.format;
168 } else {
169 OIIO_DASSERT(0 && "Unknown data format in ImageBuf.");
170 return NULL;
171 }
172 IplImage* ipl = cvCreateImage(cvSize(spec.width, spec.height), dstFormat,
173 spec.nchannels);
174 if (!ipl) {
175 OIIO_DASSERT(0 && "Unable to create IplImage.");
176 return NULL;
177 }
178
179 size_t pixelsize = dstSpecFormat.size() * spec.nchannels;
180 // Account for the origin in the line step size, to end up with the
181 // standard OIIO origin-at-upper-left:
182 size_t linestep = ipl->origin ? -ipl->widthStep : ipl->widthStep;
183
184 bool converted = convert_image(spec.nchannels, spec.width, spec.height, 1,
185 tmp.localpixels(), spec.format,
186 spec.pixel_bytes(), spec.scanline_bytes(), 0,
187 ipl->imageData, dstSpecFormat, pixelsize,
188 linestep, 0);
189
190 if (!converted) {
191 OIIO_DASSERT(0 && "convert_image failed.");
192 cvReleaseImage(&ipl);
193 return NULL;
194 }
195
196 // OpenCV uses BGR ordering
197 if (spec.nchannels == 3) {
198 cvCvtColor(ipl, ipl, CV_RGB2BGR);
199 } else if (spec.nchannels == 4) {
200 cvCvtColor(ipl, ipl, CV_RGBA2BGRA);
201 }
202
203 return ipl;
204 #else
205 return NULL;
206 #endif
207 }
208
209
210
211 // Templated fast swap of R and B channels.
212 template<class Rtype>
213 static bool
RBswap(ImageBuf & R,ROI roi,int nthreads)214 RBswap(ImageBuf& R, ROI roi, int nthreads)
215 {
216 ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
217 for (ImageBuf::Iterator<Rtype, Rtype> r(R, roi); !r.done(); ++r)
218 for (int c = roi.chbegin; c < roi.chend; ++c) {
219 Rtype tmp = r[0];
220 r[0] = Rtype(r[2]);
221 r[2] = tmp;
222 }
223 });
224 return true;
225 }
226
227 } // end namespace ImageBufAlgo
228
229
230 ImageBuf
from_OpenCV(const cv::Mat & mat,TypeDesc convert,ROI roi,int nthreads)231 ImageBufAlgo::from_OpenCV(const cv::Mat& mat, TypeDesc convert, ROI roi,
232 int nthreads)
233 {
234 pvt::LoggedTimer logtime("IBA::from_OpenCV");
235 ImageBuf dst;
236 #ifdef USE_OPENCV
237 TypeDesc srcformat;
238 switch (mat.depth()) {
239 case CV_8U: srcformat = TypeDesc::UINT8; break;
240 case CV_8S: srcformat = TypeDesc::INT8; break;
241 case CV_16U: srcformat = TypeDesc::UINT16; break;
242 case CV_16S: srcformat = TypeDesc::INT16; break;
243 case CV_32F: srcformat = TypeDesc::FLOAT; break;
244 case CV_64F: srcformat = TypeDesc::DOUBLE; break;
245 default:
246 dst.errorf("Unsupported OpenCV data type, depth=%d", mat.depth());
247 return dst;
248 }
249
250 TypeDesc dstformat = (convert != TypeDesc::UNKNOWN) ? convert : srcformat;
251 ROI matroi(0, mat.cols, 0, mat.rows, 0, 1, 0, mat.channels());
252 roi = roi_intersection(roi, matroi);
253 ImageSpec spec(roi, dstformat);
254 dst.reset(dst.name(), spec);
255 size_t pixelsize = srcformat.size() * spec.nchannels;
256 size_t linestep = mat.step[0];
257 // Block copy and convert
258 parallel_convert_image(spec.nchannels, spec.width, spec.height, 1,
259 mat.ptr(), srcformat, pixelsize, linestep, 0,
260 dst.pixeladdr(roi.xbegin, roi.ybegin), dstformat,
261 spec.pixel_bytes(), spec.scanline_bytes(), 0, -1, -1,
262 nthreads);
263
264 // OpenCV uses BGR ordering
265 if (spec.nchannels >= 3) {
266 OIIO_MAYBE_UNUSED bool ok = true;
267 OIIO_DISPATCH_TYPES(ok, "from_OpenCV R/B swap", RBswap, dstformat, dst,
268 roi, nthreads);
269 }
270
271 #else
272 dst.errorf(
273 "from_OpenCV() not supported -- no OpenCV support at compile time");
274 #endif
275
276 return dst;
277 }
278
279
280
281 bool
to_OpenCV(cv::Mat & dst,const ImageBuf & src,ROI roi,int nthreads)282 ImageBufAlgo::to_OpenCV(cv::Mat& dst, const ImageBuf& src, ROI roi,
283 int nthreads)
284 {
285 pvt::LoggedTimer logtime("IBA::to_OpenCV");
286 #ifdef USE_OPENCV
287 if (!roi.defined())
288 roi = src.roi();
289 roi.chend = std::min(roi.chend, src.nchannels());
290 const ImageSpec& spec = src.spec();
291 int chans = std::min(4, roi.nchannels());
292 int dstFormat = 0;
293 TypeDesc dstSpecFormat = spec.format;
294 if (spec.format == TypeDesc(TypeDesc::UINT8)) {
295 dstFormat = CV_MAKETYPE(CV_8U, chans);
296 } else if (spec.format == TypeDesc(TypeDesc::INT8)) {
297 dstFormat = CV_MAKETYPE(CV_8S, chans);
298 } else if (spec.format == TypeDesc(TypeDesc::UINT16)) {
299 dstFormat = CV_MAKETYPE(CV_16U, chans);
300 } else if (spec.format == TypeDesc(TypeDesc::INT16)) {
301 dstFormat = CV_MAKETYPE(CV_16S, chans);
302 } else if (spec.format == TypeDesc(TypeDesc::UINT32)) {
303 dstFormat = CV_MAKETYPE(CV_16U, chans);
304 dstSpecFormat = TypeUInt16;
305 } else if (spec.format == TypeDesc(TypeDesc::INT32)) {
306 dstFormat = CV_MAKETYPE(CV_16S, chans);
307 dstSpecFormat = TypeInt16;
308 } else if (spec.format == TypeDesc(TypeDesc::HALF)) {
309 dstFormat = CV_MAKETYPE(CV_32F, chans);
310 dstSpecFormat = TypeFloat;
311 } else if (spec.format == TypeDesc(TypeDesc::FLOAT)) {
312 dstFormat = CV_MAKETYPE(CV_32F, chans);
313 } else if (spec.format == TypeDesc(TypeDesc::DOUBLE)) {
314 dstFormat = CV_MAKETYPE(CV_64F, chans);
315 } else {
316 OIIO_DASSERT(0 && "Unknown data format in ImageBuf.");
317 return false;
318 }
319 cv::Mat mat(roi.height(), roi.width(), dstFormat);
320 if (mat.empty()) {
321 OIIO_DASSERT(0 && "Unable to create cv::Mat.");
322 return false;
323 }
324
325 size_t pixelsize = dstSpecFormat.size() * chans;
326 size_t linestep = pixelsize * roi.width();
327 bool converted = parallel_convert_image(
328 chans, roi.width(), roi.height(), 1,
329 src.pixeladdr(roi.xbegin, roi.ybegin, roi.zbegin, roi.chbegin),
330 spec.format, spec.pixel_bytes(), spec.scanline_bytes(), 0, mat.ptr(),
331 dstSpecFormat, pixelsize, linestep, 0, -1, -1, nthreads);
332
333 if (!converted) {
334 OIIO_DASSERT(0 && "convert_image failed.");
335 return false;
336 }
337
338 // OpenCV uses BGR ordering
339 if (chans == 3) {
340 cv::cvtColor(mat, mat, cv::COLOR_RGB2BGR);
341 } else if (chans == 4) {
342 cv::cvtColor(mat, mat, cv::COLOR_RGBA2BGRA);
343 }
344
345 dst = std::move(mat);
346 return true;
347 #else
348 return false;
349 #endif
350 }
351
352
353
354 namespace {
355
356 #ifdef USE_OPENCV
357 static mutex opencv_mutex;
358
359 class CameraHolder {
360 public:
CameraHolder()361 CameraHolder() {}
362 // Destructor frees all cameras
~CameraHolder()363 ~CameraHolder() {}
364 // Get the capture device, creating a new one if necessary.
operator [](int cameranum)365 cv::VideoCapture* operator[](int cameranum)
366 {
367 auto i = m_cvcaps.find(cameranum);
368 if (i != m_cvcaps.end())
369 return i->second.get();
370 auto cvcam = new cv::VideoCapture(cameranum);
371 m_cvcaps[cameranum].reset(cvcam);
372 return cvcam;
373 }
374
375 private:
376 std::map<int, std::unique_ptr<cv::VideoCapture>> m_cvcaps;
377 };
378
379 static CameraHolder cameras;
380 #endif
381
382 } // namespace
383
384
385
386 ImageBuf
capture_image(int cameranum,TypeDesc convert)387 ImageBufAlgo::capture_image(int cameranum, TypeDesc convert)
388 {
389 pvt::LoggedTimer logtime("IBA::capture_image");
390 ImageBuf dst;
391 #ifdef USE_OPENCV
392 cv::Mat frame;
393 {
394 // This block is mutex-protected
395 lock_guard lock(opencv_mutex);
396 auto cvcam = cameras[cameranum];
397 if (!cvcam) {
398 dst.errorf("Could not create a capture camera (OpenCV error)");
399 return dst; // failed somehow
400 }
401 (*cvcam) >> frame;
402 if (frame.empty()) {
403 dst.errorf("Could not cvQueryFrame (OpenCV error)");
404 return dst; // failed somehow
405 }
406 }
407
408 logtime.stop();
409 dst = from_OpenCV(frame, convert);
410 logtime.start();
411 if (!dst.has_error()) {
412 time_t now;
413 time(&now);
414 struct tm tmtime;
415 Sysutil::get_local_time(&now, &tmtime);
416 std::string datetime = Strutil::sprintf("%4d:%02d:%02d %02d:%02d:%02d",
417 tmtime.tm_year + 1900,
418 tmtime.tm_mon + 1,
419 tmtime.tm_mday, tmtime.tm_hour,
420 tmtime.tm_min, tmtime.tm_sec);
421 dst.specmod().attribute("DateTime", datetime);
422 }
423 #else
424 dst.errorf(
425 "capture_image not supported -- no OpenCV support at compile time");
426 #endif
427 return dst;
428 }
429
430
431 OIIO_NAMESPACE_END
432