机器学习中,从一张图片中提取出感兴趣的区域(以交通标志为例)

在进行机器学习中,不管是用Haar特征,或者是Hog特征,最后进行的不论是adaboost分类,还是svm分类,都只能是检测出待检测的正样本相近的图片。

比如,为了提高准确性,我们在进行正样本的训练时,必须把正样本置于整个正样本的大部分中,以交通标志中的禁止驶入为例,如下图的就是正确的正样本之一:
《机器学习中,从一张图片中提取出感兴趣的区域(以交通标志为例)》

我们可以看到,禁止驶入交通标志占了图片面积的大部分。这在训练时,可以尽可能的减少误差。如下图就是个错误的例子:
《机器学习中,从一张图片中提取出感兴趣的区域(以交通标志为例)》

在这幅图片中,我们只能在右面的一小块区域发现禁止驶入的交通标志。这个是错误的正样本。因为有太多的干扰项。

那么,我们进行实际检测时,不可能只是对如图1的图片进行检测,实际情况中,往往是如图2所示,那么,我们直接用检测方法,毫无疑问的,得到的一定是错误结果。

于是,就得到了,如何从一张图片中提取到我们感兴趣的区域。如图2中,在进行实际检测时,必然是先从这张图片中检测出右上角的禁止驶入的交通标志的区域,然后,在对其进行匹配处理,看是哪个标志,或者是不是交通标志。

这里,我们先以交通标志为例,这篇文章的主要算法思想,也是针对例如交通标志这种的,颜色划分比较明显的进行叙述。

好了,正如上段所说的,我们主要的算法思想是,针对诸如交通这类,因为我们都知道,交通标志主要分为四种颜色,即RGBY,在检测时,我们可以想得到,如果把待检测图片,分成RGBY四种颜色,那么,就能得到交通标志的区域外轮廓。再用边缘检测,可以选择用canny边缘检测,并且求出每个不连通的边缘的最小外界矩形,返回这个矩形的所在位置,就能得到我们感兴趣的区域。

当然,为了方便起见,我们可以直接用opencv中的cvFindCounter()函数,这个函数的第三个矩形,可以返回一个矩形轮廓。我们分别对返回的矩形轮廓进行检测(很明显不会只返回一个轮廓,当然,下面我们会介绍几种尽可能排除掉不希望有的矩形的返回值),最后得到想要的结果。

下面是算法的具体介绍。

首先是说明,我们用的是Hog特征检测和Svm二分类。用的编译环境是vs2010,opencv2.4.9。

然后是,对待检测图片进行分色。我们知道的,原图片一般都是RGB三色通道(CMYK是打印颜色,我们这里先不做考虑),转到RGBY,opencv上没有转的函数,我们可以选用两种方法。一种是直接转,即用RGB直接分离通道,然后得到RGB的通道,Y通道,利用黄颜色是蓝色的补色,利用补色直接转。利用Y=255-B,可以得到最后的黄色的图片的结果。
注:这里的是粗转换,如果想进行准确的RGB转Y或者转CMYK的,要注意的是最好用PS转,因为我们没有进行配置文件的添加和处理,最后会由于CMYK颜色空间比RGB颜色空间小,而得不到正确的图片,具体请参考文章最后的链接,我们可以看出,不能得到和原图一样的效果,是因为颜色被压缩了。

int rgb2cmyk( Mat &image,Mat &cmyk){  
    if(!image.data){  
        cout<<"Miss Data"<<endl;  
        return -1;  
    }  
    int nl = image.rows;    //行数 
    int nc = image.cols;    //列数 
    if(image.isContinuous()){   //没有额外的填补像素 
        nc = nc*nl;  
        nl = 1;                 //It is now a 1D array 
    }  
    //对于连续图像,本循环只执行1次 
    for(int i=0;i<nl;i++){  
        uchar *data = image.ptr<uchar>(i);  
        uchar *dataCMYK = cmyk.ptr<uchar>(i);  
        for(int j = 0;j < nc;j++){  
            uchar b = data[3*j];  
            uchar g = data[3*j+1];  
            uchar r = data[3*j+2];  
            uchar c = 255 - r;  
            uchar m = 255 - g;  
            uchar y = 255 - b;  
            uchar k = min(min(c,m),y);  
            dataCMYK[4*j] = c - k;  
            dataCMYK[4*j+1] = m  - k;  
            dataCMYK[4*j+2] = y  - k;  
            dataCMYK[4*j+3] = k;  
        }  
    }  
    return 0;  
} 

