车牌区域检测

13 篇文章 0 订阅

车牌区域检测

出至《OpenCV图像处理编程实例》

1:对于场景内车辆目标图像srcImage,首先将图像转换到HSI颜色空间,HSI颜色空间依据色彩的三个基本性质:色调(Hue)、饱和度(Saturation)和亮度(Illumination).色调(H)是描述纯色的属性:饱和度(S)是指纯色被白色稀释的程度的度量,其值越大,色彩纯度越高;亮度(I)是描述光照的强度。
对于一幅三颜色RGB(红绿蓝)彩色图像,每个RGB(红绿蓝)像素可用如下公式求HSI分量:
这里写图片描述
HSV通道分离的代码如下:

cv::Mat img_h, img_s, img_v, imghsv;
    std::vector<cv::Mat> hsv_vec;
    cv::cvtColor(srcImage, imghsv, CV_BGR2HSV);
    cv::imshow("hsv", imghsv);
    cv::waitKey(0);
    // 分割hsv通道
    cv::split(imghsv, hsv_vec);
    img_h = hsv_vec[0];
    img_s = hsv_vec[1];
    img_v = hsv_vec[2];
    img_h.convertTo(img_h, CV_32F);
    img_s.convertTo(img_s, CV_32F);
    img_v.convertTo(img_v, CV_32F);
    double max_s, max_h, max_v;
    cv::minMaxIdx(img_h, 0, &max_h);
    cv::minMaxIdx(img_s, 0, &max_s);
    cv::minMaxIdx(img_v, 0, &max_v);
    // 各个通道归一化
    img_h /= max_h;
    img_s /= max_s;
    img_v /= max_v;

2:根据目标图像HSI,在饱和度图像通道上求其竖直边缘,本节采用垂直索贝尔算子(sobel)提取边缘。饱和度是判断色彩(蓝色和黄色)与非色彩(白和黑)的最好手段,即蓝色背景白色字符以及黄色背景黑色字符的边缘响应可以被强化,还能抑制一些其他干扰边缘(白色和黑色)。通常采用索贝尔算子求图像的细化竖直边缘,具体实现中通过非极大值抑制和阈值化处理操作,剔除尽量多的伪边缘区域以获取二值化边缘图像。
细化sobel边缘提取代码如下:

bool SobelVerEdge(cv::Mat srcImage, cv::Mat& resultImage)
{
    CV_Assert(srcImage.channels() == 1);
    srcImage.convertTo(srcImage, CV_32FC1);
    // 水平方向的 Sobel 算子
    cv::Mat sobelx = (cv::Mat_<float>(3, 3) << -0.125, 0, 0.125,
        -0.25, 0, 0.25,
        -0.125, 0, 0.125);
    cv::Mat ConResMat;
    // 卷积运算
    cv::filter2D(srcImage, ConResMat, srcImage.type(), sobelx);
    // 计算梯度的幅度
    cv::Mat graMagMat;
    cv::multiply(ConResMat, ConResMat, graMagMat);
    // 根据梯度幅度及参数设置阈值
    int scaleVal = 4;
    double thresh = scaleVal * cv::mean(graMagMat).val[0];
    cv::Mat resultTempMat = cv::Mat::zeros(
        graMagMat.size(), graMagMat.type());
    float* pDataMag = (float*)graMagMat.data;
    float* pDataRes = (float*)resultTempMat.data;
    const int nRows = ConResMat.rows;
    const int nCols = ConResMat.cols;
    for (int i = 1; i != nRows - 1; ++i) {
        for (int j = 1; j != nCols - 1; ++j) {
            // 计算该点梯度与水平或垂直梯度值大小比较结果
            bool b1 = (pDataMag[i * nCols + j] > pDataMag[i *
                nCols + j - 1]);
            bool b2 = (pDataMag[i * nCols + j] > pDataMag[i *
                nCols + j + 1]);
            bool b3 = (pDataMag[i * nCols + j] > pDataMag[(i - 1)
                * nCols + j]);
            bool b4 = (pDataMag[i * nCols + j] > pDataMag[(i + 1)
                * nCols + j]);
            // 判断邻域梯度是否满足大于水平或垂直梯度
            // 并根据自适应阈值参数进行二值化
            pDataRes[i * nCols + j] = 255 * ((pDataMag[i *
                nCols + j] > thresh) &&
                ((b1 && b2) || (b3 && b4)));
        }
    }
    resultTempMat.convertTo(resultTempMat, CV_8UC1);
    resultImage = resultTempMat.clone();
    return true;
}

3:在HSI颜色空间上通过对色调H、饱和度S、亮度I进行约束条件的限制,获得图像中满足车牌背景底色(蓝色、黄色、黑色、白色)的区域,详细的HSI颜色约束条件如下:

  • 白色通道限定范围:S < 0.4, I > 0.5
  • 黑色通道限定范围:I < 0.5;
  • 黄色通道限定范围:H< 0.4,S > 0.1,I > 0.3;
  • 蓝色通道限定范围:0.35< H <0.7,S > 0.1,I >0.1;

