爆肝手码!基于OpenCV的车牌识别(Sobel、颜色定位),绝对干货

车牌识别大体上需要经历过Sobel定位、颜色定位、SVM对定位来的候选车牌进行评测,给出评分,最后通过提取HOG特征按照训练模型进入ANN识别。

这一章节介绍 定位相关的逻辑代码,其中定位用到 Sobel定位(边缘检测定位), 颜色定位:对应代码里的CarSobelPlateLocation,CarColorPlateLocation;两者定位后得到一些候选的图片,把这些图片送去SVM进行评测,SVM基于HOG提取边缘信息特征,HOG类同之前处理纹理特征的LBP,项目代码在Clion上开发的。源码地址前往车牌定位(
https://github.com/yinxiucheng/OpencvCarRecgnize)。

Sobel定位

CarSobelPlateLocation,通过以下的一些步骤进行降噪:

  • 高斯模糊
  • 灰度化
  • 边缘化
  • 二值化
  • 闭操作

高斯模糊

//预处理 :去噪 让车牌区域更加突出
    Mat blur;
    //1、高斯模糊(平滑) (1、为了后续操作 2、降噪 )
    GaussianBlur(src, blur, Size(5, 5), 0);
    //imshow("高斯模糊",blur);

灰度化

 Mat gray;
    //2、灰度化 去掉颜色 因为它对于我们这里没用  降噪
    cvtColor(blur, gray, COLOR_BGR2GRAY);
    imshow("灰度", gray);

边缘化

 Mat sobel_16;
    //3、 边缘检测 让车牌更加突出  在调用时需要以16位来保存数据 在后续操作 以及显示的时候需要转回8位
    Sobel(gray, sobel_16, CV_16S, 1, 0);
    //转为8位
    Mat sobel;
    convertScaleAbs(sobel_16, sobel);
    imshow("Sobel", sobel);

二值化

//4\. 二值化 黑白
    Mat shold;
    //大律法   最大类间算法
    threshold(sobel, shold, 0, 255, THRESH_OTSU + THRESH_BINARY);
    imshow("二值", shold);

闭操作

//5、闭操作
    // 将相邻的白色区域扩大 连接成一个整体
    Mat close;
    Mat element = getStructuringElement(MORPH_RECT, Size(17, 3));
    morphologyEx(shold, close, MORPH_CLOSE, element);
    imshow("闭操作", close);

以上的操作是在处理降噪,第六步初步赛选。

第六步:最大面积、最小面积.宽高逼。

//6、查找轮廓
    //获得初步筛选车牌轮廓================================================================
    //轮廓检测
    vector< vector<Point>> contours;
    //查找轮廓 提取最外层的轮廓  将结果变成点序列放入 集合
    findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);

    //遍历
    vector<RotatedRect> vec_sobel_roi;
    for(vector<Point> point:contours){
        RotatedRect rotatedRect= minAreaRect(point);
        //rectangle(src, rotatedRect.boundingRect(), Scalar(255, 0, 255));
        //进行初步的筛选 把完全不符合的轮廓给排除掉 ( 比如:1x1,5x1000 )
        if (verifySizes(rotatedRect)) {
            vec_sobel_roi.push_back(rotatedRect);
        }
    }

初步赛选:宽高比 float aspec,把不符合的删除掉(1 * 1的, 5* 1000的等候选矩形)

int CarPlateLocation::verifySizes(RotatedRect rotated_rect) {
    //容错率
    float error = 0.75f;

    //训练时候模型的宽高 136 * 32
    //获得宽高比
    float aspect = float(136) / float(32);

    //最小 最大面积 不符合的丢弃
    //给个大概就行 随时调整
    //尽量给大一些没关系, 这还是初步筛选。
    int min = 20 * aspect * 20;
    int max = 180 * aspect * 180;

    //比例浮动 error认为也满足
    //最小宽、高比
    float rmin = aspect - aspect * error;
    //最大的宽高比
    float rmax = aspect + aspect * error;
    //矩形的面积
    float area = rotated_rect.size.height * rotated_rect.size.width;
    //矩形的比例
    float r = (float) rotated_rect.size.width / (float) rotated_rect.size.height;
    if ((area < min || area > max) || (r < rmin || r > rmax))
        return 0;
    return 1;
}

把斜的图片转正:仿射变换