第二种,我是自己选用的方法。是把RGB转到HSI,然后利用H是表示色度的信息,在把HSI转到RGBY。

具体算法是:把这幅图片,先转到HSI空间,然后,利用HSI空间中的H颜色信息,可以把RGB分为RGBY,其中,把H扩大倍数到360,这样,当H在0~10,或340~360时,就是红色,H在10~60,就为黄色,H在180~240时,就是蓝色,H在110~200,就为绿色,当然对纯度和明度也有一定要求,一般纯度大于0.2,鲜艳色明度大于0.3,暗淡的颜色明度大于0.1。这里,我们还可以进行手动的调节S和I,以让它符合我们所要检测的环境(手动调节是为了应付任务,不建议大家使用)。

下面是代码片段:

void RGB2HSI(int R, int G, int B, double& H, double& S, double& I)
{
    if (R<0 || R>255 || G<0 || G>255 || B<0 || B>255)
    {
        AfxMessageBox("Value out of range!");
        return;
    }
    double min, mid, max;
    if (R>G && R>B)
    {
        max = R;
        mid = MAX(G, B);
        min = MIN(G, B);
    }
    else 
    {
        if (G>B) 
        {
            max = G;
            mid = MAX(R, B);
            min = MIN(R, B);
        }
        else 
        {
            max = B;
            mid = MAX(R, G);
            min = MIN(R, G);
        }
    }
    I = max / 255;
    S = 0;
    H = 0;
    if (I==0 || max==min)
    {
        // this is a black image or grayscale image
        S = 0;
        H = 0;
    }
    else 
    {
        S = (I - min/255) / I;
        // domains are 60 degrees of
        // red, yellow, green, cyan, blue or magenta
        double domainBase = 0.0;
        double oneSixth = 1.0/6.0;
        double domainOffset = ( (mid-min) / (max-min) ) / 6.0;

        if (R==max) 
        {
            if (mid==G)
            { // green is ascending
                domainBase = 0; // red domain
            }
            else 
            { // blue is descending
                domainBase = 5.0/6.0; // magenta domain
                domainOffset = oneSixth - domainOffset;
            }
        }
        else {
            if (G==max) 
            {
                if (mid==B)
                { // blue is ascending
                    domainBase = 2.0/6.0; // green domain
                }
                else
                { // red is ascending
                    domainBase = 1.0/6.0; // yellow domain
                    domainOffset = oneSixth - domainOffset;
                }
            }
            else 
            {
                if (mid==R) 
                { // red is ascending
                    domainBase = 4.0/6.0; // blue domain
                }
                else 
                { // green is descending
                    domainBase = 3.0/6.0; // cyan domain
                    domainOffset = oneSixth - domainOffset;
                }
            }
        }
        H = domainBase + domainOffset;
    }           
}
void Image2RBYGImage(IplImage* image, IplImage* imageR, IplImage* imageB, IplImage* imageY, IplImage* imageG)
{
    int i,j;
    double r,g,b,s,h,v,H;
    for (i=0;i<height;i++)
        for(j=0;j<width;j++)
        { 
            b=(double)data1[i*step+j*channels+0];
            g=(double)data1[i*step+j*channels+1];
            r=(double)data1[i*step+j*channels+2];
            RGB2HSI((int)r,(int)g,(int)b,h,s,v);
            H=h*360;
            if (((H>0&&H<10) || (H>340&&H<360))&&s>0.2&&v>0.1)//R (H>0&&H<10) || (H>330&&H<360)BLUEH>200&&H<270&&s>0.3,YELLOWH>20&&H<100&&s>0.2
            {
                data_R[i*step1+j] = 255;
            }
            else if (H>10&&H<60/*&&s>0.3&&v>0.3*/)//Y (H>0&&H<10) || (H>330&&H<360)BLUEH>200&&H<270&&s>0.3,YELLOWH>20&&H<100&&s>0.2
            {
                data_Y[i*step1+j] = 255;
            }
            else if (H>180&&H<240&&s>0.2 && v>0.3)//B(H>0&&H<10) || (H>330&&H<360)BLUEH>200&&H<270&&s>0.3,YELLOWH>20&&H<100&&s>0.2
            {
                data_B[i*step1+j] = 255;
            }
            else if (H>110&&H<200&&s>0.2&&v>0.3)
            {
                data_G[i*step1+j] = 255;
            }
        }
        cvDilate(imageR, imageR, 0, 3); //膨胀 形态学 第3个参数若为0则为长方形结构元素 第四个参数膨胀次数
        cvDilate(imageB, imageB, 0, 3);
        cvDilate(imageY, imageY, 0, 3);
        cvDilate(imageG, imageG, 0, 5);
        cvReleaseImage(&Imagerect);
}

