I'd like to detect an object using OpenCV that is distinctly different from other elements in the scene as it's gray. This is good because I can just run a test with R == G == B and it allows to be independent of luminosity, but doing it pixel by pixel is slow.
Is there a faster way to detect gray things? Maybe there's an OpenCV method that does the R == G == B test... cv2.inRange
does color thresholding, it's not quite what I'm looking for.
OpenCV has a bunch of pre-trained classifiers that can be used to identify objects such as trees, number plates, faces, eyes, etc. We can use any of these classifiers to detect the object as per our need.
OpenCV has some built-in functions to perform Color detection and Segmentation operations. So what are Color Detection and Segmentation Techniques in Image Processing? Color detection is a technique of detecting any color in a given range of HSV (hue saturation value) color space.
The fastest method I can find in Python is to use slicing to compare each channel. After a few test runs, this method is upwards of 200 times faster than two nested for-loops.
bg = im[:,:,0] == im[:,:,1] # B == G
gr = im[:,:,1] == im[:,:,2] # G == R
slices = np.bitwise_and(bg, gr, dtype= np.uint8) * 255
This will generate a binary image where gray objects are indicated by white pixels. If you do not need a binary image, but only a logical array where grey pixels are indicated by True
values, this method gets even faster:
slices = np.bitwise_and(bg, gr)
Omitting the type cast and multiplication yields a method 500 times faster than nested loops.
Running this operation on this test image:
Gives the following result:
As you can see, the gray object is correctly detected.
I'm surprised that such a simple check is slow, probably you are not coding it efficiently.
Here is a short piece of code that should do that for you. It optimal neither in speed nor in memory, but quite in number of lines of code :)
std::vector<cv::Mat> planes;
cv::split(image, planes);
cv::Mat mask = planes[0] == planes[1];
mask &= planes[1] == planes[2];
For the sake of it, here is a comparison with something that would be the fastest way to do it in my opinion (without parallelization)
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <vector>
#include <sys/time.h> //gettimeofday
static
double
P_ellapsedTime(struct timeval t0, struct timeval t1)
{
//return ellapsed time in seconds
return (t1.tv_sec-t0.tv_sec)*1.0 + (t1.tv_usec-t0.tv_usec)/1000000.0;
}
int
main(int argc, char* argv[])
{
struct timeval t0, t1;
cv::Mat image = cv::imread(argv[1]);
assert(image.type() == CV_8UC3);
std::vector<cv::Mat> planes;
std::cout << "Image resolution=" << image.rows << "x" << image.cols << std::endl;
gettimeofday(&t0, NULL);
cv::split(image, planes);
cv::Mat mask = planes[0] == planes[1];
mask &= planes[1] == planes[2];
gettimeofday(&t1, NULL);
std::cout << "Time using split: " << P_ellapsedTime(t0, t1) << "s" << std::endl;
cv::Mat mask2 = cv::Mat::zeros(image.size(), CV_8U);
unsigned char *imgBuf = image.data;
unsigned char *maskBuf = mask2.data;
gettimeofday(&t0, NULL);
for (; imgBuf != image.dataend; imgBuf += 3, maskBuf++)
*maskBuf = (imgBuf[0] == imgBuf[1] && imgBuf[1] == imgBuf[2]) ? 255 : 0;
gettimeofday(&t1, NULL);
std::cout << "Time using loop: " << P_ellapsedTime(t0, t1) << "s" << std::endl;
cv::namedWindow("orig", 0);
cv::imshow("orig", image);
cv::namedWindow("mask", 0);
cv::imshow("mask", mask);
cv::namedWindow("mask2", 0);
cv::imshow("mask2", mask2);
cv::waitKey(0);
}
Bench on an image:
Image resolution=3171x2179
Time using split: 0.06353s
Time using loop: 0.029044s
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With