利用OpenCV怎么对车牌的字符进行分割
创始人
2024-11-21 01:41:22
0次

。

检测轮廓进行分割

边缘检测

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

利用OpenCV怎么对车牌的字符进行分割

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

检测轮廓

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

利用OpenCV怎么对车牌的字符进行分割

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

利用OpenCV怎么对车牌的字符进行分割

这样看起来是不是就成功了,然后根据轮廓位置将每个字提取出来就行了,不过在这里每个轮廓的前后顺序不一定是图像中的位置,这里我使用每个轮廓左上角横坐标 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++;
 }
 }

分割出的结果:

利用OpenCV怎么对车牌的字符进行分割

完整代码:

#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;
}

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...