车牌疑似区域点提取:

cv::Mat bw_blue = ((img_h>0.45) &
        (img_h<0.75) &
        (img_s>0.15) &
        (img_v>0.25));
    int height = bw_blue.rows;
    int width = bw_blue.cols;
    cv::Mat bw_blue_edge = cv::Mat::zeros(bw_blue.size(), bw_blue.type());
    cv::imshow("bw_blue", bw_blue);
    cv::waitKey(0);
    // 车牌疑似区域提取
    for (int k = 1; k != height - 2; ++k)
    {
        for (int l = 1; l != width - 2; ++l)
        {
            cv::Rect rct;
            rct.x = l - 1;
            rct.y = k - 1;
            rct.height = 3;
            rct.width = 3;
            if ((sobelMat.at<uchar>(k, l) == 255) && (cv::countNonZero(bw_blue(rct)) >= 1))
                bw_blue_edge.at<uchar>(k, l) = 255;
        }
    }

根据上述HSI颜色通道约束条件,实验测试实例为蓝色车牌背景(黄色、白色、黑色同理),获得对应二值颜色图像。根据上面得到的二值颜色图像Icb和二值边缘图像Ieb,对图像中每一个像素投票来决策其是否属于车牌区域内部点。具体来说,用3X3的窗口依次滑动遍历目标图像,如果该像素点周围的8领域范围内至少存在两个边缘点且又存在蓝色像素点,则可判断该像素点为车牌区域内部点,这样可以抑制非车牌区域的干扰,车牌区域内部点二值化图像为I_ib.


4:得到车牌区域疑似点,对上述疑似点进行形态学闭操作,连接各个区域点集,形态学闭操作算子2X25;接着对闭操作图像进行连通区域检测,在得到连通区域外轮廓中完成车牌区域的筛选。车牌区域有着明显特征,这里筛选用的特征有非零区域占的像素比、宽高及宽高比例,此处将像素比参数设置为0.5,宽高分别是60和12,宽高比大于2小于5.需要说明的是,以上参数设置可随着车辆与相机的距离变化进行调整。
车牌区域提取代码如下:

