c++识别图片身份证号码

在这里插入图片描述
tips1:创建好项目之后,需要把识别的身份证放在所在文件夹的idcard\data\pic里面。
tips2:需要用到这个软件库——opencv,可以去下载相应的版本,附上下载地址,我用的是4.0的版本。然后在项目属性里面进行配置。不会配置或者出现问题可以去参考这两篇博客。
配置问题:https://blog.csdn.net/sinat_34707539/article/details/82804545
报错问题:https://blog.csdn.net/shuiyixin/article/details/98992644

1.类的声明
新建path类声明(path.h)

#include <io.h>
#include <string>
#include <vector>
using namespace std;

void getFileNames(string path, vector<string>& files);

新建card类声明(card.h)

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <opencv2/opencv.hpp>
#include <opencv2/ml.hpp>
using namespace std;
using namespace cv;
using namespace cv::ml;

class Card
{
public:
	Card();
	~Card();

	// 用户接口
	const int identify(const string path, string& txt);		// 识别
	void show(string winName, const int x = 0, const int y = 0); // 显示框选、识别的图片
	void setPicFolderPath(const string path);				// 设置图片文件夹路径
	void setTrainDataFolderPath(const string path);			// 设置 SVM 训练数据路径
	void setTrain(string _TRAIN);							// 设置是否训练 SVM
	void setDebug(string _Debug);							// 设置 Debug 模式
	bool is_DEBUG();										// 返回模式
	void setSavePath(const string path);					// 设置识别结果保存路径
	const string getPicFolderPath();						// 获取图片文件夹路径

private:
	// 身份证数据
	Mat idcard;												// 身份证原图
	Mat img;												// 过程图
	Mat idcardNumber;										// 保存号码截图
	vector<Mat> idcardNumbers;								// 保存切割成单字符的证件号
	vector<RotatedRect> rotatedRects;						// 检测到的连通域
	Ptr<SVM> svm;											// SVM 分类器
	vector<int> predictNumber;								// 保存号码识别结果
	string trainDataFolderPath;								// 数据文件夹路径
	string imgFolderPath;									// 待识别图片文件夹路径
	string imgPath;											// 图片路径	
	string savePath;										// 识别结果保存路径
	bool TRAIN = true;										// 是否训练 SVM
	bool DEBUG = false;										// 是否 DEBUG 模式
	map<int, string> mapPlace;								// 身份证前两位号码地点映射
	float time = 0;											// 识别时间

	// 实现函数
	void resize(const int toRows = 800, const int toCols = 800);// 原图比例缩放至预设大小
	void resize(Mat& img);									// 数字尺寸归一化
	void preDeal();											// 预处理:缩放、滤波、边缘、二值化
	const int detect();										// 检测连通域
	const int findNumber();									// 找出连通域中的号码区域
	void thin();											// 细化(可有可无 未实现)
	const int correct();									// 尾号校验(未实现)
	const int predict();									// 识别号码
	void twoPass();											// 两遍扫描法 标记连通域
	void release();											// 每次识别完一张身份证需要释放内存
};

2.类的实现
实现path.h这个头文件

#include "path.h"

