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

先来看一看原图:

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

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

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

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

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

检测轮廓进行分割

边缘检测

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

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

检测轮廓

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

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

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

完整代码:

#include

#include

#include

#include

#include

using namespace std;

using namespace cv;

int main() {

Mat img = imread("number.jpg");

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_BINARY + THRESH_OTSU);

// 边缘检测,提取轮廓

Mat img_canny;

Canny(img_seg, img_canny, 200, 100);

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 < size; i++) {

// 获取边框数据

Rect number_rect = boundingRect(contours[i]);

int width = number_rect.width;

int height = number_rect.height;

// 去除较小的干扰边框,筛选出合适的区域

if (width > 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 < num_order.size(); i++) {

Rect number_rect = boundingRect(contours[num_map.find(num_order[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", 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 = 0; j < roi_row - 1; j++) {

pix = img_threadhold.at(j, i);

pixrow[i] = 0;

if (pix > 0) {

pixrow[i] = 1;

break;

}

}

}

// 对数组进行滤波,减少突变概率

for (int i = 2; i < roi_col - 1 - 2; i++) {

if ((pixrow[i - 1] + pixrow[i - 2] + pixrow[i + 1] + pixrow[i + 2]) >= 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;

}

if (pix == 0 && flage) {

flage = false;

position2[count] = i;

count++;

}

if (i == (roi_col - 2) && flage) {

flage = false;

position2[count] = i;

count++;

}

}

分割出的结果:

完整代码:

#include

#include

#include

#include

using namespace std;

using namespace cv;

int main() {

Mat img = imread("number.jpg");

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 = 0; j < roi_row - 1; j++) {

pix = img_threadhold.at(j, i);

pixrow[i] = 0;

if (pix > 0) {

pixrow[i] = 1;

break;

}

}

}

// 对数组进行滤波,减少突变概率

for (int i = 2; i < roi_col - 1 - 2; i++) {

if ((pixrow[i - 1] + pixrow[i - 2] + pixrow[i + 1] + pixrow[i + 2]) >= 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_col - 1; i++) {

pix = pixrow[i];

if (pix == 1 && !flage) {

flage = true;

position1[count] = i;

continue;

}

if (pix == 0 && flage) {

flage = false;

position2[count] = i;

count++;

}

if (i == (roi_col - 2) && flage) {

flage = false;

position2[count] = i;

count++;

}

}

// 记录所有字符宽度

for (int n = 0; n < count; n++) {

roi_width[n] = position2[n] - position1[n];

}

// 减去最大值、最小值,计算平均值用字符宽度来筛选

int max = roi_width[0], max_index = 0;

int min = roi_width[0], min_index = 0;

for (int n = 1; n < count; n++) {

if (max < roi_width[n]) {

max = roi_width[n];

max_index = n;

}

if (min > 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 < count; i++) {

if (roi_width[i] >(avgre - 8) && roi_width[i] < (avgre + 8)) {

licenseX[licenseNum] = position1[i];

licenseW[licenseNum] = roi_width[i];

licenseNum++;

countX++;

continue;

}

if (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", number_img);

}

waitKey(0);

return 0;

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

java opencv 提取车牌_OpenCV实现车牌字符分割(C++)相关推荐

  1. 蓝底白字车牌的定位与字符分割识别matlab仿真

    目录 1.算法概述 2.仿真效果 3.MATLAB仿真源码 1.算法概述 车牌识别自然环境下,汽车图像背景复杂.光照不均匀,如何在自然背景中准确地确定车牌区域是整个识别过程的关键.首先对采集到的视频图 ...

  2. 分水岭算法java,OpenCV 学习笔记 04 深度估计与分割——GrabCut算法与分水岭算法...

    1 使用普通摄像头进行深度估计 1.1 深度估计原理 这里会用到几何学中的极几何(Epipolar Geometry),它属于立体视觉(stereo vision)几何学,立体视觉是计算机视觉的一个分 ...

  3. 【OpenCV】利用投影法进行字符分割

    1.概述 字符分割有很多方法,但并不是每一种方法是万能的,那么就需要根据自己的需要来分析.例如:我现在项目的需求是将一串编号给切分开来.查了网上的资料和文献,大致适合项目的有两种方法:投影分割法和连通 ...

  4. java opencv 提取车牌_opencv 学习之 车牌提取

    车牌识别分两步,一是车牌提取,而是字符识别. 下面是车牌提取. VS2010. OpenCV249. //载入图像 char * path = "d:\\picture\\06.jpg&qu ...

  5. java opencv 提取车牌_opencv-车牌区域提取

    #include "opencv2/highgui/highgui.hpp" #include "opencv2/features2d/features2d.hpp&qu ...

  6. java opencv 物体检测_OpenCV.物体识别

    1.度娘:"OpenCV 物体识别" ZC:主看这个,讲的比较细致,操作一般都是使用的 OpenCV里面的exe,一些代码是 java的 可以搞定,最后一段测试代码 是Python ...

  7. java opencv 相似度_opencv使用 java 对比两个人脸的相似度

    1.创建Java工程,并导入如下jar包 opencv-440.jar 2.编写代码 import org.opencv.core.*; import org.opencv.imgcodecs.Img ...

  8. java opencv 模板匹配算法_OpenCV探索之路(九):模板匹配

    模板匹配的作用在图像识别领域作用可大了.那什么是模板匹配? 模板匹配,就是在一幅图像中寻找另一幅模板图像最匹配(也就是最相似)的部分的技术. 说的有点抽象,下面给个例子说明就很明白了. 在上面这幅全明 ...

  9. java opencv 调用摄像头_opencv C++实现调用摄像头动态识别人脸

    前言 #include #include #include #include #include #include #include #include #include #include using n ...

  10. python编程胡牌将是什么意思_OpenCV+Python识别车牌和字符分割的实现

    本篇文章主要基于python语言和OpenCV库(cv2)进行车牌区域识别和字符分割,开篇之前针对在python中安装opencv的环境这里不做介绍,可以自行安装配置! 车牌号检测需要大致分为四个部分 ...

最新文章

  1. c#_委托事件的理解
  2. shp设置utf8格式_shp文件格式说明
  3. Java微服务(四)【idea配置本地maven】【中文idea版本】(手把手编写,超级详细)
  4. 通过OpenFoam记录一些c++的trick(持续更新)
  5. 利用Sqoop将MySQL海量测试数据导入HDFS和HBase
  6. html网络,HTML—构建网络
  7. 学习自动驾驶技术 学习之路_一天学习驾驶
  8. ZendStudio 安装
  9. 王海婷也可以编程,厚脸皮谈自己的一些事情
  10. 服务器上找不到iis,Web服务器打开IIS7管理器看不到站点解决方法
  11. PN结的形成及其单向导电性
  12. Qt 语言家实现中英文切换(解决纯代码添加部件的中英文转换问题)
  13. 水木周平戏说中国网络黑幽默
  14. lol服务器维护是拳头,问拳头-英雄联盟官方网站-腾讯游戏
  15. 左神算法基础class6—题目1图的存储与表达
  16. Keil_MDK 中绝对地址定位问题
  17. system和vendor分区挂载解析(Android O)
  18. 聚观早报 | 抖音推出可颂App;马斯克终止收购 Twitter
  19. 基于NFS共享存储实现kvm虚拟主机动态迁移
  20. 服务器性能计时器如何关闭,如何在游戏关闭时继续倒数计时器?

热门文章

  1. 微信养号防封攻略_防封群微信怎么卖
  2. cf鼠标宏数据大全_鼠标6个率?采样率、分辨率、回报率、轮询率、刷新率、采样频率...
  3. Mac终端命令失效( command not found)/
  4. Unity 两个UI(坐标)之间的连线(直线)。如连线题
  5. a59s刷机包卡刷 oppo_OPPO A59s 5.1 ROM刷机包 ColorOS 精简卡刷包 ROOT权限
  6. Prelude CC 2019 for Mac附激活补丁 v8.1.0中文版
  7. atmega328p引脚图_ATMEGA328P-AU 8位AVR微控制器
  8. redis 菜鸟教程中脚本笔记
  9. 数值计算求解动态热传导方程
  10. 联想ThinkPad E420安装7450M的显卡驱动后进入不了系统,试了4个系统都不..