7

I have a problem detecting speed traffic signs with opencv 2.4 for Android. I do the following:

"capture frame -> convert it to HSV -> extract red areas -> detect signs with ellipse detection"

So far ellipse detection works perfect as long as picture is good quality. But as you see in pictures bellow, that red extraction does not work OK, because of poor quality of picture frames, by my opinion.

Converting original image to HSV:

Imgproc.cvtColor(this.source, this.source, Imgproc.COLOR_RGB2HSV, 3);

Extracting red colors:

Core.inRange(this.source, new Scalar(this.h,this.s,this.v), new Scalar(230,180,180), this.source);

So my question is is there another way of detecting traffic sign like this or extracting red areas out of it, which by the way can be very faint like in last picture ?

This is the original image:

enter image description here

This is converted to HSV, as you can see red areas look the same color as nearby trees. Thats how I'm suppose to know it's red but I can't.

Converted to HSV:

enter image description here

This is with red colors extracted. If colors would be correct I should get almost perfect circle/ellipse around sign, but it is incomplet due to false colors.

Result after extraction:

enter image description here

Ellipse method:

private void findEllipses(Mat input){
Mat thresholdOutput = new Mat();
int thresh = 150;

List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
MatOfInt4 hierarchy = new MatOfInt4();

Imgproc.threshold(source, thresholdOutput, thresh, 255, Imgproc.THRESH_BINARY);
//Imgproc.Canny(source, thresholdOutput, 50, 180);
Imgproc.findContours(source, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
RotatedRect minEllipse[] = new RotatedRect[contours.size()];

for(int i=0; i<contours.size();i++){
    MatOfPoint2f temp=new MatOfPoint2f(contours.get(i).toArray());

    if(temp.size().height > minEllipseSize && temp.size().height < maxEllipseSize){
        double a = Imgproc.fitEllipse(temp).size.height;
        double b = Imgproc.fitEllipse(temp).size.width;
        if(Math.abs(a - b) < 10)
            minEllipse[i] = Imgproc.fitEllipse(temp);
    }
}
detectedObjects.clear();
for( int i = 0; i< contours.size(); i++ ){
    Scalar color = new Scalar(180, 255, 180);
    if(minEllipse[i] != null){
        detectedObjects.add(new DetectedObject(minEllipse[i].center));
        DetectedObject detectedObj = new DetectedObject(minEllipse[i].center);
        Core.ellipse(source, minEllipse[i], color, 2, 8);
    }
}

}

Problematic sign: enter image description here

skyhawk
  • 81
  • 1
  • 5
  • Can you upload the original (RGB) image, too? – Micka Sep 26 '15 at 13:30
  • http://picpaste.com/pics/image_5687132_1-85AZtEAd.1443302432.jpg – skyhawk Sep 26 '15 at 21:20
  • soryy I could only post 2 links earlier – skyhawk Sep 26 '15 at 21:20
  • there is original picture – skyhawk Sep 26 '15 at 21:21
  • thx. btw, rendering HSV image as BGR image might not be an intuitive hint. Seeing green in tje hsv image just means that the S channel is dominant. since H channel has smaller range, it might mean nothing. instead please try to threshold the H channel and try different ranges. – Micka Sep 27 '15 at 09:00
  • in this case its rgb as I get RGB from frame. S and V channels are always the same here. I only change H channel "window" with Core.inRange method. – skyhawk Sep 27 '15 at 09:32
  • I was referencing to your " This is converted to HSV, as you can see red areas look the same color as nearby trees " didnt have the time to test for actual values – Micka Sep 27 '15 at 10:34
  • I'm not pushing you to use one method or the other. Just pointing out other methods to answer: _is there another way of detecting traffic sign like this or extracting red areas out of it?_ This last image it's going to be difficult with any method, since there's basically no edge and color is not much different from the background. – Miki Sep 27 '15 at 18:00
  • yea I know it's gonna be difficult, specially because it has to work on low-med priced devices. – skyhawk Sep 27 '15 at 20:49
  • if you capture sample images again, please use some lossless comporession like `png`! Especially for bad quality image (parts) like the second one, compression artifacts do matter! – Micka Sep 28 '15 at 09:26
  • Images are not captured. I just do it every frame(onCameraFrame) with JavaCameraView. NativeView gives about 1 FPS worse result. Program still runs only on one core, with 4 cores FPS is around 13-15 FPS but even with 1 core = 3 - 4 FPS I still get enough of material to process. Lowering resolution would probably be good idea too if I want to use FeatureDetector to compare numbers. – skyhawk Sep 28 '15 at 10:49
  • can you plz provide me the link of that project on github or anywhere else so that i can study it i need it thanks in advacce – Muhammad Younas Apr 16 '16 at 06:11

4 Answers4

9

You can find a review of traffic signs detection methods here and here.

You'll see that there are 2 ways you can achieve this:

  1. Color-based (like what you're doing now)
  2. Shape-based

In my experience, I found that shape-based methods works pretty good, because the color may change a lot under different lighting conditions, camera quality, etc.

Since you need to detect speed traffic signs, which I assume are always circular, you can use an ellipse detector to find all circular objects in your image, and then apply some validation to determine if it's a traffic sign or not.

Why ellipse detection?

Well, since you're looking for perspective distorted circles, you are in fact looking for ellipses. Real-time ellipse detection is an interesting (although limited) research topic. I'll point you out to 2 papers with C++ source code available (which you can use in you app through native JNI calls):

  1. L. Libuda, I. Grothues, K.-F. Kraiss, Ellipse detection in digital image data using geometric features, in: J. Braz, A. Ranchordas, H. Arajo, J. Jorge (Eds.), Advances in Computer Graphics and Computer Vision, volume 4 of Communications in Computer and Information Science, Springer Berlin Heidelberg, 2007, pp. 229-239. link, code

  2. M. Fornaciari, A. Prati, R. Cucchiara, "A fast and effective ellipse detector for embedded vision applications", Pattern Recognition, 2014 link, code


UPDATE

I tried the method 2) without any preprocessing. You can see that at least the sign with the red border is detected very good:

enter image description here

Miki
  • 40,887
  • 13
  • 123
  • 202
  • I understand what u mean, but I already have ellipse detection, which doesn't work by itself, becuse there is simply too much other stuff around. It usuually detects anything but traffic sign. That's why i want to isolate "red" traffic signs and then detect them with ellipse. – skyhawk Sep 26 '15 at 21:15
  • Same problem that causes color not to look different enough is causing ellipse detection not effective. – skyhawk Sep 26 '15 at 21:16
  • And besides I will also need color-based detection because round traffic signs are just a begining... – skyhawk Sep 26 '15 at 21:17
  • @skyhawk just out of curiosity, which ellipse detection method you used? The ones I mentioned work both pretty well. Also, as a personal opinion, I don't think that color based methods will work in practice. – Miki Sep 26 '15 at 22:39
  • Could u tell me, how these functions in cpp are used in JNI ? – skyhawk Oct 06 '15 at 16:26
  • @skyhawk too broad for a comment. Google for that, it's well documented – Miki Oct 06 '15 at 16:52
4

Referencing to your text:

This is converted to HSV, as you can see red areas look the same color as nearby trees. Thats how I'm suppose to know it's red but I can't.

I want to show you my result of basically what you did (simple operations should be easily transferable to android openCV):

    // convert to HSV
    cv::Mat hsv;
    cv::cvtColor(input,hsv,CV_BGR2HSV);

    std::vector<cv::Mat> channels;
    cv::split(hsv,channels);

    // opencv = hue values are divided by 2 to fit 8 bit range
    float red1 = 25/2.0f;
    // red has one part at the beginning and one part at the end of the range (I assume 0° to 25° and 335° to 360°)
    float red2 = (360-25)/2.0f;

    // compute both thresholds
    cv::Mat thres1 = channels[0] < red1;
    cv::Mat thres2 = channels[0] > red2;

    // choose some minimum saturation
    cv::Mat saturationThres = channels[1] > 50;

    // combine the results
    cv::Mat redMask = (thres1 | thres2) & saturationThres;

    // display result
    cv::imshow("red", redMask);

These are my results:

enter image description here

enter image description here

From your result, please mind that findContours alters the input image, so maybe you extracted the ellipse but just don't see it in the image anymore, if you saved the image AFTER findContours.

Micka
  • 19,585
  • 4
  • 56
  • 74
  • I will try it like you did, but convert it to Java. But just to let you know I tried some time ago same method in OpenCV and in OpenCV for Android and the one for android gave me a lot worse result. – skyhawk Sep 28 '15 at 10:40
  • what's the range of HUE values in android openCV? Is it `0..180` or `0..360`? – Micka Sep 28 '15 at 10:47
  • HUE values go from 0 - 179 others go to 255 – skyhawk Sep 28 '15 at 11:30
  • no idea where there could be any differences between c++ and android then – Micka Sep 28 '15 at 11:32
  • There are some, I was warned about it, though I haven't really found them. Anyway when I get home from work I'll try again. – skyhawk Sep 28 '15 at 11:34
  • I'm not sure how to convert these lines to Java: // compute both thresholds cv::Mat thres1 = channels[0] < red1; cv::Mat thres2 = channels[0] > red2; // choose some minimum saturation cv::Mat saturationThres = channels[1] > 50; // combine the results cv::Mat redMask = (thres1 | thres2) & saturationThres; – skyhawk Sep 28 '15 at 19:30
1
private void findEllipses(Mat input){
    Mat thresholdOutput = new Mat();
    int thresh = 150;

    List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
    MatOfInt4 hierarchy = new MatOfInt4();

    Imgproc.threshold(source, thresholdOutput, thresh, 255, Imgproc.THRESH_BINARY);
    //Imgproc.Canny(source, thresholdOutput, 50, 180);
    Imgproc.findContours(source, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
//      source = thresholdOutput;
    RotatedRect minEllipse[] = new RotatedRect[contours.size()];

    for(int i=0; i<contours.size();i++){
        MatOfPoint2f temp=new MatOfPoint2f(contours.get(i).toArray());

        if(temp.size().height > minEllipseSize && temp.size().height < maxEllipseSize){
            double a = Imgproc.fitEllipse(temp).size.height;
            double b = Imgproc.fitEllipse(temp).size.width;
            if(Math.abs(a - b) < 10)
                minEllipse[i] = Imgproc.fitEllipse(temp);
        }
    }

    detectedObjects.clear();
    for( int i = 0; i< contours.size(); i++ ){
        Scalar color = new Scalar(180, 255, 180);
        if(minEllipse[i] != null){
            detectedObjects.add(new DetectedObject(minEllipse[i].center));
            DetectedObject detectedObj = new DetectedObject(minEllipse[i].center);
            Core.ellipse(source, minEllipse[i], color, 2, 8);
        }
    }
}
skyhawk
  • 81
  • 1
  • 5
  • This is in fact a _very bad_ method to detect ellipses. No surprise that this won't work in real world images. Try one of the method I mentioned, they'll work much better. Also, delete this answer and edit the code into the question. – Miki Sep 27 '15 at 10:20
1

have you tried using opencv ORB? it works really well. I created a haar cascade for a traffic sign (roundabout in my case) and used opencv ORB to match features and remove any false positives. For image recognition used Google's tensorflow and results were spectacular.

Adarsh
  • 3,273
  • 3
  • 20
  • 44