void getFileNames(string path, vector<string>& files)
{
	intptr_t   hFile = 0;
	struct _finddata_t fileinfo;
	string p;
	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
	{
		do
		{
			if ((fileinfo.attrib &  _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFileNames(p.assign(path).append("\\").append(fileinfo.name), files);
			}
			else
			{
				files.push_back(p.assign(path).append("/").append(fileinfo.name));
			}
		} while (_findnext(hFile, &fileinfo) == 0);
		_findclose(hFile);
	}
}

实现card.h这个头文件

#include "card.h"
#include "path.h"

/**************************** public ****************************/

Card::Card()
{
	// 身份证号前两位数字
	vector<int> key = {
		11, 12, 13, 14, 15,
		21, 22, 23,
		31, 32, 33, 34, 35, 36, 37,
		41, 42, 43,
		44, 45, 46,
		51, 52, 53, 54, 50,
		61, 62, 63, 64, 65,
		71, 81, 82
	};
	vector<string> value = {
		"北京","天津","河北","山西","内蒙古",
		"辽宁","吉林","黑龙江",
		"上海","江苏","浙江","安徽","福建","江西","山东",
		"河南","湖北","湖南",
		"广东","广西","湖南",
		"四川","贵州","云南","西藏","重庆",
		"陕西","甘肃","青海","宁夏","新疆",
		"台湾","香港","澳门"
	};
	for (int i = 0; i < key.size(); i++)
		mapPlace[key[i]] = value[i];
}

Card::~Card()
{
	idcard.release();
	img.release();
	map<int, string>().swap(mapPlace);
}

const int Card::identify(string path, string& txt)
{
	release();										// 识别前释放之前的内存
	txt.clear();									// 赋值前清空
	time = 0;										// 重置识别时间

	imgPath = path;									// 保存图片路径
	img = imread(imgPath);							// 读图
	if (img.empty()) 								// 保证传入的图像非空
		return 1;
	idcard = img.clone();							// 保存一份原图
	resize();										// 缩放大小
	img = idcard.clone();							// 过程图

	TickMeter _t;									// 记录运行时间
	_t.reset();										// 重置时间
	_t.start();										// 开始计时

	preDeal();										// 预处理
	if (detect()) {									// 检测
		_t.stop();
		time = _t.getTimeSec();						// 获得识别耗时
		return 2;									// 返回无法检测
	}
	if (findNumber()) {								// 找号码
		_t.stop();
		time = _t.getTimeSec();						// 获得识别耗时
		return 3;									// 返回无法找到号码
	}
	if (predict() || correct()) {					// 识别号码
		_t.stop();
		time = _t.getTimeSec();						// 获得识别耗时
		return 4;									// 返回识别错误
	}
	_t.stop();										// 停止计时
	time = _t.getTimeSec();							// 获得识别耗时

	for (auto x : predictNumber)					// 转换为字符串
		txt += x == 10 ? "X" : to_string(x);
	idcard(Rect(10, 10, 380, 40)) = Scalar(80, 80, 80);// 绘制灰色矩形背景
	putText(idcard, txt, Point(20, 40), 1, 2, Scalar(0, 255, 0), 2);// 绘制数字

	static int cnt = 0;								// 静态变量 用于编号
	imwrite(savePath + to_string(cnt++) + ".jpg", idcard);	// 保存识别结果
	return 0;
}

void Card::show(string winName, const int x, const int y)
{
	if (idcard.empty())
		return;

	winName += (" | time: " + to_string(time) + " s");
	namedWindow(winName);
	moveWindow(winName, x, y);
	imshow(winName, idcard);

	//waitKey();										// 等待按键
	//destroyWindow(winName);							// 销毁窗口
	release();										// 释放内存
}

void Card::setPicFolderPath(const string path)
{
	imgFolderPath = path;
}

void Card::setTrainDataFolderPath(const string path)
{
	trainDataFolderPath = path;
}

void Card::setTrain(string _TRAIN)
{
	transform(_TRAIN.begin(), _TRAIN.end(), _TRAIN.begin(), ::tolower);
	TRAIN = _TRAIN == "true" ? true : false;

	if (TRAIN)										// 是否训练
	{
		TickMeter trainT;
		trainT.reset();
		trainT.start();

		cout << "------- TRAIN SVM START -------" << endl;
		Mat trainImages;							// 训练数据
		vector<int> trainLabels;					// 训练标签

		svm = SVM::create();
		svm->setType(SVM::C_SVC);
		svm->setKernel(SVM::LINEAR);
		svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));

		int classes[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };	// 10 代表 X
		vector<string> files;

		for (auto x : classes)						// 遍历每个分类文件夹
		{
			getFileNames(trainDataFolderPath + to_string(x), files);// 获取该分类文件夹的所有图片
			for (auto file : files)					// 读取该类图片
			{
				img = imread(file, 0);				// 读取灰度图
				if (img.empty()) continue;			// 判断是否为空
				threshold(img, img, 10, 255, THRESH_OTSU); // 二值化
				img.convertTo(img, CV_32FC1);		// 转换为 float 类型单通道图像
				trainImages.push_back(img.reshape(0, 1));// 图片转化为一行 加入训练数据中
				trainLabels.push_back(x);			// 保存对应图片标签
			}
			files.clear();							// 遍历下一个文件夹前清空
		}
		img.release();								// 释放 img 占用的内存

		// 训练
		svm->train(trainImages, ROW_SAMPLE, trainLabels);// 训练模型
		svm->save(trainDataFolderPath + "svm.xml");	//保存模型
		svm->clear();
		trainT.stop();
		cout << "train time: " << trainT.getTimeSec() << " s" << endl;
		cout << "------- TRAIN SVM  END  -------" << endl;
	}
}

