• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

OpenCV实现车牌字符分割(C++)

c++ 搞代码 4年前 (2022-01-06) 35次浏览 已收录 0个评论

这篇文章主要为大家详细介绍了OpenCV实现车牌字符分割,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

之前的车牌定位中已经获取到了车牌的位置,并且对车牌进行了提取。我们最终的目的是进行车牌识别,在这之前需要将字符进行分割,方便对每一个字符进行识别,最后将其拼接后便是完整的车牌号码。关于车牌定位可以看这篇文章: OpenCV车牌定位(C++) ,本文使用的图片也是来自这里。

先来看一看原图:

最左边的汉字本来是 ,截取时只获得了右边一点点的部分,这与原图和获取方法都有关,对于 川、沪… 这一类左右分开的字会经常发生这类问题,对方法进行优化后可以解决,这里暂时不进行讨论。

后面的字都是完整的,字符分割的过程不会受影响。首先来一波常规操作,为了更方便处理,将其变成灰度图片:

分割的方法不止一种,最简单的就是多加点人工成分,按照大致宽度再微调进行截取,但是这样看似最快其实成本最高,只适用于单一的图片,因此这种容错低且不够自动的方法就不考虑了。

目前我使用了两种不同的方法,一种是进行边缘检测再检测轮廓,根据字符的轮廓特点筛选出字符;另一种就是像素值判断,主要根据像素数量使用水平映射截取宽度,垂直映射因为高度基本一致就不需要了,方法于水平映射一样。

两种方法我都写在后面,根据需要自行复制。如果要使用像素值进行判断的话,就需要再将灰度图转换成二值化图片,使用阈值分割就行了。若使用第一种用轮廓分割的方法,灰度图和二值化图片都可以,结果没什么区别。

检测轮廓进行分割

边缘检测

对图像进行边缘检测,这里采用的是 Canny 边缘检测,处理后的结果如下:

可以看到每个字的边缘都被描绘出来了,接下来就将每个字的轮廓获取出来。

检测轮廓

直接使用 findContours() 将所有轮廓提取出来,再将其在原图中画出来看看

可以看到不仅仅是每个字被框出来了,还有内部以及图像中表现特殊部分的轮廓也有,接下来我们就根据每个字的大致大小筛选出我们想要的结果:

这样看起来是不是就成功了,然后根据轮廓位置将每个字提取出来就行了,不过在这里每个轮廓的前后顺序不一定是图像中的位置,这里我使用每个轮廓左上角横坐标 x 的大小来排序。

完整代码:

 #include  #include  #include  #include  #include <map> using namespace std; using namespace cv; int main() { Mat img = imread("number.jpg-600"); Mat gray_img; // 生成灰度图像 cvtColor(img, gray_img, CV_BGR2GRAY); // 高斯模糊 Mat img_gau; GaussianBlur(gray_img, img_gau, Size(3, 3), 0, 0); // 阈值分割 Mat img_seg; threshold(img_gau, img_seg, 0, 255, THRESH_B<p style="color:transparent">来源gao!daima.com搞$代!码网</p>INARY + THRESH_OTSU); // 边缘检测,提取轮廓 Mat img_canny; Canny(img_seg, img_canny, 200, 100); vector<vector> contours; vector hierarchy; findContours(img_canny, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, Point()); int size = (int)(contours.size()); // 保存符号边框的序号 vector num_order; map num_map; for (int i = 0; i  img.cols/10 && height > img.rows/2) { rectangle(img_seg, number_rect.tl(), number_rect.br(), Scalar(255, 255, 255), 1, 1, 0); num_order.push_back(number_rect.x); num_map[number_rect.x] = i; } } // 按符号顺序提取 sort(num_order.begin(), num_order.end()); for (int i = 0; i second]); Rect choose_rect(number_rect.x, 0, number_rect.width, gray_img.rows); Mat number_img = gray_img(choose_rect); imshow("number" + to_string(i), number_img); // imwrite("number" + to_string(i) + ".jpg-600", number_img); } imshow("添加方框", gray_img); waitKey(0); return 0; }