cv::Mat morph;
    cv::morphologyEx(bw_blue_edge, morph, cv::MORPH_CLOSE,
        cv::Mat::ones(2, 25, CV_8UC1));
    cv::Mat imshow5;
    cv::resize(bw_blue_edge, imshow5, cv::Size(), 1, 1);
    cv::imshow("morphology_bw_blue_edge", imshow5);
    cv::waitKey(0);
    // 连通区域提取
    cv::imshow("morph", morph);
    std::vector<std::vector<cv::Point> > region_contours;
    cv::findContours(morph.clone(), region_contours,
        CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
    std::vector<cv::Rect> candidates;
    std::vector<cv::Mat> candidates_img;
    cv::Mat result;
    for (size_t n = 0; n != region_contours.size(); ++n)
    {
        // 去除高度宽度不符合条件区域
        cv::Rect rect = cv::boundingRect(region_contours[n]);
        int sub = cv::countNonZero(morph(rect));
        double ratio = double(sub) / rect.area();
        double wh_ratio = double(rect.width) / rect.height;
        if (ratio > 0.5 && wh_ratio > 2 && wh_ratio < 5 &&
            rect.height > 12 && rect.width > 60)
        {
            cv::Mat small = bw_blue_edge(rect);
            result = srcImage(rect);
            cv::imshow("rect", srcImage(rect));
            cv::waitKey(0);
        }
    }

5:所有代码如下:

#include <iostream>
#include <vector>
#include <stdint.h>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/features2d/features2d.hpp"
// 提取竖直的sobel边缘
bool SobelVerEdge(cv::Mat srcImage, cv::Mat& resultImage)
{
    CV_Assert(srcImage.channels() == 1);
    srcImage.convertTo(srcImage, CV_32FC1);
    // 水平方向的 Sobel 算子
    cv::Mat sobelx = (cv::Mat_<float>(3, 3) << -0.125, 0, 0.125,
        -0.25, 0, 0.25,
        -0.125, 0, 0.125);
    cv::Mat ConResMat;
    // 卷积运算
    cv::filter2D(srcImage, ConResMat, srcImage.type(), sobelx);
    // 计算梯度的幅度
    cv::Mat graMagMat;
    cv::multiply(ConResMat, ConResMat, graMagMat);
    // 根据梯度幅度及参数设置阈值
    int scaleVal = 4;
    double thresh = scaleVal * cv::mean(graMagMat).val[0];
    cv::Mat resultTempMat = cv::Mat::zeros(
        graMagMat.size(), graMagMat.type());
    float* pDataMag = (float*)graMagMat.data;
    float* pDataRes = (float*)resultTempMat.data;
    const int nRows = ConResMat.rows;
    const int nCols = ConResMat.cols;
    for (int i = 1; i != nRows - 1; ++i) {
        for (int j = 1; j != nCols - 1; ++j) {
            // 计算该点梯度与水平或垂直梯度值大小比较结果
            bool b1 = (pDataMag[i * nCols + j] > pDataMag[i *
                nCols + j - 1]);
            bool b2 = (pDataMag[i * nCols + j] > pDataMag[i *
                nCols + j + 1]);
            bool b3 = (pDataMag[i * nCols + j] > pDataMag[(i - 1)
                * nCols + j]);
            bool b4 = (pDataMag[i * nCols + j] > pDataMag[(i + 1)
                * nCols + j]);
            // 判断邻域梯度是否满足大于水平或垂直梯度
            // 并根据自适应阈值参数进行二值化
            pDataRes[i * nCols + j] = 255 * ((pDataMag[i *
                nCols + j] > thresh) &&
                ((b1 && b2) || (b3 && b4)));
        }
    }
    resultTempMat.convertTo(resultTempMat, CV_8UC1);
    resultImage = resultTempMat.clone();
    return true;
}
// 疑似区域提取
cv::Mat getPlateArea(cv::Mat srcImage, cv::Mat sobelMat)
{
    // 转换成hsv 
    cv::Mat img_h, img_s, img_v, imghsv;
    std::vector<cv::Mat> hsv_vec;
    cv::cvtColor(srcImage, imghsv, CV_BGR2HSV);
    cv::imshow("hsv", imghsv);
    cv::waitKey(0);
    // 分割hsv通道
    cv::split(imghsv, hsv_vec);
    img_h = hsv_vec[0];
    img_s = hsv_vec[1];
    img_v = hsv_vec[2];
    img_h.convertTo(img_h, CV_32F);
    img_s.convertTo(img_s, CV_32F);
    img_v.convertTo(img_v, CV_32F);
    double max_s, max_h, max_v;
    cv::minMaxIdx(img_h, 0, &max_h);
    cv::minMaxIdx(img_s, 0, &max_s);
    cv::minMaxIdx(img_v, 0, &max_v);
    // 各个通道归一化
    img_h /= max_h;
    img_s /= max_s;
    img_v /= max_v;
    // hsv 限定范围元素提取
    cv::Mat bw_blue = ((img_h>0.45) &
        (img_h<0.75) &
        (img_s>0.15) &
        (img_v>0.25));
    int height = bw_blue.rows;
    int width = bw_blue.cols;
    cv::Mat bw_blue_edge = cv::Mat::zeros(bw_blue.size(), bw_blue.type());
    cv::imshow("bw_blue", bw_blue);
    cv::waitKey(0);
    // 车牌疑似区域提取
    for (int k = 1; k != height - 2; ++k)
    {
        for (int l = 1; l != width - 2; ++l)
        {
            cv::Rect rct;
            rct.x = l - 1;
            rct.y = k - 1;
            rct.height = 3;
            rct.width = 3;
            if ((sobelMat.at<uchar>(k, l) == 255) && (cv::countNonZero(bw_blue(rct)) >= 1))
                bw_blue_edge.at<uchar>(k, l) = 255;
        }
    }
    // 形态学闭操作
    cv::Mat morph;
    cv::morphologyEx(bw_blue_edge, morph, cv::MORPH_CLOSE,
        cv::Mat::ones(2, 25, CV_8UC1));
    cv::Mat imshow5;
    cv::resize(bw_blue_edge, imshow5, cv::Size(), 1, 1);
    cv::imshow("morphology_bw_blue_edge", imshow5);
    cv::waitKey(0);
    // 连通区域提取
    cv::imshow("morph", morph);
    std::vector<std::vector<cv::Point> > region_contours;
    cv::findContours(morph.clone(), region_contours,
        CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
    std::vector<cv::Rect> candidates;
    std::vector<cv::Mat> candidates_img;
    cv::Mat result;
    for (size_t n = 0; n != region_contours.size(); ++n)
    {
        // 去除高度宽度不符合条件区域
        cv::Rect rect = cv::boundingRect(region_contours[n]);
        int sub = cv::countNonZero(morph(rect));
        double ratio = double(sub) / rect.area();
        double wh_ratio = double(rect.width) / rect.height;
        if (ratio > 0.5 && wh_ratio > 2 && wh_ratio < 5 &&
            rect.height > 12 && rect.width > 60)
        {
            cv::Mat small = bw_blue_edge(rect);
            result = srcImage(rect);
            cv::imshow("rect", srcImage(rect));
            cv::waitKey(0);
        }
    }
    return result;
}
int main()
{
    cv::Mat srcImage = cv::imread("car.jpg");
    if (!srcImage.data)
        return 1;
    cv::Mat srcGray;
    cv::cvtColor(srcImage, srcGray, CV_RGB2GRAY);
    cv::imshow("srcImage", srcImage);
    // sobel 提取边缘
    cv::Mat sobelMat;
    SobelVerEdge(srcGray, sobelMat);
    cv::imshow("Sobel", sobelMat);
    // 疑似区域提取
    cv::Mat result = getPlateArea(srcImage, sobelMat);
    return 0;
}

结果如下:
这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值