void Card::setDebug(string _Debug)
{
	transform(_Debug.begin(), _Debug.end(), _Debug.begin(), ::tolower);
	DEBUG = _Debug == "true" ? true : false;
}

void Card::setSavePath(const string path)
{
	savePath = path;
}

const string Card::getPicFolderPath()
{
	return imgFolderPath;
}

bool Card::is_DEBUG()
{
	return this->DEBUG;
}

/**************************** private ***************************/
// 使原图尺寸缩放至 toRows * toCols 左右
void Card::resize(const int toRows, const int toCols)
{
	int lr = 0, lc = 0;								// 记录迭代时较小的 row col
	int hr = idcard.rows, hc = idcard.cols;			// 记录迭代时较大的 row col	
	int mr = (lr + hr) >> 1, mc = (lc + hc) >> 1;	// 记录迭代时二分点 row col
	int size = mr * mc;								// 当前二分点尺寸
	int toSize = toRows * toCols;					// 目标尺寸
	int sub = toSize - size;						// 当前尺寸与目标尺寸差

	// 使尺寸差小于两倍长宽和
	int delta = (toCols + toRows) << 1;				// 允许缩放至目标尺寸的误差
	while (abs(sub) > delta)						// 差大于误差时继续循环
	{
		// 原图大于目标图
		if (sub < 0) { hr = mr; hc = mc; }			// 降低较大的 row col
		// 原图小于目标图
		else { hr += mr; hc += mc; lr = mr; lc = mc; }// 提升较小的 row col
		mr = (hr + lr) >> 1;						// 更新二分点 row
		mc = (hc + lc) >> 1;						// 更新二分点 col
		size = mr * mc;								// 更新当前尺寸
		sub = toSize - size;						// 更新当前差值
	}
	cv::resize(idcard, idcard, Size(mc, mr));		// OpenCV 双线性插值缩放
}

void Card::resize(Mat& _img)
{
	int k = _img.rows - _img.cols;						// 高 - 宽
	if (k > 0) {										// 如果宽 < 高 用黑色填充左右部分
		copyMakeBorder(_img, _img, 0, 0, k/2, k - k/2, BORDER_CONSTANT, 0);
	}
	else {												// 用黑色填充上下部分
		k = -k;
		copyMakeBorder(_img, _img, k/2, k - k/2, 0, 0, BORDER_CONSTANT, 0);	
	}
	cv::resize(_img, _img, Size(28, 28));				// 归一化到 28 * 28
}