像素值判断进行分割

分割方法:首先判断每一列的像素值大于 0 的像素个数超过5个时,认为此列是有数字的,记录每列像素是否大于 5,产生一个数组。

 // 确认为 1 的像素 int pixrow[1000]; for (int i = 0; i <roi_col - 1; i++) { for (int j < roi_row j++) pix=img_threadhold.at(j, i); pixrow[i] = 0; if (pix > 0) { pixrow[i] = 1; break; } } } // 对数组进行滤波,减少突变概率 for (int i = 2; i = 3) { pixrow[i] = 1; } else if ((pixrow[i - 1] + pixrow[i - 2] + pixrow[i + 1] + pixrow[i + 2]) <= 1) { pixrow[i] = 0; } }

之后记录像素为 0 和 1 所连续的长度来计算字符的宽度,最后用宽度的大小来筛选字符。

 // 确认字符位置 int count = 0; bool flage = false; for (int i = 0; i <roi_col - 1; i++) { pix=pixrow[i]; if (pix== 1 && !flage) flage=true; position1[count]=i; continue; } 0 flage) false; position2[count]=i; count++; (i== (roi_col 2) i; }<pre></div><p>分割出的结果:</p><p style="text-align: center"></p><p>完整代码:</p><div class="gaodaimacode"><pre class="prettyprint linenums"> #include  #include  #include  #include  using namespace std; using namespace cv; int main() { Mat img = imread("number.jpg-600"); Mat gray_img; // 生成灰度图像 cvtColor(img, gray_img, CV_BGR2GRAY); // 高斯模糊 Mat img_gau; GaussianBlur(gray_img, img_gau, Size(3, 3), 0, 0); // 阈值分割 Mat img_threadhold; threshold(img_gau, img_threadhold, 0, 255, THRESH_BINARY + THRESH_OTSU); // 判断字符水平位置 int roi_col = img_threadhold.cols, roi_row = img_threadhold.rows, position1[50], position2[50], roi_width[50]; uchar pix; // 确认为 1 的像素 int pixrow[1000]; for (int i = 0; i <roi_col - 1; i++) { for (int j < roi_row j++) pix=img_threadhold.at(j, i); pixrow[i] = 0; if (pix > 0) { pixrow[i] = 1; break; } } } // 对数组进行滤波,减少突变概率 for (int i = 2; i = 3) { pixrow[i] = 1; } else if ((pixrow[i - 1] + pixrow[i - 2] + pixrow[i + 1] + pixrow[i + 2]) <= 1) { pixrow[i] = 0; } } // 确认字符位置 int count = 0; bool flage = false; for (int i = 0; i  roi_width[n]) { min = roi_width[n]; min_index = n; } } int index = 0; int new_roi_width[50]; for (int i = 0; i <count; i++) { if (i == min_index || i == max_index) {} else { new_roi_width[index] = roi_width[i]; index++; } } // 取后面三个值的平均值 int avgre = (int)((new_roi_width[count - 3] + new_roi_width[count - 4] + new_roi_width[count - 5]) / 3.0); // 字母位置信息确认,用宽度来筛选 int licenseX[10], licenseW[10], licenseNum = 0; int countX = 0; for (int i = 0; i (avgre - 8) && roi_width[i]  (avgre * 2 - 10) && roi_width[i] <(avgre * 2 + 10)) { licenseX[licenseNum] = position1[i]; licenseW[licenseNum] = roi_width[i]; licenseNum++; } } // 截取字符 Mat number_img = Mat(Scalar(0)); for (int i = 0; i <countX; i++) { Rect choose_rect(licenseX[i], 0, licenseW[i], gray_img.rows); number_img = gray_img(choose_rect); imshow("number" + to_string(i), number_img); // imwrite("number" + to_string(i) + ".jpg-600", number_img); } waitKey(0); return 0; }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持gaodaima搞代码网

以上就是OpenCV实现车牌字符分割(C++)的详细内容,更多请关注gaodaima搞代码网其它相关文章!


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:OpenCV实现车牌字符分割(C++)

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址