身份证号码识别 Opencv3.3.0(C++)

#pragma once
#include   
#include   
#include 
#include 
#include 

#define OUT_PUT_IMG 10086

namespace CardUtils {
	void showIdReslut(std::string title);
	std::string intTostring(const int n);
	void imgShow(std::string title, cv::Mat img);
}

#include "stdafx.h"
#include "card_utils.h"

void CardUtils::showIdReslut(std::string title) {
	/* 创建一个空图 */
	CvSize size = cvSize(400, 400);
	IplImage * testImage = cvCreateImage(size, IPL_DEPTH_8U, 3);
	/* 对图像数据域的矩阵进行赋值得到一副空白图 */
	for (int y = 0; y < testImage->height; y++) {
		unsigned char * Pout = (unsigned char *)(testImage->imageData + y * testImage->widthStep);
		for (int x = 0; x < testImage->width; x++) {
			Pout[3 * x + 0] = 255;
			Pout[3 * x + 1] = 255;/* 使图像呈现白色 */
			Pout[3 * x + 2] = 255;
		}
	}
	/* 定义要显示的内容 */
	const char * text = new char[20];
	text = title.c_str();
	char * text_last = new char[60];
	text_last = "Ceair PSS iBot";/* 文本字符串2 */
	CvPoint point1 = cvPoint(50, 50);
	CvPoint point2 = cvPoint(80, 80);/* 设置字体在图片中出现的位置 */
	CvPoint point3 = cvPoint(110, 110);
	CvPoint point4 = cvPoint(10, 370);
	CvScalar color = cvScalar(10, 10, 210);/* 设置字体的颜色 */
	CvFont font1, font2, font3, font4;/* 定义一些设置字体的变量 */
	cvInitFont(&font2, CV_FONT_HERSHEY_COMPLEX, 0.5, 1.0, 0);
	//	 cvInitFont(&font3, CV_FONT_HERSHEY_SIMPLEX, 0.5, 1.0, 0);  
	cvInitFont(&font4, CV_FONT_HERSHEY_SCRIPT_COMPLEX, 0.5, 1.0, 0);
	cvPutText(testImage, text, point2, &font2, color);
	cvPutText(testImage, text_last, point4, &font2, color);
	cvNamedWindow("识别结果");
	cvShowImage("识别结果", testImage);/* 创建图片并显示文本 */
	cvWaitKey(0);
	cvReleaseImage(&testImage);
	cvDestroyAllWindows();
	cv::waitKey(0);
}

std::string CardUtils::intTostring(const int n)
{
	std::stringstream newstr;
	newstr << n;
	return newstr.str();
}

void CardUtils::imgShow(std::string title, cv::Mat img) {
	#ifdef OUT_PUT_IMG
		cv::namedWindow(title);
		cv::imshow(title, img);
		cv::waitKey(0);
	#endif 
}


#pragma once
#include "stdafx.h"
#include "card_utils.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

class IdCard
{
public:
	cv::Mat imgSrc; 
	void load_Idcard(std::string img_path);
	std::string recognition();
	cv::Mat extract_idcard_region(const cv::Mat &in);
	bool isEligible(const cv::RotatedRect & candidate); 
	std::vector split_segment_number(const cv::Mat & in);
	cv::Mat get_featuer_mat(const cv::Mat& imgSrc);
	float sumMatValue(const cv::Mat& image);
	cv::Mat projectHistogram(const cv::Mat& img, int t);
	cv::Mat image_rotate_newsize(cv::Mat& src, const CvPoint &_center, double angle, double scale);
	
	cv::Ptr  ann ;
	void get_ann_featuer_xml(std::string xml_file_path) ;  //获得神经网络的训练矩阵和标签矩阵,
	void ann_train(std::string xml_file_path , cv::Ptr &ann, int numCharacters, int nlayers);
	std::string ann_predict(cv::Ptr &ann, std::vector &char_Mat);
};

#include "stdafx.h"
#include "IdCard.h"


void IdCard::load_Idcard(std::string img_path)
{
	this->imgSrc = cv::imread(img_path);
	CardUtils::imgShow("step 1: 载入", this->imgSrc);
}

std::string IdCard::recognition()
{
	cv::Mat  Igray;
	cv::cvtColor(this->imgSrc, Igray, CV_RGB2GRAY);
	CardUtils::imgShow("step 2: 灰度化", Igray);
	cv::Mat img_idcad_nums = extract_idcard_region(Igray);   
	CardUtils::imgShow("step 10:裁剪", img_idcad_nums);
	std::vector number_mats = split_segment_number(img_idcad_nums);
	for (int i = 0; i < number_mats.size(); i++) {
		CardUtils::imgShow("step 12:分割", number_mats[i]);
	}

	std::string id_number = ann_predict(this->ann, number_mats);
	return id_number; 
}