void Card::preDeal()
{
	if (DEBUG)
	{
		imshow("0_src", idcard);
	}
	bilateralFilter(idcard, img, 7, 10, 5);				// 双边滤波
	idcard = img.clone();								// 保存双边滤波后的原图
	if (DEBUG)
	{
		imshow("predeal_0_bilateraFilter", img);
	}

	// 灰度化
	//vector<Mat> m;										// 存储分离的 BGR 通道
	//cv::split(idcard, m);								// 通道分离
	//img = m[2].clone();									// 保留 R 通道
	//vector<Mat>().swap(m);								// 清空 m 占用的内存
	cvtColor(img, img, COLOR_BGR2GRAY);
	if (DEBUG)
	{
		imshow("predeal_1_gray", img);
	}

	// 形态学边缘检测
	Mat tmp = img.clone();								// 临时存储空间
	morphologyEx(										// 形态学运算
		img,											// 输入图像
		tmp,											// 输出图像
		MORPH_CLOSE,									// 指定闭运算 连通破碎区域
		getStructuringElement(MORPH_RECT, Size(7, 7))	// 获取结构核
	);
	img = tmp - img;									// 作差

	if (DEBUG)
	{
		imshow("predeal_2_close", tmp);
		imshow("predeal_3_gray - close", img);
	}

	tmp.release();										// 释放临时空间

	// 二值化
	threshold(img, img, 0, 255, THRESH_OTSU);			// 二值化
	if (DEBUG)
	{
		imshow("predeal_4_threshold_otsu", img);
	}
}

const int  Card::detect()
{
	// 闭运算 形成连通区域
	morphologyEx(										// 形态学运算
		img,											// 输入图像
		img,											// 输出图像
		MORPH_CLOSE,									// 指定闭运算 连通号码区域
		getStructuringElement(MORPH_RECT, Size(21, 13))	// 获取结构核
	);

	if (DEBUG)
	{
		imshow("detect_0_close", img);
	}

	// 查找轮廓
	vector<vector<Point>> contours;						// 保存轮廓点
	vector<Vec4i> hierarchy;							// 轮廓的层级关系
	findContours(										// 查找轮廓
		img,											// findContours 会改变输入图像
		contours,										// 输出轮廓
		hierarchy,										// 层级关系 此处只需一个占位
		RETR_TREE,										// 树形式保存
		CHAIN_APPROX_NONE								// 不做近似
	);	
	img.release();										// 清空 img 占用的内存
	vector<Vec4i>().swap(hierarchy);					// 清空 hierarchy 占用的内存

	if (DEBUG)
	{
		Mat dbg_img = Mat::zeros(idcard.size(), CV_8UC1);
		for (int dbg_i = 0; dbg_i < contours.size(); dbg_i++)
			drawContours(dbg_img, contours, dbg_i, 255, 1, 8);
		imshow("detect_1_find_contours", dbg_img);
	}

	// 筛选轮廓
	vector<vector<Point>> contours_number;				// 保存可能的号码区域
	for (auto itr = contours.begin(); itr != contours.end(); itr++)	
		if (itr->size() > 400)							// 保留轮廓点数多于 400 的
			contours_number.push_back(*itr);			// 保存该轮廓
	vector<vector<Point>>().swap(contours);				// 释放 contours 占用的内存

	if (DEBUG)
	{
		Mat dbg_img = Mat::zeros(idcard.size(), CV_8UC1);
		for (int dbg_i = 0; dbg_i < contours_number.size(); dbg_i++)
			drawContours(dbg_img, contours_number, dbg_i, 255, 1, 8);
		imshow("detect_2_filter_contours", dbg_img);
	}

	// 最小包围矩形框
	int i = 0;
	for (auto itr = contours_number.begin(); itr != contours_number.end(); itr++, i++)
	{
		RotatedRect rotatedRect = minAreaRect(*itr);	// 从点集求出最小包围旋转矩形
		const float width = rotatedRect.size.width;		// x 轴逆时针旋转得到的第一条边定义为宽
		const float height = rotatedRect.size.height;	// 另一条边定义为高
		const float k = height / width;					// 高比宽
		if (											// 筛选
			width < 15 || height < 15					// 边长过小
			|| (0.1 < k && k < 10)						// 宽高比在一定范围(不是很长的矩形)
		)
			continue;

		rotatedRects.push_back(rotatedRect);			// 保存
	}
	vector<vector<Point>>().swap(contours_number);		// 释放 contours_number 占用的内存

	if (rotatedRects.empty()) return 2;					// 如果未检测到符合条件的连通域直接返回 2

	if (DEBUG)
	{
		Point2f dbg_p[4];
		Mat dbg_img = idcard.clone();
		for (auto dbg_rotatedRect : rotatedRects)
		{
			dbg_rotatedRect.points(dbg_p);
			for (int dbg_i = 0; dbg_i < 4; dbg_i++)
				line(dbg_img, dbg_p[dbg_i], dbg_p[(dbg_i + 1) % 4], Scalar(0, 0, 255), 2, 8);
		}
		imshow("detect_3_filter_rotated_rect", dbg_img);
	}

	return 0;
}

