opencv 十 提取车辆照片中的车牌区域(基于颜色特征)

一、算法需求

基于基于颜色特征多个图像提取车辆照片中的车牌区域,保证算法的通用性和算法效果稳定
在这里插入图片描述

二、问题分析

基于颜色特征车辆照片中提取车牌区域,需要对车牌的颜色和颜色空间有所了解。现有知识信息如下:
1、车牌的场景颜色为为蓝色、黄色和绿色
2、HSV颜色空间的H分量就是描述颜色值
3、车牌的颜色在车辆照片中是较为显著的

三、核心思路

针对上述知识信息,具体推断出的算法研发步骤为:
1、在HSV空间下提取出各类潜在颜色的车牌区域H_mask
2、提取出图像中颜色显著的区域SV_mask(需要进行观测,对图像的显著性进行定义)
3、连立H_mask与SV_mask获取最终区域

四、初步尝试

在HSV颜色空间上提取各类颜色值,首先要了解各种颜色在通道上的值,然后实现值域截取函数。

4.1 hsv颜色空间

HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. Smith在1978年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)。、这个模型中颜色的参数分别是:色调(H),饱和度(S),亮度(V)。

在opencv中可以使用以下代码实现将图像从BGR空间转换为HSV空间

cv::Mat hsv;//H:0—180, S:0—255, V:0—255
cv::cvtColor(srcImage, hsv, cv::COLOR_BGR2HSV);

4.2 值域截取

在opencv中实现值域截取(值域范围[thres_min,thres_max]),需要遍历图像对每一个像素进行判断,将小于特定值thres_min和大于特定值thres_max的像素值设置为0,将符合要求的像素值设为255;这描述,可以看出值域截取其实就是两个二值化THRESH_BINARY和THRESH_BINARY_)的结合。

//THRESH_BINARY 将超出值域thres_max的部分设为255,未超出的置0
threshold(tmp, result1, thres_max, 255, cv::THRESH_BINARY);
//THRESH_BINARY_INV将小于值域thres_min的部分设为255,大于的置0
threshold(tmp, result2, thres_min, 255, cv::THRESH_BINARY_INV);

先使用THRESH_BINARY和thres_max得出超出阈值thres_max的部分max_out,然后使用使用THRESH_BINARY_INV和thres_min得出小于阈值thres_min的部分min_out,然后用白图-max_out-min_out就得到了在值域内的部分了。
函数的具体实现如下所示,调用函数get_dst_8UC1即可截取mat中的特定值域

//按照要求进行二值化
inline Mat cut_value_8UC1(cv::Mat tmp, int thres_, int type) {
    
    
    cv::Mat result;
    if (type == 0) {
    
    
        cv::threshold(tmp, result, thres_, 255, cv::THRESH_BINARY);//大于阈值返回 最大值 255 小于返回0
    }
    else {
    
    
        cv::threshold(tmp, result, thres_, 255, cv::THRESH_BINARY_INV);//小于等于阈值返回最大值 255 
    }
    return result;
}
//将im中小于等于minv的部分和大于maxv的部分置0
inline Mat get_dst_8UC1(cv::Mat im, int minv = 0, int maxv = 255, bool only_mask=false) {
    
    
    //将大于maxv的部分置255
    cv::Mat mat_maxout_mask = cut_value_8UC1(im, maxv, 0);
    //将小于minv的部分置255
    cv::Mat mat_minout_mask = cut_value_8UC1(im, minv, 1);
    //值域外的mask
    cv::Mat all_out_mask = mat_maxout_mask + mat_minout_mask;
    if (only_mask) {
    
    
        return all_out_mask;
    }
    else {
    
    
        //值域内的mask
        cv::Mat all_mask = (255 - all_out_mask) / 255;
        //目标数据   0*n=0 1*n=1
        cv::Mat dst_data = all_mask.mul(im);
        return dst_data;
    }
}

4.3 hsv颜色分量分析

