OpenCV入门(七)——车牌区域检测

OpenCV入门(七)——车牌区域检测,第1张

对于一幅RGB图像,我们先将其转为HSI颜色空间,HSI分别是什么:

  • H:Hue 色调(描述纯色的属性)

  • S:Saturation 饱和度(指纯色被白色稀释的程度的度量,其值越大,色彩纯度越高)

  • I:Illumination 亮度(描述光照的强度)

那么对于一幅RGB的图像,RGB到底要怎么转换为HSI:

 关于HSI分离:

cv::Mat srcImage = cv::imread("./image/cheche.jpg");
// 转换成hsv 
cv::Mat img_h, img_s, img_v, imghsv;
std::vector 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;

从图像可得出,对于一幅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_(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;
}

处理后的效果:

在HSI颜色空间上对色调H、饱和度S、亮度I进行约束条件的限制。之后我们需要获得图像中满足车牌背景底色(蓝色、黄色、黑色、白色)的区域,那么条件约束如下:

蓝色通道:0.350.1 I>0.1

黄色通道:H<0.4 S>0.1 I>0.3

黑色通道:I<0.5

白色通道:S<0.4 I>0.5

那么我们就根据上方给出的数据进行车牌疑似区域点提取。

// 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 ((sobelresult.at(k, l) == 255) && (cv::countNonZero(bw_blue(rct)) >= 1))
            bw_blue_edge.at(k, l) = 255;
    }
}

那么最后划到的蓝色区域如下:

上面的约束条件只对蓝色背景的车牌有效,如果需要获取其他背景颜色的车牌,需要改对应的HSV值。当我们获取到对应的二值化图像时,我们要对图像中每一个像素投票决策其是否属于车牌区域内部点。具体来说,用3*3的窗口依次滑动遍历目标图像,如果该像素点周围的8邻域范围内至少存在两个边缘点,且又存在蓝色像素点(存在蓝色像素点就说明得到的二值化图像中,蓝色部分为白色,我们要检测是否为白色),那么我们就可以判断该像素点为车牌区域内部点,这样可以抑制非车牌区域的干扰。

countNonZero():返回灰度值不为0的像素数,可用来判断图像是否全黑。

根据上述步骤得到的车牌区域疑似点,对上述疑似点进行形态学闭 *** 作,连接各个区域点集,形态学闭 *** 作算子为1*3。(我认为时该闭 *** 作的区间越小越精确)

接着对闭 *** 作图像进行连通区域检测,在得到的连通域外轮廓中完成车牌区域的筛选。车牌区域具有明显的特征,这里筛选选用的特征有非零区域占的像素比、宽高及宽高比例,此处将像素比参数设置为0.5,宽高分别为60和12,宽高比大于2小于5。以上的参数的设置,可以跟随着车辆与摄像机的距离变化进行调整。

// 形态学闭 *** 作
cv::Mat morph;
cv::morphologyEx(bw_blue_edge, morph, cv::MORPH_CLOSE,
    cv::Mat::ones(1, 5, 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 > region_contours;
cv::findContours(morph.clone(), region_contours,
    CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
std::vector candidates;
std::vector 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);
    }
}

关于形态学技术,下一章详述。

先看看第一步形闭学 *** 作效果:

 

 再看看连通区域提取效果:

 那么最后的提取车牌效果是这样的:

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/796004.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-06
下一篇 2022-05-06

发表评论

登录后才能评论

评论列表(0条)

保存