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); // 设置是否训练 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++识别图片身份证号码相关推荐
- 【Matlab身份证识别】身份证号码识别【含GUI源码 014期】
一.代码运行视频(哔哩哔哩) [Matlab身份证识别]身份证号码识别[含GUI源码 014期] 二.matlab版本及参考文献 1 matlab版本 2014a 2 参考文献 [1] 蔡利梅.MAT ...
- 【身份证识别】身份证号码识别【含GUI Matlab源码 014期】
⛄一.身份证号码识别简介 1 引言 作为居民身份的象征,身份证是居民身份的唯一标识,它已成为生活中必不可少的证件.在火车站.酒吧等公共场所,流动人口大人员复杂,警察需要对公民的身份证进行核对,排除可疑 ...
- 腾讯OCR识别图片(身份证、车牌号、名片、驾驶证、营业执照、银行卡、车牌号、人脸等)
[原文:https://blog.csdn.net/liguoqingxjxcc/article/details/82224670] 开始 腾讯OCR可以做什么? 可以鉴黄.识别身份证.名片.驾驶证. ...
- 关于调用百度云OCR身份证识别接口,用Java语言,识别结果缺少身份证号码的问题解决
问题描述: 最近项目系统开发,使用到了相关证件的信息提取.识别,由于是学校科研使用,选择了百度云OCR文字识别的API.具体的相关识别身份等证件的代码将在另一篇文章中叙述,最近真的太忙了,草稿箱中还有 ...
- 调用opencv库进行身份证号码识别主要流程
如题,就是对身份证拍照,处理相应照片,识别出身份证号码 这里需要调用opencv库.opencv库包含了许多处理图像的函数,功能全面而且强大,兼容多种语言.如何配置可以自行搜索. 主要流程如下: 读 ...
- 8.OpenCV-识别身份证号码(Python)
需求描述: 通过OpenCV识别身份证照片上的身份证号码(仅识别身份证号码) 实现思路: 1.将身份证号中的0,1,2,3,4,5,6,7,8,9作为模板,与身份证照片中的身份证号码区域进行模板匹配. ...
- Android 识别身份证号码(图片识别)
概述 Android 身份证号码识别 (本地,在线,实时),网络识别用的别人的接口,不保障什么时候就用不了了,本地识别基于tess_two,位置对的话识别准确率达到90%以上. 详细 代码下载:htt ...
- iOS身份证号码识别
最近不少简友说git上下载下来的代码报各种问题,因为包含的库都比较大,所以大家在pod的时候耐心等待,另外我已经将代码适配到了iOS10. 一.前言 身份证识别,又称OCR技术.OCR技术是光学字 ...
- Java 身份证号码识别系统
最近发现一个有趣的项目. 这个项目是通过学习https://gitee.com/nbsl/idCardCv 后整合 tess4j,不需要经过训练直接使用的,当然,你也可以进行训练后进行使用. 该项目修 ...
- MATLAB身份证号码定位检测识别GUI
一.应用背景 随着信息时代的飞速发展,身份证作为人口信息行之有效的管理工具,已经深入到社会生活的方方面面.身份证是我国居民身份的象征,独一无二的身份证号码录入了公民的基本个人信息.出于保障公民的合 ...
最新文章
- 开始阅读 深入理解计算机系统
- 用freeze.py打包python程序成可执行程序(linux)
- 飞秋的模拟实现代码,很好很山寨!
- 设计模式学习笔记——中介者(Mediator)模式
- mysql 数据透视_sql怎么做数据透视表
- python Image 安装
- Mini 容器学习笔记10——方法注入
- Java入门基础知识
- Java 版本6下载大全
- pcb地线应该不应该做成环路_电源PCB设计流程及要点全解析
- 中文分词(上)——获取和Word2Vec模型构建
- 祝你元宵节快乐!今朝逢元夜,花与灯依旧。众里寻他千百度。蓦然回首,那人却在,灯火阑珊处。...
- 计算机表格斜线怎么打字上去,excel表格斜杠怎么分割打字,表格画线怎么画斜线...
- centos7.6安装lnmp环境
- Python学习-if语句
- python刷视频挣钱_薅羊毛--使用python+adb实现自动刷视频点赞
- 查看linux服务器的品牌和型号
- Android Studio 配置 Copyright 插入版权声明
- JavaGUI小结——实验做的QQ登录界面
- Kylin的介绍及使用说明