使用以下代码保存多个图像的yellow_mask 、green_mask 、blue_mask 、s_mask 和v_mask 进行可视化。

		string path = "C:\\Users\\Pictures\\Car\\"  ;
        srcImage = imread(path + "1.jpg");
        resize(srcImage, srcImage, {
    
     600,800 });
        //将图像的颜色空间从BGR转换为HSV
        cv::Mat hsv;//H:0—180, S:0—255, V:0—255
        cv::cvtColor(srcImage, hsv, cv::COLOR_BGR2HSV);
        vector<cv::Mat> mv;
        cv::split(hsv, mv);
        //hsv空间颜色范围参考
        //https://blog.csdn.net/leo_888/article/details/88284251
        Mat yellow_mask = get_dst_8UC1(mv[0], 26, 34);
        Mat green_mask = get_dst_8UC1(mv[0], 35, 77);
        Mat blue_mask = get_dst_8UC1(mv[0], 100, 124);

        Mat s_mask = get_dst_8UC1(mv[1], 43, 256);
        Mat v_mask = get_dst_8UC1(mv[2], 46, 256);
        imwrite(path + "_y.jpg", yellow_mask);
        imwrite(path + "_g.jpg", green_mask);
        imwrite(path + "_b.jpg", blue_mask);
        imwrite(path + "_s.jpg", s_mask);
        imwrite(path + "_v.jpg", v_mask);

通过以下图片可以看到,使用常规的颜色分量截取代码,在存在较多误检,如:截取蓝色区域(存在蓝色的车辆,且其他误检也较多),截取绿色车牌(存在植被干扰),截取黄色车牌(??目前没有采集到黄色车牌的数据)。从目前效果而言,换个角度思考在进行颜色截取后删除了图像中大部分不相关的区域
在这里插入图片描述
此外也发现,不管什么颜色的车牌区域在S通道和V通道都有显示,S是描述颜色的饱和度、H是描述明度。故尝试将s_mask与v_mask做一个点乘sv_mask ,使用sv_mask表示图像中的颜色显著区域。可以在上图中看到倒数第三列*_sv.jpg还是有效的描述了图像中颜色显著的区域(如红色、蓝色、绿色都被有效提取了)。

显著性提取计算方式如下:为了避免s_mask.mul(v_mask)超出值域,故将两个数压缩到0,1之间,在保存sv_mask 时发现颜色过暗,故又将数值*2。

s_mask.convertTo(s_mask, CV_32FC1);
v_mask.convertTo(v_mask, CV_32FC1);
Mat sv_mask = s_mask.mul(v_mask/255.0) / 255;
sv_mask = (sv_mask*255)*2;//为了将值域从0,1转换为0,255
sv_mask.convertTo(sv_mask, CV_8UC1);
imwrite(path + "_sv.jpg", sv_mask );

五、具体实现

基于上述分析和初步尝试,得出以下实现步骤

5.1 读取图像

这里缺失了部分变量定义,因为这段代码外面有个while(ture),如srcImage 则定义在while(ture)前面

string path = "C:\\Users\\hpg\\Pictures\\Car\\" + s1 ;
srcImage = imread(path + ".jpg");
resize(srcImage, srcImage, {
    
     600,800 });

5.2 颜色转HSV

将图像的颜色空间从BGR转换为HSV,并使用split方法将三通道图像hsv分割为包含3个单通道mat的数组mv。mv[0]则对于通道H,mv[1]则对于通道S,mv[2]则对于通道V。

//将图像的颜色空间从BGR转换为HSV
cv::Mat hsv;//H:0—180, S:0—255, V:0—255
cv::cvtColor(srcImage, hsv, cv::COLOR_BGR2HSV);
vector<cv::Mat> mv;
cv::split(hsv, mv);

5.3 截取颜色

基于H通道提取特定颜色区域,分别可得到yellow_mask 、green_mask 、blue_mask ,全部累加可得mask 。具体效果可以到4.3中看

//基于H通道提取特定颜色区域
//hsv空间颜色范围参考https://blog.csdn.net/leo_888/article/details/88284251
Mat yellow_mask = get_dst_8UC1(mv[0], 26, 34);
Mat green_mask = get_dst_8UC1(mv[0], 35, 77);
Mat blue_mask = get_dst_8UC1(mv[0], 100, 124);//100, 124
Mat mask = yellow_mask + green_mask + blue_mask;

5.4 显著区域提取

这里将显著区域定义为S*V,在截取时先排除了不在绿色、蓝色、黄色的S通道和V通道范围的区域。同时为了避免乘法后的结果超出255,将s_mask 和v_mask除以255。在可是保存单通道结果时需要将CV_32FC1转换为CV_8UC1,故要将sv_mask *255,同时发现图像过暗,故使用sv_mask * 255 * 2。