注:图片的声明和清空和预处理部分已经删除,请自行添加相关代码。

这样,我们就对图片进行了分色处理。之后,是检测出轮廓。

CvMemStorage* storage = cvCreateMemStorage(0);
    CvSeq* contour = 0;
    int height=image->height;//得到图片的size
    int width=image->width;
    int classID = -1;
    //寻找轮廓,返回感兴趣区域轮廓的指针,指针可以指出一个矩形
    cvFindContours(imagegray, storage, &contour, sizeof(CvContour), CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE );

    for( ; contour != 0; contour = contour->h_next )
    {
        CvRect r = ((CvContour*)contour)->rect;

        if (r.height>15&&r.width>15)
        {
            cvRectangle(image,cvPoint(r.x,r.y),cvPoint(r.x+r.width,r.y+r.height),CV_RGB(0,255,0),1);
            ......
        }
    }

注:上图主要看的是cvFindCounter()函数的用法,也可以自行百度此函数用法。

然后,就是从这些返回的矩形框中,进行svm分类的检测。最后得到效果。

当然,我们从cvFindCounter()的一个大体介绍中,也可以看出,返回的矩形框太多了。我们可以进行一个初处理,把所有长和宽小于15个像素的矩形框全部去除掉。这样,会减少一部分矩形。如果对于检测信息还有要求的话,我们还可以加上位置信息,例如,交通标志的通常放置位置是整个图片的右上方,或者水平面以上,即拍摄图片的一半或者1/3位置以上(以左下角为原点时)。还有,可以加上,只取返回的矩形框的长宽大小排名的前三进行检测,可以大大减少检测量。

对于目标物体和背景颜色相近的情况,我们可以先选出这部分,然后进行全局阈值处理(例如OTSU全局阈值处理)。
下面是otsu算法:

int BinaryOtsu(const long *Gray){
//设置Gray[256],即为原图像的所有灰度值
    int k;
    long n;
    long n1,n2;
    double sum,csum;
    double m1,m2;
    double c;
    double c1;
    int mid=0;
    n = 0;
    sum = csum = 0;
    for (k = 0; k <= 255; k++) {
        sum += (double) k * (double) Gray[k];
        n   += Gray[k];
    }
    n1 = 0;
    c = -1.0;
    for (k = 0; k < 255; k++) {
        n1 += Gray[k];
        if (!n1) { continue; }
        n2 = n - n1;
        if (n2 == 0) { break; }
        csum += (double) k *Gray[k];
        m1 = csum / n1;
        m2 = (sum - csum) / n2;
        c1 = (double) n1 *(double) n2 * (m1 - m2) * (m1 - m2);
        if (c1 > c) {
            c = c1;
            mid = k;
        }
    }
    return mid;
}

然后,就得到最后的结果,当然,对于从一张图片中提取出感兴趣的区域的方法有很多,这里只是介绍一种方法和大概思路而已。

如下图是最后处理的结果。

《机器学习中,从一张图片中提取出感兴趣的区域(以交通标志为例)》

参考链接:http://blog.csdn.net/Solomon1558/article/details/44108869
上述链接为RGB转CMYK,看效果,确实颜色被压缩了。

    原文作者:Serious_Tanx
    原文地址: https://blog.csdn.net/Serious_Tanx/article/details/52904571
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