const int Card::findNumber()
{
	// 记录下来的所有旋转矩形按照中心坐标 x 进行从右到左排序
	sort(rotatedRects.begin(), rotatedRects.end(), [](RotatedRect a, RotatedRect b) {
		return a.center.x > b.center.x;
	});

	// 透视变换设置
	const int toCols = 504, toRows = 28;				// 最终希望的号码截图大小
	const int offset = 7;								// 多截出来一圈
	vector<vector<Point>> contours;						// 保存数字轮廓

	// 遍历每个旋转矩形 是否是号码区域
	for (auto itr = rotatedRects.begin(); itr != rotatedRects.end(); itr++)
	{
		// 保存旋转矩形顶点
		Point2f p[4];
		itr->points(p);

		// 对 p 顶点进行排序
		sort(p, p + 4, [](Point2f a, Point2f b) {			// 按照 x 从小到大排序
			return a.x < b.x;
		});
		if (p[0].y > p[1].y) swap(p[0], p[1]);
		if (p[2].y < p[3].y) swap(p[2], p[3]);
		if (abs(p[0].y - p[1].y) > 60)						// 需要横放 
			return 3;

		// 对该旋转矩形透视变换
		Point2f pSrc[4] = {									// 透视变换 4 个源点
			Point2f(p[0].x - offset, p[0].y - offset), Point2f(p[3].x + offset, p[3].y - offset),
			Point2f(p[1].x - offset, p[1].y + offset), Point2f(p[2].x + offset, p[2].y + offset)
		};
		Point2f pDst[4] = {									// 透视变换 4 个目标点
			Point2f(0, 0),		Point2f(toCols, 0),
			Point2f(0, toRows),	Point2f(toCols, toRows)
		};
		warpPerspective(									// 透视变换函数
			idcard,											// 输入图像
			img,											// 输出图像
			getPerspectiveTransform(pSrc, pDst),			// 求透视变换矩阵
			Size(toCols, toRows)							// 输出图像大小
		);

		// 判断是否包含18个从左到右排列的字符(连通域)
		cvtColor(img, img, COLOR_BGR2GRAY);					// 灰度化
		idcardNumber = img.clone();							// 保存可能的区域 用于识别

		// 作差 边缘检测
		Mat tmp = img.clone();								// 临时存储空间
		morphologyEx(										// 形态学运算
			img,											// 输入图像
			tmp,											// 输出图像
			MORPH_CLOSE,									// 指定闭运算 连通破碎区域
			getStructuringElement(MORPH_RECT, Size(7, 7))	// 获取 7 * 7结构核
		);
		img = tmp - img;									// 作差

		blur(img, img, Size(3, 3));							// 3 * 3 均值滤波
		threshold(img, img, 0, 255, THRESH_OTSU);			// otsu 二值化

		// 闭运算 连通断裂的笔画
		morphologyEx(										// 形态学运算
			img,											// 输入图像
			img,											// 输出图像
			MORPH_CLOSE,									// 指定闭运算 连通破碎区域
			getStructuringElement(MORPH_RECT, Size(3, 7))	// 获取结构核
		);

		findContours(img, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);	// 查找外层轮廓

		if (contours.size() == 18)							// 轮廓数量 18 个说明是号码区域
		{
			// 在 idcard 上框出号码区域
			for (int i = 0; i < 4; i++)
				line(idcard, p[i], p[(i + 1) % 4], Scalar(0, 255, 0), 2, 8);

			if (DEBUG)
			{
				imshow("find_number_0_number_rotated_rect", idcard);
			}
			break;
		}
	}
	vector<RotatedRect>().swap(rotatedRects);				// 清空 rotatedRects 内存
	if (contours.size() != 18)								// 所有连通域的字符数量都不对 则返回无法找到号码区域
	{
		idcardNumber.release();
		return 3;
	}

	// 号码区域轮廓 从左到右排序
	sort(contours.begin(), contours.end(), [](vector<Point> a, vector<Point> b) {
		return boundingRect(a).br().x < boundingRect(b).br().x;
	});

	// 从左到右依次保存每个数字
	vector<Mat> mv;
	for (auto itr = contours.begin(); itr != contours.end(); itr++)
	{
		Rect rect = boundingRect(*itr);						// 求包围矩形
		Mat tmp = idcardNumber(Rect(rect)).clone();			// 提取出该数字
		threshold(tmp, tmp, 0, 255, THRESH_OTSU | THRESH_BINARY_INV); // OTSU 二值化 
		resize(tmp);										// 尺寸归一化 28 * 28
		idcardNumbers.push_back(tmp);						// 保存该数字

		//static int cnt = 0;									// 用于增量训练 保存新产生的单个数字图片
		//imwrite("E:/大四上/idcard/x64/Debug/data/trainData/" + to_string(cnt++) + ".jpg", tmp);
	}

	if (DEBUG)
	{
		Mat dbg_img = Mat::zeros(img.size(), CV_8UC3);
		int dbg_b = 60, dbg_g = 20, dbg_r = 100;
		Scalar dbg_color(dbg_b, dbg_g, dbg_r);
		for (int dbg_i = 0; dbg_i < contours.size(); dbg_i++)
		{
			drawContours(dbg_img, contours, dbg_i, dbg_color, 1, 8);
			dbg_b = (dbg_b + 20) % 256;
			dbg_g = (dbg_g + 40) % 256;
			dbg_r = (dbg_r + 80) % 256;
			dbg_color = Scalar(dbg_b, dbg_g, dbg_r);
		}
		Mat dbg_dst = Mat::zeros(Size(img.cols, img.rows * 3), CV_8UC3);
		merge(vector<Mat>({ idcardNumber, idcardNumber, idcardNumber }), idcardNumber);
		idcardNumber.copyTo(dbg_dst(Rect(0, 0, img.cols, img.rows)));
		dbg_img.copyTo(dbg_dst(Rect(0, img.rows, img.cols, img.rows)));

		dbg_img = Mat::zeros(Size(32 * 18, 28), CV_8UC1);
		for (int dbg_i = 0; dbg_i < idcardNumbers.size(); dbg_i++)
			idcardNumbers[dbg_i].copyTo(dbg_img(Rect(dbg_i * 32, 0, 28, 28)));
		cv::resize(dbg_img, dbg_img, Size(img.cols, img.rows));

		merge(vector<Mat>({ dbg_img, dbg_img, dbg_img }), dbg_img);
		dbg_img.copyTo(dbg_dst(Rect(0, img.rows * 2, img.cols, img.rows)));

		imshow("find_number_1_single_number", dbg_dst);
		dbg_dst.release();
		dbg_img.release();
		waitKey(1);
	}

	idcardNumber.release();									// 释放 idcardNumber 占用的内存
	return 0;
}