//基于S、V通道提取图像中的显著性区域
Mat s_mask = get_dst_8UC1(mv[1], 43, 256);//绿色、蓝色、黄色的S通道范围
Mat v_mask = get_dst_8UC1(mv[2], 46, 256);//绿色、蓝色、黄色的V通道范围
s_mask.convertTo(s_mask, CV_32FC1);
v_mask.convertTo(v_mask, CV_32FC1);
Mat sv_mask = s_mask.mul(v_mask/255.0) / 255;
sv_mask = sv_mask * 255 * 2;
sv_mask.convertTo(sv_mask, CV_8UC1);

在提取显著性颜色后,效果如下图中额度空时区域所示,可见对于颜色显著的红色和蓝色车辆有较大的错误提取区域(跟车牌不相关区域)。
在这里插入图片描述

5.5 车牌区域粗提取

在5.4的图中可以看到19、21和22的车牌误提取的显著性区域较多,同时也发现车牌区域的显著性颜色与面积去其他区域不一样,故使用adaptiveThreshold进行二值化,二值化后的结果如在5.4的图的*_bin.jpg所示,可见adaptiveThreshold函数有效的增强了车牌区域的显著性(对应变量bin_mat),但是也存在部分误检。故使用5.3得到的颜色mask与bin_mat相乘,过滤掉其他误检。

 Mat  bin_mat, final_mask;
//adaptiveThreshold的参数用法可以参考https://blog.csdn.net/laoyezha/article/details/106445437
adaptiveThreshold(sv_mask, bin_mat, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 35, -30);
final_mask = mask.mul(bin_mat);

目前算法的处理效果如下所示,可以看到针对大部分车辆,车牌区域已经是图像中最大的连通域(*_final.jpg),但是在测试更多的图像时有点小意外。
在这里插入图片描述
具体如下所示,在提取蓝色车辆的车牌时发现,蓝色既是在mask中(黄色区域、绿色区域、蓝色区域的累加),也是显著性区域(在13_sv.jpg中的白色区域),故此到这里需要设计一些形态学处理流程。
在这里插入图片描述

5.6 车牌区域优化

通过前面流程提取车牌区域,基本上都是图像中最大的连通域,而在蓝色车辆时存在bug,故需要进行优化。优化的思路是利用形态学方法增强蓝色车辆中车牌的显著性,最终使蓝色车辆中车牌区域形成最大的连通域。

通过对图片进行分析发现,所提取的车牌中基本都带有黑色的字符,若将进行黑帽运算(获取图形中的的缺口、孔洞区域等细小的黑色区域)即可提取出字符部分;而非车牌区域的白色(如蓝色车辆的*_final.jpg)中字符区域较少。对黑帽运算得出的区域进行膨胀即可得到车牌区域。

优化效果如下图所示,可见对于12、13、19等蓝色车牌区域的显著性增强是有效的。在这里插入图片描述
不过发现在这里图片20效果不佳,这是一个绿牌车辆,经具体分析发现是在提取绿色分量时没有具体提取到车牌区域(应该增到绿色的范围)。因为这个车牌的照片有些偏青色,故将截取范围扩大了3,效果立刻矫正了。

Mat green_mask = get_dst_8UC1(mv[0], 35, 80);//35-77

具体代码如如下所示,最终效果如下所示

//车牌区域优化1
Mat  blackhat_mat,dilate_mat;
morphologyEx(final_mask, blackhat_mat, MORPH_BLACKHAT,
    getStructuringElement(MORPH_RECT, Size(7, 7)));
morphologyEx(blackhat_mat, dilate_mat, MORPH_BLACKHAT,
    getStructuringElement(MORPH_RECT, Size(17, 17)));

在这里插入图片描述

5.7 车牌区域初提取

在5.6中最后的效果图中可以看到,车牌区域已经是极其明显了(在图像中宽度和高度都适中的物体),使用特殊的闭运算算子消除区域即可。

//车牌区域提取 (此时车牌区域已经是dilate_mat图像中的主要区域)
Mat  dilate_close,dilate_open;
//先使车牌中孔洞和缺口区域闭合
morphologyEx(dilate_mat, dilate_close, MORPH_CLOSE,
    getStructuringElement(MORPH_RECT, Size(13, 7)));
