身份證識別(二)——使用OpenCV得到號碼區域

前言

1.前面項目我寫了如何檢測到手持身份證的正面、反面、頭像,那接下要試的是用OpenCV去等到身份證號碼的區域。
2.我這裏用到的OpenCV的版本是3.30,IDE是Qt和VS2015。
3.這個代碼好多地方是借鑑了車牌識別那個開源項目。

代碼

1.把傳入的圖像分離成只有R通道的圖像

//獲取R通道
//傳入一個剪切好的身份證,返回一個只有R這個通道的圖像
void getRChannel(const Mat &src, Mat &dst)
{
	//容器大小爲通道數3
	vector<Mat> split_BGR(src.channels());

	//通道分離
	split(src, split_BGR);
	if (src.cols > 700 | src.cols >600)
	{
		Mat resizeR(450, 600, CV_8UC1);

		cv::resize(split_BGR[2], resizeR, resizeR.size());

		dst = resizeR.clone();
	}
	else
	{
		dst = split_BGR[2].clone();
	}
}

運行結果:
在這裏插入圖片描述
2.得到號碼區域

//傳入一個單通道的圖像,得到一個旋轉矩形的號碼區域
void posDetect(const Mat &src, vector<RotatedRect> & rects)
{
	Mat threshold_R;

	//二值化
	OstuBeresenThreshold(src, threshold_R);

#ifdef DEBUG
	imshow("二值化", threshold_R);
#endif

	//新建一個全白的圖像
	Mat reversal_img(src.size(), src.type(), cv::Scalar(255));

	//相減得到反轉的圖像
	Mat threshold_reversal = reversal_img - threshold_R;

#ifdef DEBUG
	imshow("反轉黑白", threshold_reversal);
#endif

	//形態學閉操作的結構元素
	Mat element = getStructuringElement(MORPH_RECT, Size(15, 3));

	//閉運算
	morphologyEx(threshold_reversal, threshold_reversal, CV_MOP_CLOSE, element);

#ifdef DEBUG	
	imshow("閉操作", threshold_reversal);
#endif

	vector< vector <Point> > contours;

	//輪廓檢測
	findContours(threshold_reversal, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
	
	//對得到的輪廓進行進一步篩選
	vector< vector <Point> > ::iterator itc = contours.begin();

	while (itc != contours.end())
	{
		//返回每個輪廓的最小有界矩形區域
		RotatedRect mr = minAreaRect(Mat(*itc)); 
							 
		//判斷矩形輪廓是否符合要求
		if (!isNumber(mr)) 
		{
			//刪除
			itc = contours.erase(itc);
		}
		else
		{
			rects.push_back(mr);
			++itc;
		}
	}

#ifdef DEBUG
	//測試是否找到了號碼區域
	Mat result;
	result = src.clone();

	Point2f vertices[4];
	for (int j = 0; j < rects.size(); j++)
	{
		rects[j].points(vertices);
		for (int i = 0; i < 4; i++)
		{
			//畫線
			line(result, vertices[i], vertices[(i + 1) % 4], Scalar(0, 0, 0));
		}
		imshow("號碼區域", result);
	}
#endif

}

//判斷是否爲數字區域(這個函數從車牌識別引用而來)
bool isNumber(const RotatedRect &candidate)
{
	float error = 0.2;
	//長寬比
	const float aspect = 4.5 / 0.3;
	//最小區域
	int min = 10 * aspect * 10; 
	//最大區域
	int max = 50 * aspect * 50; 
	//考慮誤差後的最小長寬比
	float rmin = aspect - aspect*error; 
	//考慮誤差後的最大長寬比
	float rmax = aspect + aspect*error; 

	int area = candidate.size.height * candidate.size.width;
	float r = (float)candidate.size.width / (float)candidate.size.height;
	if (r < 1)
	{
		r = 1 / r;
	}
		
	//滿足該條件才認爲該號碼區域
	if ((area < min || area > max) || (r< rmin || r > rmax))
	{
		return false;
	}
	else
	{
		return true;
	}
}

//二值化,輸入爲單通道,輸出一個二值圖像
void OstuBeresenThreshold(const Mat &src, Mat &out)
{
	//otsu獲得全局閾值
	double ostu_T = threshold(src, out, 0, 255, CV_THRESH_OTSU); 

	double min;
	double max;
	minMaxIdx(src, &min, &max);
	const double CI = 0.12;
	double beta = CI*(max - min + 1) / 128;
	double beta_lowT = (1 - beta)*ostu_T;
	double beta_highT = (1 + beta)*ostu_T;

	Mat doubleMatIn;
	src.copyTo(doubleMatIn);
	int rows = doubleMatIn.rows;
	int cols = doubleMatIn.cols;
	double Tbn;
	for (int i = 0; i < rows; ++i)
	{
		//獲取第 i行首像素指針
		uchar * p = doubleMatIn.ptr<uchar>(i);
		uchar *outPtr = out.ptr<uchar>(i);

		//對第i 行的每個像素(byte)操作
		for (int j = 0; j < cols; ++j)
		{

			if (i <2 | i>rows - 3 | j<2 | j>rows - 3)
			{

				if (p[j] <= beta_lowT)
				{
					outPtr[j] = 0;
				}
					
				else
				{
					outPtr[j] = 255;
				}
					
			}
			else
			{
				//窗口大小25*25
				Tbn = sum(doubleMatIn(Rect(i - 2, j - 2, 5, 5)))[0] / 25;  
				if (p[j] < beta_lowT | (p[j] < Tbn && (beta_lowT <= p[j] && p[j] >= beta_highT)))
				{
					outPtr[j] = 0;
				}
				if (p[j] > beta_highT | (p[j] >= Tbn && (beta_lowT <= p[j] && p[j] >= beta_highT)))
				{
					outPtr[j] = 255;
				}	
			}
		}
	}
}

運行效果:
在這裏插入圖片描述
紅線是爲了方便顯示效果加的,黑色那個框就是程序得到的號碼區域。

結語

關於整個工程的源碼,運行程序時的bug,或者有如何優化的想法都可以加之前我博客後面提到的羣,相互討論學習。

發佈了69 篇原創文章 · 獲贊 38 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章