For one of our customers in the scientific domain we do a lot of integration of pieces of hardware into the existing measurement- and control network. A good part of these are 2D detectors and scientific CCD cameras, which have all sorts of interfaces like ethernet, firewire and frame grabber cards. Our task is then to write some glue software that makes the camera available and controllable for the scientists.
One standard requirement for us is to do some basic image processing and analytics. Typically, this entails flipping the image horizontally and/or vertically, rotating the image around some multiple of 90 degrees, and calculcating some statistics like standard deviation.
The starting point there is always some image data in memory that has been acquired from the camera. Most of the time the image data is either gray values (8, or 16 bit), or RGB(A).
As we are generally not falling victim to the NIH syndrom we use open source image processing librarys. The first one we tried was CImg, which is a header-only (!) C++ library for image processing. The header-only part is very cool and handy, since you just have to #include <CImg.h> and you are done. No further dependencies. The immediate downside, of course, is long compile times. We are talking about > 40000 lines of C++ template code!
The bigger issue we had with CImg was that for multi-channel images the memory layout is like this: R1R2R3R4…..G1G2G3G4….B1B2B3B4. And since the images from the camera usually come interlaced like R1G1B1R2G2B2… we always had to do tricks to use CImg on these images correctly. These tricks killed us eventually in terms of performance, since some of these 2D detectors produce lots of megabytes of image data that have to be processed in real time.
So OpenCV. Their headline was already very promising:
OpenCV (Open Source Computer Vision) is a library of programming functions for real time computer vision.
Especially the words “real time” look good in there. But let’s see.
Image data in OpenCV is represented by instances of class cv::Mat, which is, of course, short for Matrix. From the documentation:
The class Mat represents an n-dimensional dense numerical single-channel or multi-channel array. It can be used to store real or complex-valued vectors and matrices, grayscale or color images, voxel volumes, vector fields, point clouds, tensors, histograms.
Our standard requirements stated above can then be implemented like this (gray scale, 8 bit image):
void processGrayScale8bitImage(uint16_t width, uint16_t height, const double& rotationAngle, uint8_t* pixelData) { // create cv::Mat instance // pixel data is not copied! cv::Mat img(height, width, CV_8UC1, pixelData); // flip vertically // third parameter of cv::flip is the so-called flip-code // flip-code == 0 means vertical flipping cv::Mat verticallyFlippedImg(height, width, CV_8UC1); cv::flip(img, verticallyFlippedImg, 0); // flip horizontally // flip-code > 0 means horizontal flipping cv::Mat horizontallyFlippedImg(height, width, CV_8UC1); cv::flip(img, horizontallyFlippedImg, 1); // rotation (a bit trickier) // 1. calculate center point cv::Point2f center(img.cols/2.0F, img.rows/2.0F); // 2. create rotation matrix cv::Mat rotationMatrix = cv::getRotationMatrix2D(center, rotationAngle, 1.0); // 3. create cv::Mat that will hold the rotated image. // For some rotationAngles width and height are switched cv::Mat rotatedImg; if ( (rotationAngle / 90.0) % 2 != 0) { // switch width and height for rotations like 90, 270 degrees rotatedImg = cv::Mat(cv::Size(img.size().height, img.size().width), img.type()); } else { rotatedImg = cv::Mat(cv::Size(img.size().width, img.size().height), img.type()); } // 4. actual rotation cv::warpAffine(img, rotatedImg, rotationMatrix, rotatedImg.size()); // save into TIFF file cv::imwrite("myimage.tiff", gray); }
The cool thing is that almost the same code can be used for our other image types, too. The only difference is the image type for the cv::Mat constructor:
8-bit gray scale: CV_U8C1
16bit gray scale: CV_U16C1
RGB : CV_U8C3
RGBA: CV_U8C4
Additionally, the whole thing is blazingly fast! All performance problems gone. Yay!
Getting basic statistical values is also a breeze:
void calculateStatistics(const cv::Mat& img) { // minimum, maximum, sum double min = 0.0; double max = 0.0; cv::minMaxLoc(img, &min, &max); double sum = cv::sum(img)[0]; // mean and standard deviation cv::Scalar cvMean; cv::Scalar cvStddev; cv::meanStdDev(img, cvMean, cvStddev); }
All in all, the OpenCV experience was very positive, so far. They even support CMake. Highly recommended!
I don’t know how deep these operations must be integrated, but for such simple tasks I’d use Python+Numpy+Scipy(+scikits-image). This means less (and simpler) code and more powerful array operations.
By the way, I have a deep hatred for the OpenCV code base since I’ve added some functionality to it. It is ugly and structured in an extremely weird way.
In what cases do `skimage` show simpler codes and perform more powerful array operations compared with `OpenCV`? Can you give a little demonstration?
By the way, `OpenCV` performs better than `skimage` when optimization is enabled. See[1,2].
Refs:
1. http://www.mastortosa.com/blog/python-image-processing-libraries-performance-opencv-scipy-scikit-image
2. http://pythonimaging.blogspot.com/2011/08/why-opencv-is-so-fast.html