//消除小面积区域,使车牌区域与其他噪声断开
//此时在大部分图片中车牌区域已经被提取出来了
morphologyEx(dilate_close, dilate_open, MORPH_OPEN,
    getStructuringElement(MORPH_RECT, Size(51, 21)));

目前提取的车牌区域效果如下,针对大部分图像已经可以了,只是极个别图像中所提取的区域较多,或者无法完整覆盖到原车牌区域。
在这里插入图片描述

5.8 车牌区域精提取

在5.7进行优化后,完全可以基于最大连通域找到车牌区域。但是这个最大连通域max_aero并不能完全覆盖到原车牌,很容易存在宽度或高度不够的情况(尤其是宽度,因为车牌是横向的长方形),故将max_aero进行膨胀,得到max_aero_dilate,膨胀算子Size(31, 23)在width上大于height。

但是,max_aero_dilate带来了一些非车牌区域,为了精准提取车牌区域,考虑使用bin_mat进行信息精细化(因为车牌区域一定在bin_mat的闭运算结果中bin_mat_dilate,max_aero_dilate新增的区域不一定在)。在绿牌电动车中由于车牌部分存在白色,使用普通算子得到的bin_mat_dilate是无法精准向上扩展覆盖的原先漏掉的白色区域,故将算子的size设计在height上大于width。

最终代码和提取的车牌区域如下

//---车牌区域精提取---
//找到最大的连通域
Mat max_aero= findMaxArea2(dilate_open);
//确保最大的连通域能完全覆盖车牌区域
Mat max_aero_dilate, bin_mat_dilate;
morphologyEx(max_aero, max_aero_dilate, MORPH_DILATE,
    getStructuringElement(MORPH_RECT, Size(31, 23)));
//对bin_mat进行进行闭运算,使车牌区域完成闭合
morphologyEx(bin_mat, bin_mat_dilate, MORPH_CLOSE,
    getStructuringElement(MORPH_RECT, Size(17, 23)));
Mat final_aero,final_mat;
bitwise_and(max_aero_dilate, bin_mat_dilate, final_aero);
srcImage.copyTo(final_mat, final_aero);
//对bin_mat进行
imwrite(path + "_max_aero_dilate.jpg", max_aero_dilate);
imwrite(path + "_bin_mat_dilate.jpg", bin_mat_dilate);
imwrite(path + "_final_aero.jpg", final_aero);
imwrite(path + "_final_img.jpg", final_mat);

在这里插入图片描述

六、最终效果

补充上以下代码,根据final_aero的最小外接矩形在final_mat中截取区域保存图像

Rect bound=boundingRect(final_aero);
        Mat only_car_num2 = final_mat(bound);
        //对bin_mat进行
        imwrite(path + "_carNum.jpg", only_car_num2);

最终实现效果如下,编码不易,想要完整实现代码的可以私聊博主有偿获取。
在这里插入图片描述
观察上述结果反向,有部分车牌图像没有截取完整,故扩大截取范围
改善方案一:

Rect bound=boundingRect(final_aero);
int width_pad = 100;
int height_pad = 20;
bound.x -= width_pad;
bound.y -= height_pad;
bound.width += width_pad*2;
bound.height += height_pad * 2;
Mat only_car_num2 = srcImage(bound);
imwrite(path + "_carNum.jpg", only_car_num2);

在这里插入图片描述
改善方案二: 因为是车牌在横向区域扩展不够,故对max_aero使用更大的膨胀算子,使其完全能覆盖到车牌区域。

//---车牌区域精提取---
//找到最大的连通域
Mat max_aero= findMaxArea2(dilate_open);
//确保最大的连通域能完全覆盖车牌区域
Mat max_aero_dilate, bin_mat_dilate;
morphologyEx(max_aero, max_aero_dilate, MORPH_DILATE,
    getStructuringElement(MORPH_RECT, Size(211, 61)));//----更大的膨胀算子
//对bin_mat进行进行膨胀,使车牌区域完成闭合
morphologyEx(bin_mat, bin_mat_dilate, MORPH_DILATE,
    getStructuringElement(MORPH_RECT, Size(17, 33)));
Mat final_aero,final_mat;
bitwise_and(max_aero_dilate, bin_mat_dilate, final_aero);
srcImage.copyTo(final_mat, final_aero);
Rect bound=boundingRect(final_aero);
Mat only_car_num2 = final_mat(bound);

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_74259636/article/details/128688707