const int Card::predict()
{
	svm = SVM::load(trainDataFolderPath + "svm.xml");// 读取模型

	// 逐个识别
	for (auto itr = idcardNumbers.begin(); itr != idcardNumbers.end(); itr++)
	{
		itr->convertTo(img, CV_32FC1);				// 转换为 float 类型单通道图片
		float res = svm->predict(img.reshape(0, 1));// 预测结果
		predictNumber.push_back(res);				// 保存预测结果
	}

	img.release();
	svm->clear();

	return correct() ? 1 : 0;
}

const int Card::correct()
{
	int sum = 0;
	vector<int> W = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1 };	// 权重
	for (int i = 0; i < W.size(); i++)							// 加权和
		sum += predictNumber[i] * W[i];
	sum %= 11;													// 取余 11

	if (
		sum == 1												// 如果余数为 1
		|| (sum == 10 && predictNumber.back() == 10)			// 或者 (10 且最后一位 X)
		|| mapPlace.find(predictNumber[0]*10 + predictNumber[1]) != mapPlace.end() // 如果前两位正确
	)	
		return 0;
	else
		return 1;
}

void Card::release()
{
	idcard.release();
	img.release();
	idcardNumber.release();
	imgPath.clear();
	vector<RotatedRect>().swap(rotatedRects);
	vector<int>().swap(predictNumber);
	vector<Mat>().swap(idcardNumbers);
}

