OpenCV速度交通标志检测

7

我有一个使用OpenCV 2.4在Android中检测交通标志速度的问题。 我的处理流程是:

"捕获帧 -> 转换为HSV -> 提取红色区域 -> 用椭圆检测检测标志"

到目前为止,只要图片质量好,椭圆检测就能完美地工作。 但是,如您在下面的图片中所看到的那样,由于图片帧的质量不佳,所以红色提取效果不佳,这是我个人的看法。

将原始图像转换为HSV:

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

提取红色颜色:
Core.inRange(this.source, new Scalar(this.h,this.s,this.v), new Scalar(230,180,180), this.source);

我的问题是是否有另一种检测这样的交通标志或提取其中的红色区域的方法,即使像最后一张图片中那样非常微弱?
这是原始图像:

enter image description here

我将其转换为HSV,你可以看到红色区域看起来与附近的树木颜色相同。这就是我应该知道它是红色但我不能知道的原因。

转换为HSV:

enter image description here

这是提取红色后的结果。如果颜色正确,我会获得几乎完美的圆/椭圆来围绕标记,但由于颜色失真,它不完整。
提取后的结果:

enter image description here

椭圆法:

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);
    }
}

}

问题标志: 在此输入图片描述


你能同时上传原始的(RGB)图像吗? - Micka
抱歉,我之前只能发布两个链接。 - skyhawk
我在引用你的“这被转换为HSV格式,正如你所看到的,红色区域看起来与附近的树木颜色相同”,但我没有时间测试实际值。 - Micka
我并不是在推荐你使用某种方法,只是指出其他回答问题的方法:是否有另一种检测交通标志或提取其中红色区域的方法?对于最后一张图片,任何方法都会很困难,因为基本上没有边缘,并且颜色与背景差别不大。 - Miki
是的,我知道这会很困难,特别是因为它必须在低中价位的设备上运行。 - skyhawk
显示剩余7条评论
4个回答

9
你可以在这里找到与交通标志检测相关的方法评估:这里这里
你可以通过以下两种方式来实现:
  1. 基于颜色(就像你现在所做的)
  2. 基于形状
根据我的经验,我发现基于形状的方法效果比较好,因为颜色在不同的光照条件、相机质量等情况下可能会有很大变化。
由于您需要检测速度交通标志,我假设它们总是圆形的,因此您可以使用椭圆检测器在图像中找到所有圆形对象,然后应用一些验证来确定它是否为交通标志。
为什么要使用椭圆检测呢?
嗯,由于您正在寻找透视失真的圆,实际上您正在寻找椭圆。实时椭圆检测是一个有趣(尽管有限)的研究课题。我会向您介绍两篇具有C++源代码的论文,并通过本机JNI调用在您的应用程序中使用:
  1. L. Libuda, I. Grothues, K.-F. Kraiss,在数字图像数据中使用几何特征检测椭圆,收录于"Advances in Computer Graphics and Computer Vision",J. Braz, A. Ranchordas, H. Arajo, J. Jorge(编者),Communications in Computer and Information Science卷4,Springer Berlin Heidelberg出版社,2007年,pp.229-239。链接代码

  2. M. Fornaciari, A. Prati, R. Cucchiara,"嵌入式视觉应用的快速有效椭圆检测器",Pattern Recognition,2014年。链接代码


更新

我尝试了方法2)但没有进行任何预处理。你可以看到至少带有红边框的标志被检测得很好:

enter image description here


我理解你的意思,但是我已经有椭圆检测了,但它不能单独工作,因为周围有太多其他的东西。它通常会检测除交通标志以外的任何东西。这就是为什么我想要分离“红色”的交通标志,然后用椭圆来检测它们的原因。 - skyhawk
导致颜色不够不同的问题也导致了椭圆检测不够有效。 - skyhawk
此外,我还需要基于颜色的检测,因为圆形交通标志只是一个开始... - skyhawk
@skyhawk 只是出于好奇,你使用了哪种椭圆检测方法?我提到的那些方法都很有效。另外,就个人意见而言,我认为基于颜色的方法在实践中不会起作用。 - Miki
请问您,这些 C++ 函数在 JNI 中如何使用? - skyhawk
@skyhawk 评论太笼统了。你可以去谷歌搜索一下,相关文档已经被充分记录了。 - Miki

4

参考您的文本:

这被转换为HSV,您可以看到红色区域与附近的树木颜色相同。这就是我应该知道它是红色但我不知如何判断。

我想向您展示我的结果,基本上与您所做的相同(简单操作应易于转移到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);

以下是我的结果:

enter image description here

enter image description here

请注意,从您的结果来看,findContours会更改输入图像,因此如果在运行findContours之后保存了图像,则可能已提取椭圆但不再在图像中看到它。

我会像你一样尝试,但是将其转换为Java。但是只是让你知道,我之前尝试过在OpenCV和OpenCV for Android中使用相同的方法,而Android版本的结果要差得多。 - skyhawk
安卓OpenCV中HUE值的范围是多少?是0..180还是0..360 - Micka
HUE值从0到179,其他值最高可达255。 - skyhawk
不知道C++和Android之间可能存在什么差异。 - Micka
有一些问题,我被警告过,但实际上我还没有发现它们。无论如何,等我下班回家后再试一次。 - skyhawk
我不确定如何将这些行转换为Java代码:// 计算两个阈值 cv :: Mat thres1 = channels [0] <red1; cv :: Mat thres2 = channels [0]> red2;// 选择一些最小饱和度 cv :: Mat saturationThres = channels [1]> 50;// 合并结果 cv :: Mat redMask =(thres1 | thres2)&saturationThres; - skyhawk

1

你尝试过使用OpenCV ORB吗?它的效果非常好。 我为一个交通标志(在我的情况下是环形交叉口)创建了一个Haar级联,使用OpenCV ORB匹配特征并消除任何错误的正例。 对于图像识别,使用了Google的TensorFlow,结果非常出色。


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);
        }
    }
}

这实际上是一种非常糟糕的检测椭圆的方法。毫不奇怪,这在现实世界的图像中行不通。尝试我提到的其中一种方法,它们会更好地工作。此外,请删除此答案并将代码编辑到问题中。 - Miki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接