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);                           // 设置是否训练 SVMvoid 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;                                      // 是否训练 SVMbool 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 代表 Xvector<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 colint hr = idcard.rows, hc = idcard.cols;            // 记录迭代时较大的 row col int mr = (lr + hr) >> 1, mc = (lc + hc) >> 1;   // 记录迭代时二分点 row colint 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 colmr = (hr + lr) >> 1;                      // 更新二分点 rowmc = (hc + lc) >> 1;                        // 更新二分点 colsize = 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;                  // 如果未检测到符合条件的连通域直接返回 2if (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 * 28idcardNumbers.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;                                                  // 取余 11if (sum == 1                                              // 如果余数为 1|| (sum == 10 && predictNumber.back() == 10)          // 或者 (10 且最后一位 X)|| mapPlace.find(predictNumber[0]*10 + predictNumber[1]) != mapPlace.end() // 如果前两位正确)  return 0;elsereturn 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 / FALSEcard.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 / FALSEcard.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;
}

c++识别图片身份证号码相关推荐

  1. 【Matlab身份证识别】身份证号码识别【含GUI源码 014期】

    一.代码运行视频(哔哩哔哩) [Matlab身份证识别]身份证号码识别[含GUI源码 014期] 二.matlab版本及参考文献 1 matlab版本 2014a 2 参考文献 [1] 蔡利梅.MAT ...

  2. 【身份证识别】身份证号码识别【含GUI Matlab源码 014期】

    ⛄一.身份证号码识别简介 1 引言 作为居民身份的象征,身份证是居民身份的唯一标识,它已成为生活中必不可少的证件.在火车站.酒吧等公共场所,流动人口大人员复杂,警察需要对公民的身份证进行核对,排除可疑 ...

  3. 腾讯OCR识别图片(身份证、车牌号、名片、驾驶证、营业执照、银行卡、车牌号、人脸等)

    [原文:https://blog.csdn.net/liguoqingxjxcc/article/details/82224670] 开始 腾讯OCR可以做什么? 可以鉴黄.识别身份证.名片.驾驶证. ...

  4. 关于调用百度云OCR身份证识别接口,用Java语言,识别结果缺少身份证号码的问题解决

    问题描述: 最近项目系统开发,使用到了相关证件的信息提取.识别,由于是学校科研使用,选择了百度云OCR文字识别的API.具体的相关识别身份等证件的代码将在另一篇文章中叙述,最近真的太忙了,草稿箱中还有 ...

  5. 调用opencv库进行身份证号码识别主要流程

    如题,就是对身份证拍照,处理相应照片,识别出身份证号码 这里需要调用opencv库.opencv库包含了许多处理图像的函数,功能全面而且强大,兼容多种语言.如何配置可以自行搜索.  主要流程如下: 读 ...

  6. 8.OpenCV-识别身份证号码(Python)

    需求描述: 通过OpenCV识别身份证照片上的身份证号码(仅识别身份证号码) 实现思路: 1.将身份证号中的0,1,2,3,4,5,6,7,8,9作为模板,与身份证照片中的身份证号码区域进行模板匹配. ...

  7. Android 识别身份证号码(图片识别)

    概述 Android 身份证号码识别 (本地,在线,实时),网络识别用的别人的接口,不保障什么时候就用不了了,本地识别基于tess_two,位置对的话识别准确率达到90%以上. 详细 代码下载:htt ...

  8. iOS身份证号码识别

    最近不少简友说git上下载下来的代码报各种问题,因为包含的库都比较大,所以大家在pod的时候耐心等待,另外我已经将代码适配到了iOS10. 一.前言   身份证识别,又称OCR技术.OCR技术是光学字 ...

  9. Java 身份证号码识别系统

    最近发现一个有趣的项目. 这个项目是通过学习https://gitee.com/nbsl/idCardCv 后整合 tess4j,不需要经过训练直接使用的,当然,你也可以进行训练后进行使用. 该项目修 ...

  10. MATLAB身份证号码定位检测识别GUI

    ​ 一.应用背景 随着信息时代的飞速发展,身份证作为人口信息行之有效的管理工具,已经深入到社会生活的方方面面.身份证是我国居民身份的象征,独一无二的身份证号码录入了公民的基本个人信息.出于保障公民的合 ...

最新文章

  1. 开始阅读 深入理解计算机系统
  2. 用freeze.py打包python程序成可执行程序(linux)
  3. 飞秋的模拟实现代码,很好很山寨!
  4. 设计模式学习笔记——中介者(Mediator)模式
  5. mysql 数据透视_sql怎么做数据透视表
  6. python Image 安装
  7. Mini 容器学习笔记10——方法注入
  8. Java入门基础知识
  9. Java 版本6下载大全
  10. pcb地线应该不应该做成环路_电源PCB设计流程及要点全解析
  11. 中文分词(上)——获取和Word2Vec模型构建
  12. 祝你元宵节快乐!今朝逢元夜,花与灯依旧。众里寻他千百度。蓦然回首,那人却在,灯火阑珊处。...
  13. 计算机表格斜线怎么打字上去,excel表格斜杠怎么分割打字,表格画线怎么画斜线...
  14. centos7.6安装lnmp环境
  15. Python学习-if语句
  16. python刷视频挣钱_薅羊毛--使用python+adb实现自动刷视频点赞
  17. 查看linux服务器的品牌和型号
  18. Android Studio 配置 Copyright 插入版权声明
  19. JavaGUI小结——实验做的QQ登录界面
  20. Kylin的介绍及使用说明

热门文章

  1. 用FlashCache加速MySQL
  2. Android15_ListView分页
  3. 解决在使用rtx2060跑算法时遇到显存不足的问题
  4. 关于ModifyStyle
  5. 2010最新***工具包
  6. 基于ESP32制作流光溢彩氛围灯
  7. SharePoint365 接入简要笔记
  8. windows 好用软件推荐
  9. 内存、磁盘硬盘、软盘、光盘、磁盘驱动器的介绍
  10. vmware 7.0 序列号_更改solidworks序列号及修改安装