cv::Mat IdCard::extract_idcard_region(const cv::Mat &in)
{
	cv::Mat img_threshold ; 
	cv::threshold(in, img_threshold, 0, 255, cv::THRESH_OTSU);
	CardUtils::imgShow("step 3: 二值化", img_threshold);
	cv::Mat img_white(in.size(), in.type(), cv::Scalar(255));
	img_threshold = img_white - img_threshold;   //黑白色反转,即背景为黑色
	CardUtils::imgShow("step 4: 取反", img_threshold);
	cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(13, 14));  //闭形态学的结构元素
	cv::Mat img_close ;
	cv::morphologyEx(img_threshold, img_close, cv::MORPH_CLOSE , element);
	CardUtils::imgShow("step 5: 形态学变换(闭操作)", img_close);
	std::vector > contours;
	cv::findContours(img_close, contours, cv::RETR_EXTERNAL , cv::CHAIN_APPROX_NONE) ;//只检测外轮廓
	cv::Mat outRecs;  //对候选的轮廓进行进一步筛选 
	in.copyTo(outRecs);
	cv::RotatedRect rect;
	for(std::vector> ::iterator itc = contours.begin(); itc != contours.end() ; itc++)
	{
		cv::RotatedRect mr = cv::minAreaRect(cv::Mat(*itc)); //返回每个轮廓的最小有界矩形区域
		cv::Point2f verticesNow[4];
		mr.points(verticesNow);
		for (int i = 0; i < 4; i++) {
			line(outRecs, verticesNow[i], verticesNow[(i + 1) % 4], cv::Scalar(0));//画黑色线条
		}
		if (isEligible(mr))  //判断矩形轮廓是否符合要求
		{
			rect = mr ;
		}
	}
	CardUtils::imgShow("step 6: 提取", outRecs);
	cv::Mat out;    //测试是否找到了号码区域
	in.copyTo(out);
	cv::Point2f vertices[4];
	rect.points(vertices);
	for (int i = 0; i < 4; i++) {
		line(out, vertices[i], vertices[(i + 1) % 4], cv::Scalar(0));//画黑色线条
	}
	CardUtils::imgShow("step 7: 区域定位", out);

	cv::Mat  img_idcard_region ;
	cv::Size rect_size = rect.size;
	std::cout << rect_size << std::endl;
//	std::swap(rect_size.width, rect_size.height);
	cv::getRectSubPix(in.clone() , rect_size, rect.center, img_idcard_region);
	CardUtils::imgShow("step 8: 区域分割", img_idcard_region);

	double angle = rect.angle;
	double r = (double)rect.size.width / (double)rect.size.height;
	if (r < 1.0) {
		angle = 90 + angle;
	}
	cv::Point center = cv::Point(in.rows / 2, in.cols / 2);
	cv::Mat img_rotated = image_rotate_newsize(img_idcard_region, center, angle, 1.0);
	CardUtils::imgShow("step 9:旋转矫正", img_rotated);
	return img_rotated;
}

bool IdCard::isEligible(const cv::RotatedRect &candidate)
{
	float error = 0.2;
	const float aspect = 4.5 / 0.3; //长宽比
	int area_min = 10 * aspect * 10; //最小区域
	int area_max = 50 * aspect * 50;  //最大区域
	float r_min = aspect - aspect*error; //考虑误差后的最小长宽比
	float r_max = 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;
	return area_min <= area && area <= area_max && r_min <= r && r <= r_max;
}

cv::Mat IdCard::image_rotate_newsize(cv::Mat& src, const CvPoint &_center, double angle, double scale)
{
	double angle2 = angle * CV_PI / 180;
	int width = src.cols;
	int height = src.rows;
	double alpha = cos(angle2) * scale;
	double beta = sin(angle2) * scale;
	int new_width = (int)(width * fabs(alpha) + height * fabs(beta));
	int new_height = (int)(width * fabs(beta) + height * fabs(alpha));
	cv::Point2f center = cv::Point2f(float(width / 2), float(height / 2));
	cv::Mat M = cv::getRotationMatrix2D(center, angle, scale);  //计算二维旋转的仿射变换矩阵  
	M.at(0, 2) += (int)((new_width - width) / 2); // 给计算得到的旋转矩阵添加平移  
	M.at(1, 2) += (int)((new_height - height) / 2);
	cv::Mat dst;  
	cv::warpAffine(src, dst, M, cvSize(new_width, new_height), 1 , 0  , cv::Scalar(255)); 	// rotate  
	return dst;
}

