此图只用于学习本文代码。
输出如下:
输出如下:
#include <iostream>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgproc/types_c.h>
#include <cmath>
#include <cstdlib>
using namespace std;
using namespace cv;
/*
I:原图,完整车牌
O:二值图,完整车牌
*/
int silmpleImgProcess(const cv::Mat& inputArray, cv::Mat& outputArray);
/*
I:二值图,完整车牌
O:二值图,被切掉边框的车牌
*/
int preCutAdjust(const cv::Mat& inputArray, cv::Mat& outputArray);
/*
I:二值图,被切掉边框的车牌
O:二值图,切出的字符拼成的车牌
*/
int cut(const cv::Mat& inputArray, cv::Mat& outputArray);
/*
I:二值图,被切掉边框的车牌
O:二值图,经过连通区域面积筛选后得到一张过滤网,bitwise_and实现(被切掉边框的车牌&过滤网)与操作。
*/
int findArea(const Mat inputArray, Mat& outputArray);
static int imfill(const Mat inputArray, Mat& outputArray);
static int cmpfunc(const void* a, const void* b);
static int delUpCol(cv::Mat& outputArray);
static int delDownCol(cv::Mat& outputArray);
static int delLeftRow(cv::Mat& outputArray);
static int delRightRow(cv::Mat& outputArray);
static int delRectRivet(const cv::Mat& inputArray, cv::Mat& outputArray);
int main()
{
Mat img = imread("plate.jpg");
Mat sip;
silmpleImgProcess(img, sip);
Mat pca;
preCutAdjust(sip, pca);
Mat Area;
findArea(pca, Area);
Mat out;
cut(Area, out);
waitKey(0);
return 0;
}
int imfill(const Mat inputArray, Mat& outputArray)
{
Size m_Size = inputArray.size();
Mat temimage = Mat::zeros(m_Size.height + 2, m_Size.width + 2, inputArray.type());
inputArray.copyTo(temimage(Range(1, m_Size.height + 1), Range(1, m_Size.width + 1)));
floodFill(temimage, Point(0, 0), Scalar(255));
Mat cutImg;
temimage(Range(1, m_Size.height + 1), Range(1, m_Size.width + 1)).copyTo(cutImg);
outputArray = inputArray | (~cutImg);
return 0;
}
//使用了张王李刘赵孙杨的文章中的代码https://blog.csdn.net/xuyangcao123/article/details/81023732
int findArea(const Mat inputArray, Mat& outputArray)
{
Mat andArray;
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(inputArray.clone(), contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
double max_area = 0;
int index = 0;
const int srcArea = inputArray.rows * inputArray.cols;
const int srcAreaD512 = srcArea >> 9;
andArray = Mat::zeros(inputArray.rows, inputArray.cols, inputArray.type());
for (int i = 0; i < contours.size(); i++)
{
if (contourArea(contours[i]) > srcAreaD512)
{
drawContours(andArray, contours, i, Scalar(255));
imfill(andArray, andArray);
}
}
//imshow("andArray", andArray);
//void bitwise_and(InputArray src1, InputArray src2, OutputArray dst, InputArray mask = noArray());
bitwise_and(inputArray, andArray, outputArray);
//imshow("outputArray", outputArray);
return 0;
}
int cmpfunc(const void* a, const void* b)
{
return (*(int*)a - *(int*)b);
}
int dot2line(const cv::Mat& inputArray, const int (&pixArr)[1000],
int &count,int (&posStart)[50], int (&posEnd)[50], int(&width)[50], int(&height)[50],int &sum)
{
int col = inputArray.cols;
int row = inputArray.rows;
uchar pix;
bool flage = false;
for (int i = 0; i < col - 1; i++)
{
pix = pixArr[i];
if (pix == 1 && !flage)
{
flage = true;
posStart[count] = i;
continue;
}
if (pix == 0 && flage)
{
flage = false;
posEnd[count] = i;
count++;
}
if (i == (col - 2) && flage)
{
flage = false;
posEnd[count] = i;
count++;
}
}
// 记录所有字符宽度
sum = 0;
for (int i = 0; i < count; i++)
{
width[i] = posEnd[i] - posStart[i];
sum += width[i];
}
//求出投影高度
for (int k = 0; k < count; k++)
{
int cnt = 0;
for (int i = 0; i < row - 1; i++)
{
for (int j = posStart[k]; j < posEnd[k]; j++)
{
pix = inputArray.at<uchar>(i, j);
if (pix > 0)
{
cnt++;
break;
}
}
}
height[k] = cnt;
}
return 0;
}
//去除上边框
int delUpCol(cv::Mat& outputArray)
{
int roi_col = outputArray.cols;
int roi_row = outputArray.rows;
uchar pix;
int planePixCnt = 0;
int pixPlane[1000] = { 0 };//int pixPlane[img_delRR.cols];
int posStart[50] = { 0 };
int posEnd[50] = { 0 };
int roi_width[50] = { 0 };
int roi_height[50] = { 0 };
int count = 0;
int sum = 0;
//产生投影数组
for (int i = 0; i < roi_col - 1; i++)
{
for (int j = 0; j < roi_row - 1; j++)
{
pix = outputArray.at<uchar>(j, i);
pixPlane[i] = 0;
if (pix > 0)
{
pixPlane[i] = 1;//投影
planePixCnt++;
break;
}
}
}
//将连续的投影的起始终止坐标以及宽度记录到数组
dot2line(outputArray,pixPlane, count, posStart, posEnd, roi_width, roi_height, sum);
qsort(roi_width, count, sizeof(int), cmpfunc);
int max_width = roi_width[count - 1];
//
//有效性判断
bool validFlag = false;
uchar validCnt = 0;
const int widthD10 = roi_col / 10;
const int heightD3M2 = (roi_row << 1) / 3;
for (int i = count - 1; i >= 0; i--)
{
if (roi_width[i] >= widthD10 || roi_height[i] >= heightD3M2)
{
validCnt++;
}
}
if (validCnt >= 7)
{
validFlag = true;
}
/
//边缘
int planePixCnt_border = 0;
int pixPlane_border[1000] = { 0 };
int posStart_border[50] = { 0 };
int posEnd_border[50] = { 0 };
int roi_width_border[50] = { 0 };
int roi_height_border[50] = { 0 };
int count_border = 0;
int sum_border = 0;
for (int i = 0; i < roi_col - 1; i++)
{
pix = outputArray.at<uchar>(0, i);
if (pix > 0)
{
pixPlane_border[i] = 1;
planePixCnt_border++;
}
}
dot2line(outputArray,pixPlane_border, count_border, posStart_border, posEnd_border, roi_width_border,roi_height_border, sum_border);
//短投影判断 && 短跳变判断
//产生投影数组
bool shortFlag = false;
const int shortCmp = roi_col >> 3;
for (int i = 0; i < roi_row - 1; i++)
{
int avrgCnt=0;
for (int j = 0; j < roi_col - 1; j++)
{
pix = outputArray.at<uchar>(i, j);
if (pix > 0)
{
avrgCnt++;
}
}
if (avrgCnt <= shortCmp)
{
shortFlag = true;
break;
}
}
///
const int up = (roi_col >> 3) * 7;
const int halfCol = roi_col >> 1;
if (false == validFlag || count < 7// || planePixCnt >= up || sum < halfCol || max_width*7 > roi_col
|| count_border < 7
|| true == shortFlag)
{
cv::Rect m_select;
m_select = Rect(0, 1, outputArray.cols, outputArray.rows - 1);
Mat ROI = outputArray(m_select);
outputArray = ROI;
//imshow("outputArray", outputArray);
//waitKey(0);
delUpCol(outputArray);
}
/
//waitKey(0);
return 0;
}
//去除下边框
int delDownCol(cv::Mat& outputArray)
{
int roi_col = outputArray.cols;
int roi_row = outputArray.rows;
uchar pix;
int planePixCnt = 0;
int pixPlane[1000] = { 0 };//int pixPlane[img_delRR.cols];
int posStart[50] = { 0 };
int posEnd[50] = { 0 };
int roi_width[50] = { 0 };
int roi_height[50] = { 0 };
int count = 0;
int sum = 0;
//产生投影数组
for (int i = 0; i < roi_col - 1; i++)
{
for (int j = 0; j < roi_row - 1; j++)
{
pix = outputArray.at<uchar>(j, i);
pixPlane[i] = 0;
if (pix > 0)
{
pixPlane[i] = 1;//投影
planePixCnt++;
break;
}
}
}
//将连续的投影的起始终止坐标以及宽度记录到数组
dot2line(outputArray, pixPlane, count, posStart, posEnd, roi_width, roi_height, sum);
qsort(roi_width, count, sizeof(int), cmpfunc);
int max_width = roi_width[count - 1];
//
//有效性判断
uchar validFlag = false;
uchar validCnt = 0;
const int widthD10 = roi_col / 10;
const int heightD3M2 = (roi_row << 1) / 3;
for (int i = count - 1; i >= 0; i--)
{
if (roi_width[i] >= widthD10 || roi_height[i] >= heightD3M2)
{
validCnt++;
}
}
if (validCnt >= 7)
{
validFlag = true;
}
/
//边缘
int planePixCnt_border = 0;
int pixPlane_border[1000] = { 0 };
int posStart_border[50] = { 0 };
int posEnd_border[50] = { 0 };
int roi_width_border[50] = { 0 };
int roi_height_border[50] = { 0 };
int count_border = 0;
int sum_border = 0;
for (int i = 0; i < roi_col - 1; i++)
{
pix = outputArray.at<uchar>(roi_row-1, i);
if (pix > 0)
{
pixPlane_border[i] = 1;
planePixCnt_border++;
}
}
dot2line(outputArray, pixPlane_border, count_border, posStart_border, posEnd_border, roi_width_border, roi_height_border, sum_border);
//短投影判断 && 短跳变判断
//产生投影数组
bool shortFlag = false;
const int shortCmp = roi_col >> 3;
for (int i = roi_row - 1; i >= 0 ; i--)
{
int avrgCnt = 0;
for (int j = 0; j < roi_col - 1; j++)
{
pix = outputArray.at<uchar>(i, j);
if (pix > 0)
{
avrgCnt++;
}
}
if (avrgCnt <= shortCmp)
{
shortFlag = true;
break;
}
}
///
const int up = (roi_col >> 3) * 7;
const int halfCol = roi_col >> 1;
if (false == validFlag|| count < 7// || planePixCnt >= up || sum < halfCol || max_width*7 > roi_col
|| count_border < 7
|| true == shortFlag)
{
cv::Rect m_select;
m_select = Rect(0, 0, outputArray.cols, outputArray.rows - 1);
Mat ROI = outputArray(m_select);
outputArray = ROI;
delDownCol(outputArray);
}
/
//waitKey(0);
return 0;
}
//去除左边框
int delLeftRow(cv::Mat& outputArray)
{
int roi_col = outputArray.cols;
int roi_row = outputArray.rows;
uchar pix;
int cnt = 0;
for (int i = 0; i < roi_row - 1; i++)
{
for (int j = 0; j < roi_col - 1; j++)
{
pix = outputArray.at<uchar>(i, j);
if (pix > 0)
{
cnt++;
break;
}
}
}
int up = (roi_row >> 3) * 7;
if (cnt >= up)
{
cv::Rect m_select;
m_select = Rect(1, 0, outputArray.cols - 1, outputArray.rows);
Mat ROI = outputArray(m_select);
outputArray = ROI;
delLeftRow(outputArray);
}
return 0;
}
//去除右边框
int delRightRow(cv::Mat& outputArray)
{
int roi_col = outputArray.cols;
int roi_row = outputArray.rows;
uchar pix;
int cnt = 0;
for (int i = roi_row - 1; i > 0; i--)
{
for (int j = 0; j < roi_col - 1; j++)
{
pix = outputArray.at<uchar>(i, j);
if (pix > 0)
{
cnt++;
break;
}
}
}
int up = (roi_row >> 3) * 7;
if (cnt >= up)
{
cv::Rect m_select;
m_select = Rect(0, 0, outputArray.cols - 1, outputArray.rows);
Mat ROI = outputArray(m_select);
outputArray = ROI;
delRightRow(outputArray);
}
return 0;
}
int delRectRivet(const cv::Mat& inputArray, cv::Mat& outputArray)
{
outputArray = inputArray;
cv::Rect m_select;
m_select = Rect(0, 0, outputArray.cols, outputArray.rows >> 1);
Mat UROI = outputArray(m_select);
m_select = Rect(0, outputArray.rows >> 1, outputArray.cols, outputArray.rows - (outputArray.rows >> 1));
Mat DROI = outputArray(m_select);
delUpCol(UROI);
//imshow("UROI", UROI);
delDownCol(DROI);
//imshow("DROI", DROI);
//delUpRivet(UROI);
//delDownRivet(DROI);
Mat UD;
vconcat(UROI, DROI, UD);
outputArray = UD;
//imshow("UD", UD);
//
//m_select = Rect(0, 0, outputArray.cols >> 1, outputArray.rows);
//Mat LROI = outputArray(m_select);
//m_select = Rect(outputArray.cols >> 1, 0, outputArray.cols - (outputArray.cols >> 1), outputArray.rows);
//Mat RROI = outputArray(m_select);
//delLeftRow(LROI);
//delRightRow(RROI);
//Mat LR;
//hconcat(LROI, RROI, LR);
//outputArray = LR;
return 0;
}
int silmpleImgProcess(const cv::Mat& inputArray, cv::Mat& outputArray)
{
outputArray = inputArray;
Mat img = outputArray;
Mat gray_img;
// 生成灰度图像
cvtColor(img, gray_img, CV_BGR2GRAY);
//imshow("gray_img", gray_img);
// 高斯模糊
Mat img_gau;
GaussianBlur(gray_img, img_gau, Size(3, 3), 0, 0);
//imshow("img_gau", img_gau);
// 阈值分割
Mat img_threadhold;
threshold(img_gau, img_threadhold, 0, 255, THRESH_BINARY + THRESH_OTSU);
//imshow("img_threadhold", img_threadhold);
outputArray = img_threadhold;
return 0;
}
int preCutAdjust(const cv::Mat& inputArray, cv::Mat& outputArray)
{
outputArray = inputArray;
Mat img_delRR;
delRectRivet(outputArray, img_delRR);
outputArray = img_delRR;
//imshow("img_delRR", img_delRR);
//waitKey(0);
//return 0;
return 0;
}
int cut(const cv::Mat& inputArray, cv::Mat& outputArray)
{
outputArray = inputArray;
//切割边框&铆钉
int roi_col = outputArray.cols;
int roi_row = outputArray.rows;
int count = 0;
bool flage = false;
int sum = 0;
int posStart[50] = { 0 };
int posEnd[50] = { 0 };
int roi_width[50] = { 0 };
int roi_height[50] = { 0 };
int roi_width_cpy[50] = { 0 };
int roi_height_cpy[50] = { 0 };
uchar pix = 0;
int pixPlane[1000] = { 0 };//int pixPlane[outputArray.cols];
//产生投影数组
for (int i = 0; i < roi_col - 1; i++)
{
for (int j = 0; j < roi_row - 1; j++)
{
pix = outputArray.at<uchar>(j, i);
pixPlane[i] = 0;
if (pix > 0)
{
pixPlane[i] = 1;//投影
break;
}
}
}
if (1)
{
dot2line(outputArray, pixPlane, count, posStart, posEnd, roi_width, roi_height, sum);
}
else
{
//将连续的投影的起始终止坐标以及宽度记录到数组
for (int i = 0; i < roi_col - 1; i++)
{
pix = pixPlane[i];
if (pix == 1 && !flage)
{
flage = true;
posStart[count] = i;
continue;
}
if (pix == 0 && flage)
{
flage = false;
posEnd[count] = i;
count++;
}
if (i == (roi_col - 2) && flage)
{
flage = false;
posEnd[count] = i;
count++;
}
}
// 记录所有字符宽度
for (int n = 0; n < count; n++)
{
roi_width[n] = posEnd[n] - posStart[n];
}
//求出投影高度
for (int k = 0; k < count; k++)
{
int cnt = 0;
for (int i = 0; i < roi_row - 1; i++)
{
for (int j = posStart[k]; j < posEnd[k]; j++)
{
pix = outputArray.at<uchar>(i, j);
if (pix > 0)
{
cnt++;
break;
}
}
}
roi_height[k] = cnt;
}
}
//
//求出投影宽度的中位数
memcpy(roi_width_cpy, roi_width, 50 * sizeof(int));
qsort(roi_width_cpy, count, sizeof(int), cmpfunc);
int median_width = roi_width_cpy[count >> 1];
//求出投影高度的中位数
memcpy(roi_height_cpy, roi_height, 50 * sizeof(int));
qsort(roi_height_cpy, count, sizeof(int), cmpfunc);
int median_height = roi_height_cpy[count >> 1];
// 截取字符
int outCnt = 0;
Mat tmp, out;
Mat number_img;
//const int cmp_col = roi_col >> 3;//误差范围,自己定就行
const int cmp_row = (roi_row * 3) >> 2;//误差范围,自己定就行
for (int i = 0; i < count; i++)
{
//const int val_col = abs(roi_width[i] - median_width);
//const int val_row = abs(roi_height[i] - median_height);
//if (val_col < cmp_col && val_row < cmp_row)
if (roi_height[i] > cmp_row)
{
Rect choose_rect(posStart[i], 0, roi_width[i], outputArray.rows);
number_img = outputArray(choose_rect);
imshow("number_img" + to_string(i), number_img);
此处不了解怎么写比较好,干脆写成这样把,反正没多少个循环
//if (0 == outCnt)
//{
// tmp = number_img;
//}
//else
//{
// hconcat(tmp, number_img, out);
// tmp = out;
//}
outCnt++;
}
}
// imshow("out", out);
if (7 == outCnt)
{
cout << "stand" << endl;
}
else
{
cout << "outCnt == " << outCnt << " non-stand!" << endl;
}
//waitKey(0);
return 0;
}