基于OpenCV的车牌识别(ANN识别)

2,088 阅读4分钟

上一章节完成了车牌图片的切割,这一章节进入到ANN识别过程:

同样地流程:先去燥,灰度,二值化,得到以下图片,发现有两个螺丝孔。

//imshow("车牌", dst);
    //识别 ...... ann :神经网络
    Mat plate_gray;
    cvtColor(dst, plate_gray,COLOR_BGR2GRAY);

    //二值化
    Mat plate_shold;
    threshold(plate_gray, plate_shold,0,255, THRESH_OTSU + THRESH_BINARY);

去掉 车牌在车子上的固定点

//因为固定点所在的行 它的黑到白、白到黑的改变次数是最小的
clearFixPoint(plate_shold);
imshow("去干扰", plate_shold);


//去螺丝钉的挂牌,根据每一行的跳变 小于10的都描黑。
void CarPlateRecgnize::clearFixPoint(Mat &src) {
    //最大改变次数
    int maxChange = 10;
    //一个集合统计每一行的跳变次数
    vector<int> c;
    for (size_t i = 0; i < src.rows; i++)
    {
        //记录这一行的改变次数
        int change = 0;
        for (size_t j = 0; j < src.cols - 1; j++)
        {
            //获得像素值
            char p = src.at<char>(i, j);
            //当前的像素点与下一个像素点值是否相同
            if (p != src.at<char>(i, j + 1)) {
                change++;
            }
        }
        c.push_back(change);
    }

    for (size_t i = 0; i < c.size(); i++)
    {
        //取出每一行的改变次数
        int change = c[i];
        //如果小与max ,则可能就是干扰点所在的行
        if (change <= maxChange) {
            //把这一行都抹黑
            for (size_t j = 0; j < src.cols; j++)
            {
                src.at<char>(i, j) = 0;
            }
        }
    }
}

去掉干扰点后的图片,发现上面被切掉了一些。

字符分割

每一个都是一个轮廓,调用轮廓查找:

    vector< vector<Point>> contours;
    findContours(plate_shold, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);

    for (vector<Point> point:contours) {
        RotatedRect rotatedRect= minAreaRect(point);
        rectangle(dst, rotatedRect.boundingRect(), Scalar(255, 0, 255));

    }
    imshow("查找轮廓", dst);

矩形框太多,一些无用的框,需要用之前判断宽高比去掉明显不合规矩的。

bool CarPlateRecgnize::verityCharSize(Mat src) {
    //最理想情况 车牌字符的标准宽高比
    float aspect = 45.0f/90;
    //当前的真是的宽高比
    float realAspect = src.cols / src.rows;
    //最小的字符高
    float minHeight = 10.0f;
    //最大的字符高
    float maxHeight = 35.0f;
    //1. 判断高符合范围 2.宽、高比符合范围
    float error = 0.7f;

    float maxAspect = aspect + aspect*error;
    float minAspect = aspect - aspect*error;

    //这里只判断高,因为字符 1 宽度太窄了。
 if (realAspect >= minAspect && realAspect <= maxAspect && src.rows >= minHeight && src.rows <= maxHeight){
        return true;
    }
    return false;
}

汉字的字符不好取,需要通过城市的字符来定位,所以先拿城市的字符:

//通过拿到城市的轮廓取到 省份汉字的轮廓
int CarPlateRecgnize::getCityIndex(vector<Rect> src) {
    int cityIndex = 0;
    for (int i = 0; i < src.size(); ++i) {
        Rect rect = src[i];
        //获得矩形
        //把车牌区域划分为7个字符
        //如果当前获得的矩形 它的中心点 比 1/7 大,比2/7小,那么就是城市的轮廓
        int midX = rect.x + rect.width / 2;
        if (midX < 136 / 7 * 2 && midX > 136 / 7) {
            cityIndex = i;
            break;
        }
    }
    return cityIndex;
}

通过城市的轮廓 判断定位 汉字:拿到汉字。

//获取省份汉字框
void CarPlateRecgnize::getChineseRect(Rect city, Rect &chineseRect) {
    //把宽度稍微扩大一点
    float width = city.width * 1.15f;
    //城市轮廓的x坐标
    int x = city.x;

    int newX = x - width;
    chineseRect.x = newX >= 0 ? newX : 0;
    chineseRect.y = city.y;
    chineseRect.width = width;
    chineseRect.height = city.height;
}

//通过城市的下标 判断获取汉字轮廓
    Rect chineseRect;
    getChineseRect(charVec[cityIndex], chineseRect);

    rectangle(dst, chineseRect, Scalar(255, 0, 255));
    imshow("省份汉字", dst);

把这些截取的矩形框放入集合中:测试显示。

//集合里面首先加入汉字
    vector<Mat> plateChar;
    plateChar.push_back(plate_shold(chineseRect));

    //从城市开始加入字符
    int count = 0;
    for (size_t j = cityIndex; j < charVec.size(), count<6; count++, ++j) {
        plateChar.push_back(plate_shold(charVec[j]));
    }
    for(Mat s: plateChar){
        imshow("", s);
        waitKey();
    }

人工神经网络

把截取的字符集合送给ANN然后

//通过ANN进行识别
void CarPlateRecgnize::predict(vector<Mat> vector, String &result) {
    for (size_t i = 0; i < vector.size(); ++i) {
        Mat src = vector[i];
        Mat features;
        //
        getHogFeatures(annHog, src, features);
        Mat response;
        Point maxLoc;
        Point minLoc;

        Mat samples = features.reshape(1, 1);
        if (i){
            //识别字母和数字
            ann->predict(samples,response);
            //获取最大可信度 匹配度最高的属于31种中的哪一个。
            minMaxLoc(response, 0, 0, &minLoc, &maxLoc);
            //跟你训练时候有关
            int index = maxLoc.x;
            result += CHARS[index];
        }else{
            //识别汉字
            annCh->predict(samples, response);
            minMaxLoc(response, 0, 0, 0, &maxLoc);
            //跟你训练的有关
            int index = maxLoc.x;
            //识别出来的汉字,拼到string当中去
            result += ZHCHARS[index];
        }
        cout << "匹配度:" << minLoc.x << endl;
    }
}

这里需要用到训练的两个模型:

#define ANN_ZH_XML "/Users/xiuchengyin/Documents/Tina-NDK/CarAnnTrain/CarAnnTrain/resource/HOG_ANN_ZH_DATA2.xml"
#define ANN_XML "/Users/xiuchengyin/Documents/Tina-NDK/CarAnnTrain/CarAnnTrain/resource/HOG_ANN_DATA2.xml"

因为样本模型不是很全,中文字识别率比较低,以下是识别出来的车牌号。

源码可参考:车牌定位