Search code examples
javaopencvgeometrydetectioncolor-detection

Detection of red circle-alike | openCV | Java


I am writing a program which need to detect red circle-alikes from this picture.

enter image description here

I have tried canny edge detection and find contours but none of them find this red "circles". I also tried to convert this to hsv and detect this by color but I couldn't determine good range for this color, maybe background color confuses it?

I put here a piece of my code with my final attempt..

Mat image = new Mat();
image = Imgcodecs.imread("image.jpg");
Mat hsvImage = new Mat();
Mat grayscaleImage = new Mat();
Mat binaryImage = new Mat();
Imgproc.blur(image, image, new Size(1, 1));  
Imgproc.cvtColor(image, hsvImage, Imgproc.COLOR_BGR2HSV);           
Imgproc.cvtColor(image, grayscaleImage, Imgproc.COLOR_BGR2GRAY);            
Imgproc.equalizeHist(grayscaleImage, grayscaleImage);
Imgproc.Canny(grayscaleImage, grayscaleImage, 50, 150, 3,false);

List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        Imgproc.findContours(grayscaleImage.clone(), contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);


        for (int id=0;id<contours.size();id++){
            MatOfPoint2f mop2f = new MatOfPoint2f();
            contours.get(id).convertTo(mop2f,CvType.CV_32F);
            RotatedRect rectangle = Imgproc.minAreaRect(mop2f);
            if (rectangle.boundingRect().width>80)
            Imgproc.drawContours(image,contours,id,new Scalar(0,255,0));

        }

Solution

  • If you want to process that marked image, you really might want to detect colors. Typically this is done in HSV color-space.

    Here is some C++ code to detect "red" color. The result isn't good enough to use findContours yet, but maybe after some dilation. Maybe you can convert the code to Java.

    If you want to detect different color, change the line redMask = thresholdHue(hsv, 0, 20, 50, 50); to mask = thresholdHue(hsv, yourWantedHueColorValue, 20, 50, 50);`

    // for example to shift a circluar hue-channel
    cv::Mat shiftChannel(cv::Mat H, int shift, int maxVal = 180)
    {
        // CV_8UC1 only!
        cv::Mat shiftedH = H.clone();
        //int shift = 25; // in openCV hue values go from 0 to 180 (so have to be doubled to get to 0 .. 360) because of byte range from 0 to 255
        for (int j = 0; j < shiftedH.rows; ++j)
        for (int i = 0; i < shiftedH.cols; ++i)
        {
            shiftedH.at<unsigned char>(j, i) = (shiftedH.at<unsigned char>(j, i) + shift) % maxVal;
        }
    
        return shiftedH;
    }
    
    cv::Mat thresholdHue(cv::Mat hsvImage, int hueVal, int range = 30, int minSat = 50, int minValue = 50)
    {
        // hsvImage must be CV_8UC3 HSV image.
        // hue val and range are in openCV's hue range (0 .. 180)
        // range shouldnt be bigger than 90, because that's max (all colors), after shifting the hue channel.
    
        // this function will
        //    1. shift the hue channel, so that even colors near the border (red color!) will be detectable with same code.
        //    2. threshold the hue channel around the value 90 +/- range
    
        cv::Mat mask; // return-value
    
        std::vector<cv::Mat> channels;
        cv::split(hsvImage, channels);
    
        int targetHueVal = 180 / 2; // we'll shift the hue-space so that the target val will always be 90 afterwards, no matter which hue value was chosen. This can be important if 
        int shift = targetHueVal - hueVal;
        if (shift < 0) shift += 180;
    
        cv::Mat shiftedHue = shiftChannel(channels[0], shift, 180);
    
        // merge the channels back to hsv image
        std::vector<cv::Mat> newChannels;
        newChannels.push_back(shiftedHue);
        newChannels.push_back(channels[1]);
        newChannels.push_back(channels[2]);
        cv::Mat shiftedHSV;
        cv::merge(newChannels, shiftedHSV);
    
        // threshold
        cv::inRange(shiftedHSV, cv::Vec3b(targetHueVal - range, minSat, minValue), cv::Vec3b(targetHueVal + range, 255, 255), mask);
    
        return mask;
    }
    
    
    int main(int argc, char* argv[])
    {
        cv::Mat input = cv::imread("C:/StackOverflow/Input/redCircleLikeContours.jpg");
    
    
        cv::Mat redMask; 
    
        cv::Mat hsv;
        cv::cvtColor(input, hsv, CV_BGR2HSV);
    
        redMask = thresholdHue(hsv, 0, 20, 50, 50);
    
        cv::imshow("red", redMask);
    
        cv::imshow("input", input);
        cv::imwrite("C:/StackOverflow/Output/redCircleLikeContoursMask.png", redMask);
    
        cv::waitKey(0);
        return 0;
    }
    

    Here's the result:

    enter image description here