std::vector IdCard::split_segment_number(const cv::Mat &inputImg)
{
	std::vector dst_mats ;
	cv::Mat img_threshold;
	cv::Mat img_white(inputImg.size(), inputImg.type(), cv::Scalar(255));
	cv::Mat in_Inv = img_white - inputImg;
	cv::threshold(in_Inv, img_threshold, 0, 255, CV_THRESH_OTSU); //大津法二值化
	CardUtils::imgShow("step 11: 二值化", img_threshold);

	int x_char[19] = { 0 };
	short counter = 1;
	short num = 0;
	bool* flag = new bool[img_threshold.cols];

	int last;
	for (int j = 0; j < img_threshold.cols; ++j)
	{
		flag[j] = true;
		for (int i = 0; i < img_threshold.rows; ++i)
		{
			if (img_threshold.at(i, j) != 0)
			{
				flag[j] = false;
				break;
			}
		}
		if (flag[j]) {
			last = j+2;
		}
	}

	for (int i = 0; i < img_threshold.cols - 2; ++i)
	{
		if (flag[i] == true)//黑
		{
			x_char[counter] += i;
			num++;
			if (flag[i + 1] == false && flag[i + 2] == false)//白
			{
				x_char[counter] = x_char[counter] / num;
				num = 0;
				counter++;
			}
		}
	}
	x_char[counter] = img_threshold.cols;
	for (int i = 0; i < counter; i++)
	{
		dst_mats.push_back(cv::Mat(img_threshold, cv::Rect(x_char[i] , 0, x_char[i + 1] - x_char[i], img_threshold.rows)));
	}
	return dst_mats;
}

void IdCard::get_ann_featuer_xml(std::string xml_file_path)  
{
	cv::FileStorage fs(xml_file_path , cv::FileStorage::WRITE);
	cv::Mat  trainData;
	cv::Mat classes = cv::Mat::zeros(1, 550, CV_8UC1);    
	cv::Mat img_read;
	for (int i = 0; i <= 10; i++)  //第i类
	{
		for (int j = 1; j< 51; ++j)  //i类中第j个,即总共有11类字符,每类有50个样本
		{
			std::string path = "C:/Users/liyang/Desktop/Id_recognition/Id_recognition/Number_char/" + CardUtils::intTostring(i) + "/" + CardUtils::intTostring(i) + " (" + CardUtils::intTostring(j) + ").png";
			img_read = cv::imread(path, 0);
			cv::Mat dst_feature = get_featuer_mat(img_read) ; //计算每个样本的特征矢量
			trainData.push_back(dst_feature);
			classes.at(0, i * 50 + j - 1) = i;
		}
	}
	fs << "TrainingData" << trainData;
	fs << "classes" << classes;
	fs.release();
}

cv::Mat IdCard::get_featuer_mat(const cv::Mat &imgSrc)
{
	std::vectorfeat;
/*--------特征1--------------------------------------------------------------------------------------*/
	cv::Mat image;
	cv::resize(imgSrc, image, cv::Size(8, 16));
	float mask[3][3] = { { 1, 2, 1 },{ 0, 0, 0 },{ -1, -2, -1 } };  // 计算x方向和y方向上的滤波
	cv::Mat y_mask = cv::Mat(3, 3, CV_32F, mask) / 8;
	cv::Mat x_mask = y_mask.t(); // 转置
	cv::Mat sobelX, sobelY;
	cv::filter2D(image, sobelX, CV_32F, x_mask);
	cv::filter2D(image, sobelY, CV_32F, y_mask);
	sobelX = cv::abs(sobelX);
	sobelY = cv::abs(sobelY);
	float totleValueX = sumMatValue(sobelX);
	float totleValueY = sumMatValue(sobelY);

	// 将图像划分为4*2共8个格子,计算每个格子里灰度值总和的百分比
	for (int i = 0; i < image.rows; i = i + 4)
	{
		for (int j = 0; j < image.cols; j = j + 4)
		{
			cv::Mat subImageX = sobelX(cv::Rect(j, i, 4, 4));
			feat.push_back(sumMatValue(subImageX) / totleValueX);
			cv::Mat subImageY = sobelY(cv::Rect(j, i, 4, 4));
			feat.push_back(sumMatValue(subImageY) / totleValueY);
		}
	}
	
/*--------特征2--------------------------------------------------------------------------------------*/
	cv::Mat imageGray;
	cv::resize(imgSrc, imageGray, cv::Size(4, 8));
	cv::Mat p = imageGray.reshape(1, 1);
	p.convertTo(p, CV_32FC1);
	for (int i = 0; i(i));
	}

	//增加水平直方图和垂直直方图
	cv::Mat vhist = projectHistogram(imgSrc, 1); //水平直方图
	cv::Mat hhist = projectHistogram(imgSrc, 0);  //垂直直方图
	for (int i = 0; i(i));
	}
	for (int i = 0; i(i));
	}


	cv::Mat featuer_mat = cv::Mat::zeros(1, feat.size(), CV_32F);
	for (int i = 0; i(i) = feat[i];
	}
	return featuer_mat;
}

