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