Solenoid
Solenoid

Reputation: 2381

Detect gray things with OpenCV

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.

Upvotes: 5

Views: 4293

Answers (2)

Aurelius
Aurelius

Reputation: 11349

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:

image with gray object

Gives the following result:

gray object detection mask

As you can see, the gray object is correctly detected.

Upvotes: 9

remi
remi

Reputation: 3988

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

Upvotes: 1

Related Questions