opencv图像识别数字的简单实例

嵌入式设计应用

133人已加入

描述

  opencv基本图片操作:

  因为opencv有2.0 和 3.0 的版本区别,所以网上搜到的函数或类型都是两种格式,建议用新版的,什么impImage* 类型的都是2.0版本的写法,我全部使用的是Mat。一定要统一好,不要一会新的一会旧的,会报错的。

  读图片imread,显示imshow,等待waitKey等等,这些要先熟悉

  opencv的强大之处在于几乎所有的图像操作它都有现成的函数可供调用,非常方便。多谷歌,一定会有函数已经实现了你想完成的功能。

  二值化:不论是原图还是有划痕或噪点的图,背景都不干净,这对识别的影响还是挺不好的,所以要先二值化,把黑白像素点区分的开一些。但是图片右侧明显要比左侧更暗,所以在阈值选取的时候比较难办,很难用一个固定的值将两部分图像都二值化得很理想,所以就用到了逼格更高的自适应二值化(adaptiveThreshold),tips:二值化前先直方图均衡一下效果会更好。

  中值滤波:针对有噪点和有划痕的图像,中值滤波是非常好的处理方案,中值的参数可调,可以很好的消除噪音的影响。缺点就是参数不好调啊,调的想死。。

  模板匹配:模板的来源可以是自己从待识别的图片中抠图,不过我们作业提供了模板图片,所以这一步就可以省掉了。opencv提供了非常强大的matchTemplate函数,可以将给定图片与模板按照你规定的计算方法计算一个相似度的值,并将对应的坐标存储下来,你需要做的只是将值比较大(或小,与你规定计算相似度的函数有关)的图像框出来即可

  窗口扫描:为了提高识别率,我设定了一个窗口对原图进行扫描,扫描窗口的移动设定了一点规则,就是如果前一个窗口没有匹配到数字就微调窗口位置,如果匹配到数字就将窗口左轴移动到匹配到的数字的右侧,再重复扫描。

  以下是基于OpenCV实现简单的数字识别。这里以游戏Angry Birds为例,通过以下几个主要步骤对其中右上角的分数部分进行自动识别。

 

  1. 学习分类器

  根据训练样本,选取模型训练产生数字分类器。这里的样本可以是通用的数字样本库(如NIST等),也可以是针对应用场景而制作的专门训练样本。前者优在泛化性,后者强在准确率,当然常用做法是将这两者结合,即在通用数字库基础上做修改。另外这里由于模式并不复杂,计算量也不大,所以不对样本进行特征提取,对原始样本作简单变换后直接作为训练样本。

  具体地,首先是生成训练样本矩阵,一般样本是以二维矩阵的方式存在文件当中,现在要将它们读出来,进行适当的预处理,然后生成OpenCV能理解的数据结构。

  train_X = cvCreateMat(sample_num * class_num, size * size, CV_32FC1);

  train_Y = cvCreateMat(sample_num * class_num, 1, CV_32FC1);

  for(i = 0; i 《 class_num; i++){

  for(j = 0; j 《 sample_num; j++){

  src_image = cvLoadImage(file,0);

  pimage = preprocessing(src_image, size, size);

  。。。

  cvGetRow(train_X, &row, i * sample_num + j);

  row_vec = cvReshape(&data, &mathdr, 0, 1);

  cvCopy(row_vec, &row, NULL);

  。。。

  cvGetRow(train_Y, &row, i * sample_num + j);

  cvSet(&row, cvRealScalar(i));

  }

  }

  训练样本中的数字位置形态各异,因此读入时需要进行规整化。主要方法是先找到数字的边界框,然后以宽和高中大的一边为基准进行缩放和拉伸,从而使得其可以占满整个表示单个样本的矩阵。

  IplImage preprocessing(IplImage* img, int w, int h){

  。。。

  bb = findBoundingBox(img);

  cvGetSubRect(img, &data, cvRect(bb.x, bb.y, bb.width, bb.height));

  size = (bb.width 》 bb.height) ? bb.width : bb.height;

  res = cvCreateImage(cvSize(size, size), 8, 1);

  x = floor((float)(size - bb.width) / 2.0f);

  y = floor((float)(size - bb.height) / 2.0f);

  cvGetSubRect(res, &subdata, cvRect((int)x, (int)y, bb.width, bb.height));

  cvCopy(&data, &subdata, NULL);

  ret = cvCreateImage(cvSize(w, h), 8, 1);

  cvResize(res, ret, CV_INTER_NN);

  return *ret;

  }

  假设单个样本可表示为0/1矩阵,那findBoundingBox()只要从x和y方向分别扫描最大最小的非0值就可以了。 训练样本准备好后,在OpenCV中创建相应的分类器非常方便。这里用的是KNN,当然除了KNN外还有其它很多封装好的分类器(如NN, SVM等)。

  knn = new CvKNearest(train_X, train_Y, 0, false, K);

  2. 图像预处理

  前面通过学习产生了分类器,但我们输入图像中的数字并不能直接作为测试输入。图像中的数字笔画有时并不规整,还可能相互重叠。因为本文例子为了简化用的是屏幕截图,所以位置形变校正,色彩亮度校正等等都省去了,但仍需要一些简单处理。下面先对输入图像进行一把简单的预处理,主要目的是将数字之间两两分开。方法很简单,首先将图像转成二值图,然后腐蚀一把,数字之间就分离得比较开了,这样便于我们下一步分割和识别。这样做还有个好处,就是把其余的噪声也顺带去掉了。

  cvtColor(input, out_img, CV_BGR2GRAY);

  threshold(out_img, out_img, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);

  。。。

  erode(out_img, out_img, elem);

  结果:

  

  3. 图像分割

  接下来,就可以对图像进行分割了。由于我们的分类器只能对数字一个一个地识别,所以首先要把每个数字分割出来。基本思想是先用findContours()函数把基本轮廓找出来,然后通过简单验证以确认是否为数字的轮廓。对于那些通过验证的轮廓,接下去会用boundingRect()找出它们的包围盒。

  vector《 vector《 Point》 》 contours;

  findContours(contour_img, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);

  vector《vector《Point》 》::iterator it = contours.begin();

  while (it!=contours.end()) {

  RotatedRect rect = minAreaRect(Mat(*it));

  if(verifyRect(rect)){

  ++it; // A valid rectangle found

  } else {

  it= contours.erase(it);

  }

  }

  。。。

  vector《Rect》 boundRect(contours.size());

  for (int i = 0; i 《 contours.size(); ++i) {

  Scalar color = Scalar(200, 200, 200);

  boundRect[i] = boundingRect(Mat(contours[i]));

  rectangle(out_img, boundRect[i].tl(), boundRect[i].br(), color, 0.2, 8, 0);

  CvRect roi = CvRect(boundRect[i]);

  IplImage orig = out_img;

  IplImage *res = cvCreateImage(cvSize(roi.width, roi.height), orig.depth, orig.nChannels);

  cvSetImageROI(&orig, roi);

  cvCopy(&orig, res);

  cvResetImageROI(&orig);

  IplImage *bininv_img;

  bininv_img = cvCreateImage(cvSize(128, 128), IPL_DEPTH_8U, 1);

  cvResize(res, bininv_img);

  cvThreshold(bininv_img, bininv_img, 100, 255, CV_THRESH_BINARY_INV);

  int ret = do_ocr(bininv_img);

  res_elem elem;

  elem.num = ret;

  elem.xpos = boundRect[i].tl().x;

  res_vec.push_back(elem);

  。。。

  }

  结果:

  

  4. 应用分类器

  分割完后就可以应用我们前面训练好的分类器对分割结果进行识别了。当然,如果感觉结果不满意,可以将分类错误的样本加上正确的标签后放入训练样本重新生成分类器,使得分类器能够有更好的识别率。上一步中的do_ocr()函数就是利用先前训练好的分类器识别单个数字。注意训练样本进行过怎么样的预处理,这里也一样要做。

  int do_ocr(IplImage *img)

  {

  。。。

  pimage = preprocessing(img, size, size);

  。。。

  cvGetSubRect(pimage, &data, cvRect(0, 0, size, size));

  CvMat mathdr, *vec;

  vec = cvReshape(&data, &mathdr, 0, 1);

  ret = knn-》find_nearest(vec, K, 0, 0, nearest, 0);

  return (int)ret;

  }

  5. 后期处理

  因为分割图像时查找数字轮廓并不保证是按顺序来的,所以这儿要将识别结果按分割时输出的包围盒位置信息进行排序,最后将它们转换成数字输出

  sort(res_vec.begin(), res_vec.end(), sort_func);

  int j, num = 0;

  for (j = 0; j 《 res_vec.size(); ++j) {

  num = num * 10 + res_vec[j].num;

  }

  char resbuf[256];

  sprintf(resbuf, “%d”, num);

  putText(show_img, resbuf, Point(OUTPUT_X, OUTPUT_Y), FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0, 255, 0), 2);

  imshow(“show”, show_img);

  结果:

  

  opencv3实现简单的数字图像识别(KNN)完整代码如下:

  // knnrecognizenum.cpp:使用knn识别手写数字

  //

  #include “stdafx.h”

  #include《iostream》

  #include《opencv2mlml.hpp》

  #include《highguihighgui.hpp》

  using namespace std;

  using namespace cv;

  using namespace cv::ml;

  int main()

  {

  Mat img = imread(“digits.png”, 0);

  int boot = 20;

  int m = img.rows / boot; int n = img.cols / boot;

  Mat data, labels; //data和labels分别存放

  //截取数据的时候要按列截取

  for (int i = 0; i 《 n; i++)

  {

  int colNum = i * boot;

  for (int j = 0; j 《 m; j++)

  {

  int rowNum = j * boot;

  Mat tmp;

  img(Range(rowNum, rowNum + boot), Range(colNum, colNum + boot)).copyTo(tmp);

  data.push_back(tmp.reshape(0, 1)); //将图像转成一维数组插入到data矩阵中

  labels.push_back((int)j / 5); //将图像对应的标注插入到labels矩阵中

  }

  }

  data.convertTo(data, CV_32F);

  int sampleNum = data.rows;

  int trainNum = 3000;

  Mat trainData, trainLabel;

  trainData = data(Range(0, trainNum), Range::all());

  trainLabel = labels(Range(0, trainNum), Range::all());

  //使用KNN算法

  int k = 5;

  Ptr《TrainData》 tData = TrainData::create(trainData,ROW_SAMPLE, trainLabel); //ROW_SAMPLE表示一行一个样本

  Ptr《KNearest》 model = KNearest::create();

  model-》setDefaultK(k); model-》setIsClassifier(true);

  model-》train(tData);

  //预测分类

  /* Mat sample = data.row(500);

  float res = model-》predict(sample);

  cout 《《 “预测结果是:”《《 res 《《 endl;*/ //预测一个的代码

  double train_hr=0, test_hr=0;

  Mat response;

  for (int i = 0; i 《 sampleNum; i++)

  {

  Mat sample = data.row(i);

  float r = model-》predict(sample);

  r = abs(r - labels.at《int》(i));

  if (r 《= FLT_EPSILON)// FLT_EPSILON表示最小的float浮点数,小于它,就是等于0

  r = 1.f;

  else

  r = 0.f;

  if (i 《 trainNum)

  train_hr=train_hr+r;

  else

  test_hr=test_hr + r;

  }

  //cout 《《 train_hr 《《 “ ” 《《 test_hr 《《 endl;

  cout 《《 “KNN模型在训练集上的准确率为” 《《 train_hr / trainNum * 100 《《 “%,在测试集上的准确率为” 《《 test_hr / (data.rows-trainNum)*100《《“%”《《endl;

  system(“pause”);

  return 0;

  }

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分