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

#pragma once
#include <highgui.h>  
#include <cv.h>  
#include <string>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

#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 <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/ml/ml.hpp>
#include <string>
#include <stdlib.h>
#include <iostream>
#include <stdio.h>
#include <vector>

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<cv::Mat> 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<cv::ml::ANN_MLP>  ann ;
	void get_ann_featuer_xml(std::string xml_file_path) ;  //获得神经网络的训练矩阵和标签矩阵,
	void ann_train(std::string xml_file_path , cv::Ptr<cv::ml::ANN_MLP> &ann, int numCharacters, int nlayers);
	std::string ann_predict(cv::Ptr<cv::ml::ANN_MLP> &ann, std::vector<cv::Mat> &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<cv::Mat> 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<std::vector<cv::Point> > 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<std::vector<cv::Point>> ::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<double>(0, 2) += (int)((new_width - width) / 2); // 给计算得到的旋转矩阵添加平移  
	M.at<double>(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<cv::Mat> IdCard::split_segment_number(const cv::Mat &inputImg)
{
	std::vector<cv::Mat> 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<uchar>(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<uchar>(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::vector<float>feat;
/*--------特征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<p.cols; i++)
	{
		feat.push_back(p.at<float>(i));
	}

	//增加水平直方图和垂直直方图
	cv::Mat vhist = projectHistogram(imgSrc, 1); //水平直方图
	cv::Mat hhist = projectHistogram(imgSrc, 0);  //垂直直方图
	for (int i = 0; i<vhist.cols; i++)
	{
		feat.push_back(vhist.at<float>(i));
	}
	for (int i = 0; i<hhist.cols; i++)
	{
		feat.push_back(hhist.at<float>(i));
	}


	cv::Mat featuer_mat = cv::Mat::zeros(1, feat.size(), CV_32F);
	for (int i = 0; i<feat.size(); i++)
	{
		featuer_mat.at<float>(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<uchar>(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<float>(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<cv::ml::ANN_MLP> &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<int>(0) = trainData.cols;   
	layerSizes.at<int>(1) = nlayers; //1个隐藏层的神经元结点数,设置为24
	layerSizes.at<int>(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<uchar>(i))
			{
				trainClasses.at<float>(i, k) = 1;
			}
			else
				trainClasses.at<float>(i, k) = 0;
		}
	}
	cv::Ptr<cv::ml::TrainData> trainSet = cv::ml::TrainData::create(trainData, cv::ml::ROW_SAMPLE, trainClasses);
	ann->train(trainSet); 
}

std::string IdCard::ann_predict(cv::Ptr<cv::ml::ANN_MLP> &ann, std::vector<cv::Mat> &char_Mat)
{
	std::vector<int> result;
	result.resize(char_Mat.size());
	for (int i = 0; i<char_Mat.size(); ++i)
	{
		cv::Mat output(1, 11, CV_32FC1);  
		cv::Mat char_feature = get_featuer_mat(char_Mat[i]) ;
		ann->predict(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 <iostream>  

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







  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值