OpenCV西班牙車牌識別(一)

開發環境:Qt

車牌定位的大致流程:
  1. 讀取原圖
    imread();
  2. 灰度化操作
    cvtColor();
    或者在讀取原圖時直接做灰度化處理
  3. 濾波去噪
    blur();
  4. 梯度運算(sobel算子)
  5. 閾值化操作(二值化)
    threshold();// CV_THRESH_OTSU大律法
  6. 利用形態學閉運算(先膨脹再腐蝕),閉運算能夠排除小型黑洞,將白色區域連成一塊。膨脹操作會使物體的邊界向外擴張,如果物體內部存在小空洞的話,經過膨脹操作這些洞將被補上,因而不再是邊界了。再進行腐蝕操作時,外部邊界將變回原來的樣子,而這些內部空洞則永遠消失了。
  7. 輪廓檢測
        //Point->vector<Point>->vector<vector<Point>>//一個輪廓由幾個點構成
        vector<vector<Point>> contours;//定義存儲輪廓的向量
        //輪廓又是 點的向量
    
        //CV_RETR_EXTERNAL:只檢測外輪廓
        //CV_CHAIN_APPROX_SIMPLE 近似方法:只存儲垂直/水平/對角直線的起始點-->就是只需要邊緣點
        findContours(closed,contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
  8. 繪製輪廓
  9. 求輪廓的最小外接矩形
  10. 驗證過濾,通過面積和寬高比,驗證是否爲車牌區域
    int i;
        //  key(整型下標), value(旋轉矩形-待選車牌區域)
        map<int, RotatedRect> imap; //映射/哈希,保存 待選車牌區域
        for(i=0; i<contours.size(); i++)
        {
            //                  所有輪廓   下標  顏色       線條粗細
            //drawContours(thresh, contours, i, Scalar(255), 3);
            //9.求輪廓最小外接矩形
            RotatedRect rect;//RotatedRect:旋轉矩形
            rect = minAreaRect(contours[i]);
    
            //10.驗證:過濾-->通過面積和寬高比 驗證是否爲車牌區域
            if(!verify(rect))
                continue;
            else
                imap[i] = rect;//哈希存儲,用數組下標的方式-->第幾個輪廓以及它的旋轉矩形
            //https://www.cnblogs.com/fnlingnzb-learner/p/5833051.html
    
            //獲取每個矩形的四個頂點
            Point2f vertices[4];
            rect.points(vertices);
            //畫直線,看效果
            int j;
            for(j=0; j<4; j++)
                line(closed,vertices[j],vertices[(j+1)%4], Scalar(255), 3);
        }
  11. 利用矩形度來驗證
    利用迭代器,通過循環找出矩形度和標準車牌最接近的矩形
     /*
        *    矩形度:周長的平方除以面積
        *    (2w+2h)*(2w+2h)/w*h
        *    (4w^2 + 4h^2 + 4w*h)/w*h
        *    4w/h + 4h/w + 8
        *    4*4.7272 + 4*(1/4.7272) + 4 = 27.75
        */
        float min_diff = 10000;//任意初始值
        int index = 0;
        map<int,RotatedRect>::iterator iter;//迭代器
        for(iter = imap.begin(); iter!=imap.end(); iter++)//通過循環,找出矩形度與標準車牌的矩形度最接近的
        {
            //求面積:
            int area = contourArea(contours[iter->first]);
            int perimeter = arcLength(contours[iter->first],true);
    
            if(area != 0)
            {
                int squareness = perimeter * perimeter / area;//矩形度
                float diff = abs(squareness - 27.75);//差值
                if(diff<min_diff)
                {
                    min_diff = diff;//當前最小差值
                    index = iter->first;//對應的下標
                }
            }
        }
  12. 繪製最接近的矩形
  13. 圖像切割

以下爲本模塊全部代碼:

#include "locate.h"
#include <opencv.hpp>
#include <map>

using namespace cv;

#define Debug 1

void myDebug(const string &winname, const Mat &image)//調試代碼
{
#if Debug
    imshow(winname,image);
#endif
}

bool verify(RotatedRect rect)//寬高比的算法
{
    const float aspect = 4.7272;//寬高比(西班牙車牌)
    int min = 15 * aspect * 15;//面積下限(寬*高)
    int max = 125 * aspect * 125;//面積上限

    //寬高比誤差範圍
    float error = 0.1;
    float rmin = aspect - aspect * error; //誤差
    float rmax = aspect + aspect * error;

    int area = rect.size.width * rect.size.height; //實際面積
    float rate = rect.size.width / rect.size.height; //實際寬高比

    return area>=min && area<=max && rate>=rmin && rate<=rmax;
}


void locate(const string &filename)
{
    //1.讀入圖像
    Mat image;
    image = imread(filename);
    if(image.empty())
        return;
    myDebug("src",image);

    //2.灰度化
    Mat gray;
    cvtColor(image,gray,COLOR_BGR2GRAY);
    myDebug("gray",gray);

    //3.濾波去噪
    Mat blured;
    blur(gray,blured,Size(5,5));
    myDebug("blured",blured);

    //4.梯度運算,求邊緣--定位出可能的位置-->利用sobel算子
    Mat xsobel,ysobel;
    Sobel(blured, xsobel, CV_8U, 1, 0, 3);
    Sobel(blured, ysobel, CV_8U, 0, 1, 3);
    myDebug("xsobel",xsobel);
    myDebug("ysobel",ysobel);

    //5.閾值化--得到最優的二值圖像
    Mat thresh;
    threshold(xsobel,thresh, 0, 255, CV_THRESH_OTSU);//大律法
    myDebug("thresh",thresh);

    //6.形態學閉運算--白色連成一片-->獲取結構元素/閉運算
    //使用閉運算(先膨脹再腐蝕)連通臨近區域,說白了就是將亮的區域連在一起
    Mat closed;
    Mat element = getStructuringElement(MORPH_RECT,Size(17,3));
    morphologyEx(thresh,closed,MORPH_CLOSE,element);
    myDebug("closed",closed);

    //7.輪廓檢測
    //Point->vector<Point>->vector<vector<Point>>//一個輪廓由幾個點構成
    vector<vector<Point>> contours;//定義存儲輪廓的向量
    //輪廓又是 點的向量

    //CV_RETR_EXTERNAL:只檢測外輪廓
    //CV_CHAIN_APPROX_SIMPLE 近似方法:只存儲垂直/水平/對角直線的起始點-->就是只需要邊緣點
    findContours(closed,contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    //8.繪製輪廓
    int i;
    //  key(整型下標), value(旋轉矩形-待選車牌區域)
    map<int, RotatedRect> imap; //映射/哈希,保存 待選車牌區域
    for(i=0; i<contours.size(); i++)
    {
        //                  所有輪廓   下標  顏色       線條粗細
        //drawContours(thresh, contours, i, Scalar(255), 3);
        //9.求輪廓最小外接矩形
        RotatedRect rect;//RotatedRect:旋轉矩形
        rect = minAreaRect(contours[i]);

        //10.驗證:過濾-->通過面積和寬高比 驗證是否爲車牌區域
        if(!verify(rect))
            continue;
        else
            imap[i] = rect;//哈希存儲,用數組下標的方式-->第幾個輪廓以及它的旋轉矩形
        //https://www.cnblogs.com/fnlingnzb-learner/p/5833051.html

        //獲取每個矩形的四個頂點
        Point2f vertices[4];
        rect.points(vertices);
        //畫直線,看效果
        int j;
        for(j=0; j<4; j++)
            line(closed,vertices[j],vertices[(j+1)%4], Scalar(255), 3);

    }
    myDebug("contours", closed);
    cout << imap.size() << endl;

    //11.利用矩形度來驗證
    /*
        矩形度:周長的平方除以面積
        (2w+2h)*(2w+2h)/w*h
        (4w^2 + 4h^2 + 4w*h)/w*h
        4w/h + 4h/w + 8
        4*4.7272 + 4*(1/4.7272) + 4 = 27.75
    */
    float min_diff = 10000;//任意初始值
    int index = 0;
    map<int,RotatedRect>::iterator iter;//迭代器
    for(iter = imap.begin(); iter!=imap.end(); iter++)//通過循環,找出矩形度與標準車牌的矩形度最接近的
    {
        //求面積:
        int area = contourArea(contours[iter->first]);
        int perimeter = arcLength(contours[iter->first],true);

        if(area != 0)
        {
            int squareness = perimeter * perimeter / area;//矩形度
            float diff = abs(squareness - 27.75);//差值
            if(diff<min_diff)
            {
                min_diff = diff;//當前最小差值
                index = iter->first;//對應的下標
            }
        }

    }

    //12.繪製最接近的矩形
    RotatedRect rect2 = imap[index];
    Point2f vertices2[4];
    rect2.points(vertices2);
    for(int i=0; i<4; i++)
    {
        line(image,vertices2[i],vertices2[(i+1)%4], Scalar(0,255,0), 3);
    }
    myDebug("final",image);

    //13.圖像切割 始終保持 寬 > 高
    Mat locate;
    RotatedRect rest = imap[index];
    Size size = rest.size;
    if(size.width < size.height)
        swap(size.width, size.height);
    //           原圖像  大小(寬高) 中心點   輸出結果
    getRectSubPix(image, size, rest.center, locate);
    imwrite("locate1.jpg",locate);

    waitKey(0);
}
歡迎交流學習,若有不足,還望指正!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章