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