【模板图像】
【图像载入】
/*输入图像*/
Mat img;
img = imread("E:/C语言/opencv/基于opencv垂直投影法的图像数字识别/test.png");
【灰度图像转换】
/*灰度图像*/
Mat grayimg;
cvtColor(img, grayimg, COLOR_BGR2GRAY);
【二值化图像转换】
/*二值图像*/
Mat binimg;
//第4个参数为CV_THRESH_BINARY_INV是因为输入原图为白底黑字
//若为黑底白字则选择CV_THRESH_BINARY即可
threshold(grayimg, binimg, 100, 255, CV_THRESH_BINARY_INV);
注:因测试图像为白底黑字简单图像未增加图像预处理部分,若为背景复杂的图像需增加图像预处理部分
【寻找与绘制图像轮廓】
/*寻找轮廓*/
//寻找轮廓,必须指定为寻找外部轮廓,不然一个数字可能有多个轮廓组成,比如4,6,8,9等数字
Mat conimg = Mat::zeros(binimg.size(), binimg.type());
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
//指定CV_RETR_EXTERNAL寻找数字的外轮廓
findContours(binimg, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
//绘制轮廓
drawContours(conimg, contours, -1, 255);
imshow("conimg", conimg);
【图像分割】
方向投影法主要思想就是记录每一行或者每一列对应值的像素的个数,然后根据这些个数判断它是不是边界或者是目标物体。其中像素的个数就像是一个阈值,最后可以把每一行点的个数画出来便于直观的观察。垂直(或水平)投影法的就是,利用二值化图像素分布直方图进行分析,从而找出相邻字符的分界点进行分割。
/*垂直投影*/
int vertical_projection(const Mat& src, vector<Mat>& roiList)
{
//step1. 计算竖直投影白色点数量
int w = src.cols;
int h = src.rows;
vector<int> project_val_arry;
for (int j = 0; j < w; j++)//列
{
Mat j_im = src.col(j);
int num = countNonZero(j_im);
project_val_arry.push_back(num);
}
//垂直投影显示
Mat hist_im(h, w, CV_8UC1, Scalar(255));
for (int i = 0; i < w; i++)
{
for (int j = 0; j < project_val_arry[i]; j++)
{
hist_im.ptr<unsigned char>(h - 1 - j)[i] = 0; //第h-1-j行第i个数据
}
}
//imshow("project_v", hist_im);
//step2. 字符分割
//vector<Mat> roiList;
int startIndex = 0;
int endIndex = 0;
bool inBlock = false;//是否遍历到了字符区内
for (int i = 0; i < w; ++i)
{
if (!inBlock && project_val_arry[i] != 0)//进入字符区了
{
inBlock = true;
startIndex = i;
}
else if (project_val_arry[i] == 0 && inBlock)//进入空白区了
{
endIndex = i;
inBlock = false;
Mat roiImg = src(Rect(startIndex, 0, endIndex + 1 - startIndex, h));
roiList.push_back(roiImg);
}
}
return 0;
}
/*水平投影*/
int horizontal_projection(const Mat& src, vector<Mat>& coiList)
{
//step1. 计算水平投影白色点数量
int w = src.cols;
int h = src.rows;
vector<int> project_val_arry;
for (int i = 0; i < h; i++)//行
{
Mat i_im = src.row(i);
int num = countNonZero(i_im);
project_val_arry.push_back(num);
}
//显示
Mat hist_im(h, w, CV_8UC1, Scalar(255));
for (int i = 0; i < h; i++)
{
for (int j = 0; j < project_val_arry[i]; j++)
{
hist_im.ptr<unsigned char>(i)[w - 1 - j] = 0; //第h-1-j行第i个数据
}
}
//imshow("project_h", hist_im);
//step2. 字符分割
//vector<Mat> coiList;
int startIndex = 0;
int endIndex = 0;
bool inBlock = false;//是否遍历到了字符区内
for (int i = 0; i < h; ++i)
{
if (!inBlock && project_val_arry[i] != 0)//进入字符区了
{
inBlock = true;
startIndex = i;
}
else if (project_val_arry[i] == 0 && inBlock)//进入空白区了
{
endIndex = i;
inBlock = false;
Mat coiImg = src(Rect(0, startIndex, w, endIndex + 1 - startIndex));
coiList.push_back(coiImg);
}
}
return 0;
}
【搭建模板库】
/*搭建模板库*/
vector<Mat> model_v;
vertical_projection(conimg, model_v);
vector<Mat>model_h;
for (int i = 0; i < model_v.size(); i++)
{
horizontal_projection(model_v[i],model_h);
}
for (int j = 0; j < model_h.size(); j++)
{
char model_name[100];
resize(model_h[j], model_h[j], Size(150, 300), 0, 0, INTER_NEAREST);
sprintf_s(model_name,"E:/C语言/opencv/基于opencv垂直投影法的图像数字识别/数字/%d.png",j);
imwrite(model_name,model_h[j]);
}
【模板库载入】
/*载入模板库*/
vector<Mat> img_model;
for (int i = 0; i < 10; i++)
{
char model_name[100];
sprintf_s(model_name, "E:/C语言/opencv/基于opencv垂直投影法的图像数字识别/数字/%d.png", i);
Mat model_img=imread(model_name);
img_model.push_back(model_img);
}
【保存分割图像】
/*保存分割图像*/
vector<Mat> img_segh;
horizontal_projection(conimg, img_segh);
vector<Mat> img_segv;
for (int i = 0; i < img_segh.size(); i++)
{
vertical_projection(img_segh[i],img_segv);
}
for (int j = 0; j < img_segv.size(); j++)
{
resize(img_segv[j], img_segv[j], img_model[0].size(), 0, 0, INTER_NEAREST);
char seg_name[100];
sprintf_s(seg_name, "E:/C语言/opencv/基于opencv垂直投影法的图像数字识别/切割图像/%d.png", j);
imwrite(seg_name, img_segv[j]);
}
【载入分割图像】
/*载入分割图像*/
vector<Mat> img_i;
for (int i = 0; i < img_segv.size(); i++)
{
char i_name[100];
sprintf_s(i_name, "E:/C语言/opencv/基于opencv垂直投影法的图像数字识别/切割图像/%d.png", i);
Mat i_img = imread(i_name);
img_i.push_back(i_img);
}
【图像识别】
图像识别采用欧氏距离判断两个图像的相似度,欧氏距离越小,图像相似度越高。
/*图像特征*/
void getFeature(Mat m, float *a)
{
int W, H;//存储图像m的宽和高
int i, j;
float b[25];
W = m.cols;
H = m.rows;
for (i = 0; i < 25; i++)
b[i] = 0;
//将图像分为5×5个子块,计算每个子块内所有像素的平均值
for(i = 0; i < H;i++ )
{
for(j=0;j<W;j++)
if (m.at<uchar>(i, j) == 255)
{
b[i / (W / 5) * 5 + j / (H / 5)]++;
}
}
//计算当前像素块的平均值
for (i = 0; i < 25; i++)
{
a[i] = b[i] / ((W / 5)*(H / 5));
}
}
/*欧氏距离*/
float ouDistance(float a[25], float b[25])
{
int i;
float distance = 0;
//欧氏距离计算公式
for (i = 0; i < 25; i++)
distance += (a[i] - b[i])*(a[i] - b[i]);
distance = sqrt(distance);
return distance;
}
vector<int> seq;//顺序存放识别结果
for (int i = 0; i < img_i.size(); i++)
{
vector<float>distance;//顺序存放欧氏距离
float a[25], b[25]; //存储图像像素平均值
getFeature(img_i[i], a);
for (int j = 0; j < img_model.size(); j++)
{
//计算两个图片的差值
getFeature(img_model[j], b);
distance.push_back(ouDistance(a, b));
}
float min = 0;
int min_seq = 0;//记录最小的欧氏距离对应的数字
min = distance[0];
for (int k = 0; k < distance.size(); k++)
{
if (distance[k] < min)
{
min = distance[k];
min_seq = k;
}
}
seq.push_back(min_seq);
}
//输出结果
cout << "识别结果为:";
for (int i = 0; i < seq.size(); i++)
{
cout << seq[i];
}
cout << endl;
【内容总结】
1、图像载入:imread
2、图像灰度化:cvtColor
3、图像二值化:threshold
4、图像轮廓:寻找轮廓findContours、绘制轮廓drawContours
5、图像分割:垂直投影法、水平投影法
6、图像识别:欧氏距离与模板匹配
【参考文章】
https://blog.csdn.net/sss_369/article/details/89857499
https://blog.csdn.net/weixin_41743247/article/details/90635931
https://blog.csdn.net/m0_37543178/article/details/82658020
OpenCV编程案例详解-中国工信出版集团-李立宗