Bootstrap

[opencv][C++][车牌识别][切除车牌边框和钉子]车牌识别之分割字符,使用手机相机拍的图验证代码

此图只用于学习本文代码。
在这里插入图片描述
输出如下:

在这里插入图片描述

输出如下:
在这里插入图片描述

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

;