OpenCV: how to detect lines of a specific colour?

mm24 picture mm24 · Mar 8, 2016 · Viewed 8.6k times · Source

I am working on a small OpenCV project to detect lines of a certain colour from a mobile phone camera.

In short would like to:

  1. Transform the input image into an image of a certain colour (e.g. Red from a specific upper and lower range)
  2. Apply Hough line transformation to the resulting image so that it detects only lines of that specific colour
  3. Superimpose on the original image the lines detected

Those are the functions that I'd like to use but not quiet sure how to fill the missing bits.

This is the processImage function called from an smartphone app when processing images from an instance of CvVideoCamera

- (void)processImage:(Mat&)image;
{
cv::Mat orig_image = image.clone();

cv::Mat red_image = ?? 

// Apply houghes transformation to detect lines between a minimum length and a maximum length (I was thinking of using the CV_HOUGH_PROBABILISTIC method..)
// Comment.. see below..

I am unable to understand the documentation here as the C++ method signature does not have a method field

vector<Vec2f> lines;

From the official documentation:

C++: void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )

HoughLines(dst, lines, 1, CV_PI/180, 100, 0, 0 ); 

Taken from sample code, haven't understood properly how it works..

(e.g. what's the usage of theta? How does giving a different angle reflect into line detection?)

for( size_t i = 0; i < lines.size(); i++ )
{

Here I should only consider lines above a certain size.. (no idea how)

}

Here I should then add the resulting lines to original image (no idea how) so that they can be shown on the screen..

Any help would be greatly appreciated.

Answer

Micka picture Micka · Mar 8, 2016

You can use HSV color space to extract color tone information.

Here's some code with comments, if there are any questions feel free to ask:

int main(int argc, char* argv[])
{
    cv::Mat input = cv::imread("C:/StackOverflow/Input/coloredLines.png");

    // convert to HSV color space
    cv::Mat hsvImage;
    cv::cvtColor(input, hsvImage, CV_BGR2HSV);

    // split the channels
    std::vector<cv::Mat> hsvChannels;
    cv::split(hsvImage, hsvChannels);

    // hue channels tells you the color tone, if saturation and value aren't too low.

    // red color is a special case, because the hue space is circular and red is exactly at the beginning/end of the circle.
    // in literature, hue space goes from 0 to 360 degrees, but OpenCV rescales the range to 0 up to 180, because 360 does not fit in a single byte. Alternatively there is another mode where 0..360 is rescaled to 0..255 but this isn't as common.
    int hueValue = 0; // red color
    int hueRange = 15; // how much difference from the desired color we want to include to the result If you increase this value, for example a red color would detect some orange values, too.

    int minSaturation = 50; // I'm not sure which value is good here...
    int minValue = 50; // not sure whether 50 is a good min value here...

    cv::Mat hueImage = hsvChannels[0]; // [hue, saturation, value]

    // is the color within the lower hue range?
    cv::Mat hueMask;
    cv::inRange(hueImage, hueValue - hueRange, hueValue + hueRange, hueMask);

    // if the desired color is near the border of the hue space, check the other side too:
    // TODO: this won't work if "hueValue + hueRange > 180" - maybe use two different if-cases instead... with int lowerHueValue = hueValue - 180
    if (hueValue - hueRange < 0 || hueValue + hueRange > 180)
    {
        cv::Mat hueMaskUpper;
        int upperHueValue = hueValue + 180; // in reality this would be + 360 instead
        cv::inRange(hueImage, upperHueValue - hueRange, upperHueValue + hueRange, hueMaskUpper);

        // add this mask to the other one
        hueMask = hueMask | hueMaskUpper;
    }

    // now we have to filter out all the pixels where saturation and value do not fit the limits:
    cv::Mat saturationMask = hsvChannels[1] > minSaturation;
    cv::Mat valueMask = hsvChannels[2] > minValue;

    hueMask = (hueMask & saturationMask) & valueMask;

    cv::imshow("desired color", hueMask);

    // now perform the line detection
    std::vector<cv::Vec4i> lines;
    cv::HoughLinesP(hueMask, lines, 1, CV_PI / 360, 50, 50, 10);

    // draw the result as big green lines:
    for (unsigned int i = 0; i < lines.size(); ++i)
    {
        cv::line(input, cv::Point(lines[i][0], lines[i][1]), cv::Point(lines[i][2], lines[i][3]), cv::Scalar(0, 255, 0), 5);
    }


    cv::imwrite("C:/StackOverflow/Output/coloredLines_mask.png", hueMask);
    cv::imwrite("C:/StackOverflow/Output/coloredLines_detection.png", input);

    cv::imshow("input", input);
    cv::waitKey(0);
    return 0;
}

using this input image:

enter image description here

Will extract this "red" color (adjust hueValue and hueRange to detect different colors):

enter image description here

and HoughLinesP detects those lines from the mask (should work with HoughLines similarly):

enter image description here

Here's another set of images with non-lines too...

enter image description here

enter image description here

enter image description here

About your different questions:

  1. There are two functions HoughLines and HoughLinesP. HoughLines does not extract a line length, but you can compute it in a post-processing by checking again, which pixels of the edge-mask (HoughLines input) correspond to the extracted line.

  2. parameters:

    image - edge image (should be clear?) lines - lines given by angle and position, no length or sth. they are interpreted infinitely long rho - the accumulator resolution. The bigger, the more robust in case of slightly distorted lines it should be, but the less accurate in the extracted lines' position/angle threshold - the bigger the less false positives, but you might miss some lines theta - angle resolution: the smaller, the more different lines (depending on the orientation) can be detected. If your line's orientation does not fit in the angle steps, the line might not be detected. For example if you CV_PI/180 will detect in resolution, if your line has a 0.5° (e.g. 33.5°) orientation, it might be missed.

I'm not so extremely sure about all the parameters, maybe you'll have to look at the literature about hough line detection, or someone else can add some hints here.

If you instead use cv::HoughLinesP, line segments with start- and end-point will be detected, which is easier to interpret and you can compute the line length from cv::norm(cv::Point(lines[i][0], lines[i][1]) - cv::Point(lines[i][2], lines[i][3]))