//1、矫正前 2、矫正后 3、矩形的大小 4、矩形中心点坐标  5、角度
void CarPlateLocation::rotation(Mat src, Mat &dst, Size rect_size,
                                Point2f center, double angle) {

    //获得旋转矩阵
    Mat rot_mat = getRotationMatrix2D(center, angle, 1);

    //运用仿射变换
    Mat mat_rotated;
    //矫正后 大小会不一样,但是对角线肯定能容纳
    int max = sqrt(pow(src.rows, 2) + pow(src.cols, 2));
    //仿射变换
    warpAffine(src, mat_rotated, rot_mat, Size(max, max),
               CV_INTER_CUBIC);
    imshow("旋转前", src);
    imshow("旋转", mat_rotated);
    //截取 尽量把车牌多余的区域截取掉
    getRectSubPix(mat_rotated, Size(rect_size.width, rect_size.height), center, dst);
    imshow("截取", dst);
    mat_rotated.release();
    rot_mat.release();

颜色定位

HSV颜色模型

色调(H), 饱和度(S), 明度(V);

BGR 转成 HSV

cvtColor(src,hsv,COLOR_BGR2HSV);

色调H

用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,品红为300°;

饱和度S

饱和度S表示颜色接近光谱色的程度。一种颜色,可以看成是某种光谱色与白色混合的结果。其中光谱色所占的比例愈大,颜色接近光谱色的程度就愈高,颜色的饱和度也就愈高。饱和度高,颜色则深而艳。光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,值越大,颜色越饱和。

明度V

明度表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为0%(黑)到100%(白)

在OpenCV中hsv 数据为8UC则取值分别为 0-180 0-255 0-255 ,即蓝色应该是120

爆肝手码!基于OpenCV的车牌识别(Sobel、颜色定位),绝对干货

按照上面的表格找到蓝色区域 (100 ~ 124), 然后将HSV中的H、S转为 0, V变为255。其它区域的HSV赋值为0。

//3通道
    int chanles = hsv.channels();
    //高
    int h = hsv.rows;
    //宽数据长度
    int w = hsv.cols * 3;

    //判断数据是否为一行存储的
    //内存足够的话 mat的数据是一块连续的内存进行存储
    if (hsv.isContinuous()) {
        w *= h;
        h = 1;
    }

    for (size_t i = 0; i < h; ++i) {
        //第i 行的数据 hsv的数据 uchar = java byte
        uchar *p = hsv.ptr<uchar>(i);

        for (size_t j = 0; j < w; j += 3) {
            int h = int(p[j]);
            int s = int(p[j + 1]);
            int v = int(p[j + 2]);

            bool blue = false;
            //蓝色
            if (h >= 100 && h <= 124 && s >= 43 && s <= 255 && v >= 46 && v <= 255) {
                blue = true;
            }

            if (blue){
                p[j] = 0;
                p[j + 1]=0;
                p[j + 2]=255;
            }else {
                //hsv 模型 h:0 红色 亮度和饱和度都是0 ,也就变成了黑色
                p[j] = 0;
                p[j + 1] = 0;
                p[j + 2] = 0;
            }
        }
    }

得到下面的图:

爆肝手码!基于OpenCV的车牌识别(Sobel、颜色定位),绝对干货

接下来抽取亮度:

//把亮度数据抽出来
    //把h、s、v分离出来
    vector<Mat> hsv_split;
    split(hsv, hsv_split);

然后跟sobel一样通过二值化、大律法等操作

    // 整个图片+经过初步赛选的车牌 + 得到的候选车牌
    tortuosity(src, vec_sobel_roi, dst);

    for (Mat s: dst) {
        imshow("候选", s);
        waitKey();
    }

筛选出来一个集合:

爆肝手码!基于OpenCV的车牌识别(Sobel、颜色定位),绝对干货

把两个结合结合起来,然后通过SVM进行评测, 因为不像人脸检测是没有现成的模型。

vector< Mat > sobel_plates;
//sobel定位
plateLocation->location(src, sobel_plates);

//颜色定位
vector< Mat > color_plates;
plateColorLocation->location(src, color_plates);

vector<Mat> plates;
//把sobel_plates的内容 全部加入plates向量
plates.insert(plates.end(),sobel_plates.begin(), sobel_plates.end());
plates.insert(plates.end(), color_plates.begin(), color_plates.end());

SVM

简单来说,SVM就是用于区分不同的类型(车牌、非车牌)。SVM的训练数据既有特征又有标签,通过训练,让机器可以自己找到特征和标签之间的联系,在面对只有特征没有标签的数据时,可以判断出标签。属于机器学习中的监督学习。线性可分、线性不可分,不可分的时候用核函数来区分:

核函数: 用于将不同类型进行提维

人脸检测用的LBP提取特征,这里采取HOG来提取特征。

SVM load模型, 模型是同样是xml文件

svm = SVM::load(svm_model);
CarPlateRecgnize p("/Users/xiuchengyin/Documents/Tina-NDK/OpencvCarRecgnize/resource/HOG_SVM_DATA2.xml");

HOG特征

局部归一化的梯度方向直方图,是一种对图像局部重叠区域的密集型描述符, 它通过计算局部区域的梯度方向直方图来构成特征。

参数1(检测窗口)的宽- 参数2(块大小)的宽 结果与参数3(块滑动增量)的余数要为0 高也一样

参数4是胞元大小,参数5是梯度方向

HOGDescriptor hog(Size(128, 64), Size(16, 16), Size(8, 8), Size(8, 8), 3);

初始化HOG变量

 //参数1的宽-参数2的宽 结果与参数3的余数为0  高也一样
    svmHog = new HOGDescriptor(Size(128,64),Size(16,16),Size(8,8),Size(8,8),3);
爆肝手码!基于OpenCV的车牌识别(Sobel、颜色定位),绝对干货
爆肝手码!基于OpenCV的车牌识别(Sobel、颜色定位),绝对干货

检测窗口被分为:((128-16)/8+1)*((64-16)/8+1)=105个块(Block);

一个Block有4个胞元(Cell);

一个Cell的Hog描述子向量的长度是9;

统计梯度直方图特征,就是将梯度方向(0-360)划分为x个区间,将图像化为16x16的若干个窗口,每个窗口又划分为x个block,每个block再化为4个cell(8x8)。对每一个cell,算出每一像素点的梯度方向,按梯度方向增加对应bin的值,最终综合N个cell的梯度直方图组成特征。

简单来说,车牌的边缘与内部文字组成的一组信息(在边缘和角点的梯度值是很大的,边缘和角点包含了很多物体的形状信息),HOG就是抽取这些信息组成一个直方图。

HOG : 梯度方向弱化光照的影响,适合捕获轮廓。

LBP : 中心像素的LBP值反映了该像素周围区域的纹理信息。

SVM 依据HOG提取的特征将所给的候选图片进行评分,选取最优的:

string CarPlateRecgnize::plateRecgnize(Mat src) {
    vector< Mat > sobel_plates;
    //sobel定位
    sobelPlateLocation->location(src, sobel_plates);
    //颜色定位
    vector< Mat > color_plates;
    colorPlateLocation->location(src, color_plates);
    vector< Mat > plates;
    //把sobel_plates的内容 全部加入plates向量
    plates.insert(plates.end(),sobel_plates.begin(), sobel_plates.end());
    plates.insert(plates.end(), color_plates.begin(), color_plates.end());

    int index = -1;
    float minScore = FLT_MAX; //float的最大值
    //使用 svm 进行 评测
    for (int i = 0;i< plates.size();++i)
    {
        Mat plate = plates[i];
        //先灰度化,再二值化,灰度化只剩下一个通道
        Mat gray;
        cvtColor(plate, gray,COLOR_BGR2GRAY);
        //二值化 必须是以单通道进行
        Mat shold;
        threshold(gray, shold, 0, 255, THRESH_OTSU + THRESH_BINARY);
        //提取特征
        Mat features;
        getHogFeatures(svmHog, shold, features);
        //features 进行转化,把数据保存成一行
        Mat samples = features.reshape(1,1);
        //转化数据存储格式
        samples.convertTo(samples, CV_32FC1 );

        //原始模式
        // svm: 直接告诉你这个数据是属于什么类型.
        // RAW_OUTPUT:让svm 给出一个评分
//        char name[100];
//        sprintf(name, "候选车牌%d", i);
//        imshow(name, plate);

        float score = svm->predict(samples, noArray(), StatModel::Flags::RAW_OUTPUT);
        printf("评分:%f\n",score);
        if (score < minScore) {
            minScore = score;
            index = i;
        }
        gray.release();
        shold.release();
        features.release();
        samples.release();
    }

    Mat dst;
    if (index >= 0) {
        dst = plates[index].clone();
    }
//    imshow("车牌", dst);
//    waitKey();
//    释放
    for (Mat p : plates) {
        p.release();
    }
    return string("123");
}

svm评分如下:

/Users/xiuchengyin/Documents/Tina-NDK/OpencvCarRecgnize/cmake-build-debug/OpencvCarRecgnize
评分:-1.224322
评分:1.255759
评分:1.831937
评分:-0.070820
评分:1.525869
评分:1.117042

测试最终取出来的就是我们的车牌选图了。

参考:github.com/liuruoze/Ea…

www.cnblogs.com/subconsciou

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容