float IdCard::sumMatValue(const cv::Mat &image)
{
	float sumValue = 0;
	int r = image.rows;
	int c = image.cols;
	if (image.isContinuous())
	{
		c = r*c;
		r = 1;
	}
	for (int i = 0; i < r; i++)
	{
		const uchar* linePtr = image.ptr(i);
		for (int j = 0; j < c; j++)
		{
			sumValue += linePtr[j];
		}
	}
	return sumValue;
}

cv::Mat IdCard::projectHistogram(const cv::Mat &img, int t)
{
	cv::Mat lowData;
	cv::resize(img, lowData, cv::Size(8, 16)); //缩放到8*16

	int sz = (t) ? lowData.rows : lowData.cols;
	cv::Mat mhist = cv::Mat::zeros(1, sz, CV_32F);

	for (int j = 0; j < sz; j++)
	{
		cv::Mat data = (t) ? lowData.row(j) : lowData.col(j);
		mhist.at(j) = cv::countNonZero(data);
	}

	double min, max;
	cv::minMaxLoc(mhist, &min, &max);

	if (max > 0)
		mhist.convertTo(mhist, -1, 1.0f / max, 0);

	return mhist;
}

void IdCard::ann_train(std::string xml_file_path , cv::Ptr &ann, int numCharacters, int nlayers)
{
	cv::Mat trainData, classes;
	cv::FileStorage fs;
	fs.open(xml_file_path , cv::FileStorage::READ);

	fs["TrainingData"] >> trainData;
	fs["classes"] >> classes;

	cv::Mat layerSizes(1, 3, CV_32SC1);     //3层神经网络
	layerSizes.at(0) = trainData.cols;   
	layerSizes.at(1) = nlayers; //1个隐藏层的神经元结点数,设置为24
	layerSizes.at(2) = numCharacters; //输出层的神经元结点数为:11
	ann = cv::ml::ANN_MLP::create() ; 
	ann->setLayerSizes(layerSizes) ;
	ann->setActivationFunction(cv::ml::ANN_MLP::SIGMOID_SYM, 1.0, 1.0);
	ann->setTrainMethod(cv::ml::ANN_MLP::BACKPROP, 0.1, 0.1);
	ann->setTermCriteria(cv::TermCriteria(
		cv::TermCriteria::COUNT + cv::TermCriteria::EPS
		, 5000
		, 0.01));
	cv::Mat trainClasses;
	trainClasses.create(trainData.rows, numCharacters, CV_32FC1);
	for (int i = 0; i< trainData.rows; i++)
	{
		for (int k = 0; k< trainClasses.cols; k++)
		{
			if (k == (int)classes.at(i))
			{
				trainClasses.at(i, k) = 1;
			}
			else
				trainClasses.at(i, k) = 0;
		}
	}
	cv::Ptr trainSet = cv::ml::TrainData::create(trainData, cv::ml::ROW_SAMPLE, trainClasses);
	ann->train(trainSet); 
}

std::string IdCard::ann_predict(cv::Ptr &ann, std::vector &char_Mat)
{
	std::vector result;
	result.resize(char_Mat.size());
	for (int i = 0; ipredict(char_feature, output);  
		cv::Point maxLoc;
		double maxVal;
		minMaxLoc(output, 0, &maxVal, 0, &maxLoc);
		result[i] = maxLoc.x;
	}
	std::string id = "";
	for (int i = 0; i < result.size(); ++i)
	{
		if (result[i] == 10)
			id += "X";
		else
			id += CardUtils::intTostring(result[i]); //将int转换为Qstring
	}
	return id;
}


#include "stdafx.h"
#include "idcard.h"
#include   

int main(){
    
	double t = (double)cvGetTickCount();
	std::string featuer_xml_path = "C:\\Users\\liyang\\Desktop\\ann_xml.xml";
	IdCard w;
	w.get_ann_featuer_xml(featuer_xml_path) ;
	w.ann_train(featuer_xml_path , w.ann , 11, 24);  //11为输出层结点,也等于输出的类别,24为隐藏层结点
	t = ((double)cvGetTickCount() - t) / (cvGetTickFrequency() * 1000);
	std::cout <<"train cost: "<< t << "ms" << std::endl;

	t = (double)cvGetTickCount();
	std::string img_path2 = "C:/Users/liyang/Desktop/dudu.png";
	w.load_Idcard(img_path2);
	std::string id_number = w.recognition();
	t = ((double)cvGetTickCount() - t) / (cvGetTickFrequency() * 1000);
	std::cout << "predict cost: " << t << "ms" << std::endl;

	CardUtils::showIdReslut(id_number);

	system("pause");
    return 0;
}


身份证号码识别 Opencv3.3.0(C++)_第1张图片




你可能感兴趣的:(工作足迹)