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

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

先来看一看原图:

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

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

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

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

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

检测轮廓进行分割

边缘检测

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

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

检测轮廓

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

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

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

完整代码:

#include <iostream>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgproc/types_c.h>
#include <map>

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<vector<Point>> contours;
 vector<Vec4i> hierarchy;
 findContours(img_canny, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, Point());
 int size = (int)(contours.size());
 // 保存符号边框的序号
 vector<int> num_order;
 map<int, int> 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<uchar>(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 <iostream>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgproc/types_c.h>

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<uchar>(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;
}

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

(0)

相关推荐

  • python+OpenCV实现车牌号码识别

    基于python+OpenCV的车牌号码识别,供大家参考,具体内容如下 车牌识别行业已具备一定的市场规模,在电子警察.公路卡口.停车场.商业管理.汽修服务等领域已取得了部分应用.一个典型的车辆牌照识别系统一般包括以下4个部分:车辆图像获取.车牌定位.车牌字符分割和车牌字符识别 1.车牌定位的主要工作是从获取的车辆图像中找到汽车牌照所在位置,并把车牌从该区域中准确地分割出来 这里所采用的是利用车牌的颜色(黄色.蓝色.绿色) 来进行定位 #定位车牌 def color_position(img,ou

  • OpenCV实现车牌定位(C++)

    最近开始接触 C++ 了,就拿一个 OpenCV 小项目来练练手.在车牌自动识别系统中,从汽车图像的获取到车牌字符处理是一个复杂的过程,本文就以一个简单的方法来处理车牌定位. 我国的汽车牌照一般由七个字符和一个点组成,车牌字符的高度和宽度是固定的,分别为90mm和45mm,七个字符之间的距离也是固定的12mm,点分割符的直径是10mm. 使用的图片是从百度上随便找的(侵删),展示一下原图和灰度图: #include <iostream> #include <opencv2/highgui

  • OpenCV+Python识别车牌和字符分割的实现

    本篇文章主要基于python语言和OpenCV库(cv2)进行车牌区域识别和字符分割,开篇之前针对在python中安装opencv的环境这里不做介绍,可以自行安装配置! 车牌号检测需要大致分为四个部分: 1.车辆图像获取 2.车牌定位. 3.车牌字符分割 4.车牌字符识别 具体介绍 车牌定位需要用到的是图片二值化为黑白后进canny边缘检测后多次进行开运算与闭运算用于消除小块的区域,保留大块的区域,后用cv2.rectangle选取矩形框,从而定位车牌位置 车牌字符的分割前需要准备的是只保留车牌

  • android端使用openCV实现车牌检测

    现在,汽车的踪影无处不在,公路上疾驰,大街边临停,小区中停靠,车库里停泊.管理监控如此庞大数量的汽车是个头疼的问题.精明的人们把目光放在车牌上,因为车牌是汽车的"身份证".所以车牌识别成为了焦点,而车牌检测是车牌识别的基础和前提.本篇文章,主要讨论使用openCV实现车牌检测. openCV是开源计算机视觉库,基于计算机视觉与机器学习,提供强大的图像处理能力.我们可以快速集成openCV库到android端,其中一种方式是直接安装openCV Manager,按需使用:启动服务去动态加

  • python+opencv实现车牌定位功能(实例代码)

    写在前面 HIT大三上学期视听觉信号处理课程中视觉部分的实验三,经过和学长们实验的对比发现每一级实验要求都不一样,因此这里标明了是2019年秋季学期的视觉实验三. 由于时间紧张,代码没有进行任何优化,实验算法仅供参考. 实验要求 对给定的车牌进行车牌识别 实验代码 代码首先贴在这里,仅供参考 源代码 实验代码如下: import cv2 import numpy as np def lpr(filename): img = cv2.imread(filename) # 预处理,包括灰度处理,高斯

  • Python+OpenCV实现车牌字符分割和识别

    最近做一个车牌识别项目,入门级别的,十分简单. 车牌识别总体分成两个大的步骤: 一.车牌定位:从照片中圈出车牌 二.车牌字符识别 这里只说第二个步骤,字符识别包括两个步骤: 1.图像处理 原本的图像每个像素点都是RGB定义的,或者称为有R/G/B三个通道.在这种情况下,很难区分谁是背景,谁是字符,所以需要对图像进行一些处理,把每个RGB定义的像素点都转化成一个bit位(即0-1代码),具体方法如下: ①将图片灰度化 名字拗口,但是意思很好理解,就是把每个像素的RGB都变成灰色的RGB值,而灰色的

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

    之前的车牌定位中已经获取到了车牌的位置,并且对车牌进行了提取.我们最终的目的是进行车牌识别,在这之前需要将字符进行分割,方便对每一个字符进行识别,最后将其拼接后便是完整的车牌号码.关于车牌定位可以看这篇文章: OpenCV车牌定位(C++),本文使用的图片也是来自这里. 先来看一看原图: 最左边的汉字本来是 沪,截取时只获得了右边一点点的部分,这与原图和获取方法都有关,对于 川.沪- 这一类左右分开的字会经常发生这类问题,对方法进行优化后可以解决,这里暂时不进行讨论. 后面的字都是完整的,字符分

  • opencv实现车牌识别

    本文实例为大家分享了opencv实现车牌识别的具体代码,供大家参考,具体内容如下 (1)提取车牌位置,将车牌从图中分割出来:(2)车牌字符的分割:(3)通过模版匹配识别字符:(4)将结果绘制在图片上显示出来. import cv2 from matplotlib import pyplot as plt import os import numpy as np # plt显示彩色图片 def plt_show0(img):     # cv2与plt的图像通道不同:cv2为[b,g,r];plt

  • Python中OpenCV实现简单车牌字符切割

    在Jupyter Notebook上使用Python+opencv实现如下简单车牌字符切割.关于opencv库的安装可以参考:Python下opencv库的安装过程与一些问题汇总. 1.实现代码 import cv2 import numpy as np import matplotlib.pyplot as plt from PIL import Image #读取原图片 image1=cv2.imread("123456.jpg") cv2.imshow("image1&

  • python中超简单的字符分割算法记录(车牌识别、仪表识别等)

    背景 在诸如车牌识别,数字仪表识别等问题中,最关键的就是将单个的字符分割开来再分别进行识别,如下图.最近刚好用到,就自己写了一个简单地算法进行字符分割,来记录一下. 图像预处理 彩图二值化以减小参数量,再进行腐蚀膨胀去除噪点. image = cv2.imread('F://demo.jpg', 0) # 读取为灰度图 _, image = cv2.threshold(image, 50, 255, cv2.THRESH_BINARY) # 二值化 kernel1 = cv2.getStruct

  • Opencv创建车牌图片识别系统方法详解

    目录 前言 包含功能 软件版本 软件架构 参考文档 效果图展示 车牌检测过程 图片车牌文字识别过程 部分核心代码 前言 这是一个基于spring boot + maven + opencv 实现的图像识别及训练的Demo项目 包含车牌识别.人脸识别等功能,贯穿样本处理.模型训练.图像处理.对象检测.对象识别等技术点 java语言的深度学习项目,在整个开源社区来说都相对较少: 拥有完整的训练过程.检测.识别过程的开源项目更是少之又少!! 包含功能 蓝.绿.黄车牌检测及车牌号码识别 网上常见的轮廓提

  • Java中将String类型依照某个字符分割成数组的方法

    将下列字符串,依照|拆分成数组: String numbers = "1|2|3|4|5"; 使用split拆分 String[] numberArray = numbers.split("\\| "); 以上这篇Java中将String类型依照某个字符分割成数组的方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们. 您可能感兴趣的文章: java 字符串分割的三种方法(总结) Java中分割字符串的两种方法实例详解 java Stri

随机推荐