3.主函数

#include "card.h"
#include "path.h"
#include<conio.h>

int main(int argc, char** argv)
{
	Card card;												// 识别身份证号码类

	if (argc == 1)											// 如果没有参数 则默认相对路径
	{
		card.setPicFolderPath("./data/pic");				// 图片根目录 注意最后没有斜杠 '/'
		card.setTrainDataFolderPath("./data/trainData/");	// 训练数据目录 注意最后有斜杠 '/'
		card.setSavePath("./data/res/");					// 设置结果保存路径
		card.setTrain("FALSE");								// 设置 SVM 是否重新训练 TRUE / FALSE
		card.setDebug("FALse");								// 设置是否 DEBUG 模式 TRUE / FALSE
	}
	else if (argc == 6)
	{
		/// params 1: 待识别图片文件夹路径
		/// params 2: SVM 训练数据路径
		/// params 3: 设置结果保存路径
		/// params 4: 是否训练 SVM
		/// params 5: 设置 debug 模式
		card.setPicFolderPath(argv[1]);						// 不要有中文
		card.setTrainDataFolderPath(argv[2]);				// 不要有中文
		card.setSavePath(argv[3]);							// 设置结果保存路径
		card.setTrain(argv[4]);								// TRUE / FALSE
		card.setDebug(argv[5]);								// TRUE / FALSE
	}
	else
	{
		cout << "please check your dir" << endl;
		system("pause");
		return 0;
	}

	TickMeter totalT;										// 记录总时间
	totalT.reset();											// 置零

	string txt;												// 保存识别结果
	vector<string> files;									// 图片路径列表
	getFileNames(card.getPicFolderPath(), files);			// 获得路径列表
	cout << "------- IDENTIFY START -------" << endl;
	for (auto file : files)									// 遍历
	{
		totalT.start();										// 开始计时
		int res = card.identify(file, txt);					// 识别
		if (res == 0) {
			cout << "| result: " + txt + " | " + file << endl;// 输出号码
			if (card.is_DEBUG())
			{
				card.show(file);							// 显示并返回识别出的号码 调用 show 后会释放内存
			}
		}
		else if (res == 1)
			cout << "| error : no such picture | " + file << endl;// 提示图片为空
		else if (res == 2)
			cout << "| error : can not detect | " + file << endl; // 提示不能检测到号码
		else if (res == 3)
			cout << "| error : can not find number area | " + file << endl; // 号码不是18位
		else if (res == 4)
			cout << "| error : wrong number | " + file << endl; // 提示识别错误

		totalT.stop();										// 暂停计时
		if (card.is_DEBUG())
		{
			waitKey();										// 等待按键
			destroyAllWindows();							// 销毁所有窗口
		}
	}

	totalT.stop();											// 停止计时
	cout << "total time: " << totalT.getTimeSec() << " s" << endl;	// 总时间
	cout << "------- IDENTIFY  END -------" << endl;

	_getch();
	return 0;
}
  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孤独的根号三号程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值