这是基于opencv写的数字识别程序 对于记事本里的宋体常规数字能够完美的识别
思路: 放大图片→灰度化→二值化→开运算→寻找外轮廓→轮廓排序→遍历像素与模板匹配→得到数字
最后为了验证识别率,读入txt文件与识别图片后的输出结果对比
总结:
1.图片放大可以把各个数字分开一点,防止数字黏在一起导致找轮廓时出错
2.开运算,进一步把各个数字分开 开运算(膨胀->腐蚀)//去除图像中较小区域
3.寻找外轮廓,必须是外轮廓,不可以找所有轮廓,这样就可以等到各个数字
4.根据轮廓找出各个轮廓的最小矩形
5.根据最小矩形左上角的坐标为各个矩形排序,这样就可以知道各个字符输出顺序了
6,.把事先分割好的模板图片进行匹配(把这个程序改改就可以成一个分割数字的程序了)
7.最后读入txt文件与图片输出结果对比
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
#include <fstream>
#include <cassert>
#include <string>
using namespace std;
using namespace cv;
const int NUM = 10000;
#define WINDOW_NAME "二值图"
int g_nThreshold = 200;
static void on_Threshold(int, void*);
void getNumber();//分割字符
char getNumChar(Mat&);//识别单个字符图片
Mat g_srcImage, gray, image_threshold;
Mat open;
char result_num[NUM];//txt文件中的数字
char result_true[NUM];//识别后的数字
/*读取txt文件*/
void readTxt(string file)
{
ifstream infile;
infile.open(file.data()); //将文件流对象与文件连接起来
assert(infile.is_open()); //若失败,则输出错误消息,并终止程序运行
int i = 0;
char c;
while (!infile.eof())
{
infile >> c;
result_true[i] = c;
result_true[i + 1] = '\0';
i++;
}
infile.close();
}
int main()
{
readTxt("123.txt");
cout << "文件值为:";
for (unsigned int i = 0; i < strlen(result_true)-1; i++)//读取txt输出数字
{
cout << result_true[i];
}
cout << endl;
g_srcImage = imread("1.png");
//放大原图,图片太小数字可能会连在一起
resize(g_srcImage, g_srcImage, Size(g_srcImage.cols * 2, g_srcImage.rows * 2), 0, 0, INTER_LINEAR);
cvtColor(g_srcImage, gray, COLOR_BGR2GRAY);
namedWindow(WINDOW_NAME, WINDOW_AUTOSIZE);
createTrackbar("阈值", WINDOW_NAME, &g_nThreshold, 255, on_Threshold);
on_Threshold(g_nThreshold, 0);
waitKey(0);
return 0;
}
/*阈值操作*/
static void on_Threshold(int, void*)
{
threshold(gray, image_threshold, g_nThreshold, 255, 1);
imshow(WINDOW_NAME, image_threshold);
Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));
morphologyEx(image_threshold, open, MORPH_OPEN, element);//开运算,防止图片连在一起
getNumber();
}
/*单字符识别*/
char getNumChar(Mat &temp)
{
int min = 10000;
int min_i = 0;
int total = 0;
for (int n = 0; n < 10; n++)//n对应图片n.jpg
{
total = 0;
char a[100];
sprintf(a, "number/%d.jpg", n);//读取number模板文件夹图片,名字对应数字
string name = a;
Mat match = imread(name);
cvtColor(match, match, COLOR_BGR2GRAY);
threshold(match, match, 254, 255, 1);
resize(temp, temp, match.size(), 0, 0, INTER_LINEAR);//转换图片大小和模板图片一样
for (int i = 0; i < match.rows; i++)//遍历所有像素与n.jpg对比
{
uchar *data_match = match.ptr<uchar>(i);
uchar *data_temp = temp.ptr<uchar>(i);
for (int j = 0; j < match.cols; j++)
{
if (*data_match == *data_temp)
{
total++;
}
*data_match++;
*data_temp++;
}
}
//读入的图片二值化后与原图二值化相同像素为0
//读入的图片二值化与程序运行过程中二值化的产生图片刚好相反
//所以相同的像素越少图片相似度越高
//在这里match二值化后与temp二值化图相反即黑白正好颠倒了
if (total < min)
{
min = total;
min_i = n;
}
}
char a = min_i+'0';
return a;
}
/*字符分割*/
void getNumber()
{
Mat image_threshold_clone = image_threshold.clone();
vector<vector<Point>>contours;
vector<Vec4i>hierarchy;
findContours(image_threshold_clone, contours, hierarchy,
CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);//找出图片外轮廓,不要连内轮廓也找出来
int contour_size = contours.size();
int average_height = 0;
int count = 0;
Rect rc[NUM];
int min_y =100000;
int max_y = 0;
//找出字符平均高度,最上方的数字的y,最下方的数字的y
for (int i = 0; i < contour_size; i++)
{
rc[i] = boundingRect(contours.at(i));
if (rc[i].y<min_y)
{
min_y = rc[i].y;
}
if (rc[i].y>max_y)
{
max_y = rc[i].y;
}
average_height += rc[i].height;
}
average_height /= contour_size;
Rect sort_rc[NUM];
Rect rc_y[NUM];
int _t = 0;
//由于轮廓顺序不是字符顺序所以要排序,主要根据矩形左上方坐标排序
//排序从上到下,从左到右,每隔一个字符高度获取一次,当超出图片边界时停止
for (int n = 0;; n++)
{
int t = 0;
for (int i = 0; i < contour_size; i++)
{
if (min_y - average_height*0.5 < rc[i].y&& rc[i].y < min_y + average_height*0.5)
{
rc_y[t] = rc[i];
t++;
}
}
if (min_y>max_y)
{
break;
}
if (t == 0)
{
_t += t;
min_y += average_height;
continue;
}
for (int i = 0; i < t; i++)
{
for (int j = i+1; j < t; j++)
{
if (rc_y[i].x>rc_y[j].x)
{
Rect temp = rc_y[i];
rc_y[i] = rc_y[j];
rc_y[j] = temp;
}
}
}
for (int i = 0; i < t; i++)
{
sort_rc[i + _t] = rc_y[i];
}
_t += t;
min_y += average_height;
}
//把排序后的图片一一识别
for (int i = 0; i < contour_size; i++)
{
Mat ROI = image_threshold(sort_rc[i]);
resize(ROI, ROI, Size(40, 56), 0, 0, INTER_LINEAR);
result_num[i] = getNumChar(ROI);
result_num[i + 1] = '\0';
}
//与txt文件的数字进行比较
int equal_num = 0;
cout << "结果值为:";
for (unsigned int i = 0; i < strlen(result_num); i++)
{
cout << result_num[i];
if (result_num[i] == result_true[i])
equal_num++;
}
cout << "\n字符数为:" << contour_size << endl;
cout << "正确率为:" << (double)equal_num / contour_size *100<<"%"<< endl;
}
以下为输出结果:
当然识别数字后你想干嘛 就是你自己的事了