使用Opencv进行字符提取

一.使用Opencv进行轮廓检测!

所需函数:

1. cvFindContours

函数功能:从二值图像中检索轮廓,并返回检测到的轮廓的个数

函数原型:


    
    
  1. int) cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq** first_contour,
  2. int header_size = sizeof(CvContour),
  3. int mode = CV_RETR_LIST,
  4. int method = CV_CHAIN_APPROX_SIMPLE,
  5. CvPoint offset = cvPoint( 0, 0));

参数介绍:

CvArr* image:要检测轮廓的图像,必须为二值图
    
    
CvMemStorage* storage:存储轮廓的容器
    
    
 CvSeq** first_contour:输出参数,用于存储指向第一个外接轮廓。是一个链表,h_next用于指向下一个外接轮廓
    
    

    
    
  1. int header_size:header序列的尺寸.如果选择method = CV_CHAIN_CODE, 则header_size >= sizeof(CvChain);其他,则
  2. header_size >= sizeof(CvContour)。

    
    
  1. int mode:
  2. CV_RETR_EXTERNAL:只检索最外面的轮廓;
  3. CV_RETR_LIST:检索所有的轮廓,并将其放入 list中;
  4. CV_RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
  5. CV_RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次。

    
    
  1. int method
  2. 边缘近似方法(除了CV_RETR_RUNS使用内置的近似,其他模式均使用此设定的近似算法)。可取值如下:
  3. CV_CHAIN_CODE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
  4. CV_CHAIN_APPROX_NONE:将所有的连码点,转换成点。
  5. CV_CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。
  6. CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS:使用the flavors of Teh-Chin chain近似算法
  7. 的一种。
  8. CV_LINK_RUNS:通过连接水平段的 1,使用完全不同的边缘提取算法。使用CV_RETR_LIST检索模式能使用此方法。

    
    
CvPoint offset
     
     
偏移量,用于移动所有轮廓点。当轮廓是从图像的ROI提取的,并且需要在整个图像中分析时,这个参数将很有用。
    
    

返回值:检测到的轮廓数量

2.cvThreshold

函数功能:对单通道数组应用固定阈值操作。该函数的典型应用是对灰度图像进行阈值操作得到二值图像。

函数原型:


    
    
  1. void cvThreshold
  2. ( const CvArr* src,
  3. CvArr* dst,
  4. double threshold,
  5. double max_value,
  6. int threshold_type );

参数介绍:

扫描二维码关注公众号,回复: 11253869 查看本文章

    
    
  1. src:原始数组 (单通道 , 8-bit of 32-bit 浮点数)。
  2. dst:输出数组,必须与 src 的类型一致,或者为 8-bit。
  3. threshold:阈值
  4. max_value:使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值。
  5. threshold_type:阈值类型
  6. threshold_type=CV_THRESH_BINARY:如果 src(x,y)>threshold ,dst(x,y) = max_value; 否则,dst(x,y)= 0;
  7. threshold_type=CV_THRESH_BINARY_INV:如果 src(x,y)>threshold,dst(x,y) = 0; 否则,dst(x,y) = max_value.
  8. threshold_type=CV_THRESH_TRUNC:如果 src(x,y)>threshold,dst(x,y) = max_value; 否则dst(x,y) = src(x,y).
  9. threshold_type=CV_THRESH_TOZERO:如果src(x,y)>threshold,dst(x,y) = src(x,y) ; 否则 dst(x,y) = 0
  10. threshold_type=CV_THRESH_TOZERO_INV:如果 src(x,y)>threshold,dst(x,y) = 0 ; 否则dst(x,y) = src(x,y).

3. cvCreateMemStorage

函数功能:用来创建一个内存存储器,来统一管理各种动态对象的内存

函数原型:

CvMemStorage*  cvCreateMemStorage( int block_size = 0);
    
    

参数介绍:

int block_size = 0:对应内存器中每个内存块的大小,为0时内存块默认大小为64k。
    
    

返回值:返回一个新创建的内存存储器指针。

4. cvBoundingRect

函数功能:计算点集的最外面(up-right)矩形边界。

函数原型:

CvRect  cvBoundingRect( CvArr* points, int update = 0);
    
    

参数介绍:

CvArr* points:二级点矩阵
    
    

    
    
  1. int update:
  2. 更新标识。
  3. 下面是轮廓类型和标识的一些可能组合:
  4. update= 0, contour ~ CvContour*: 不计算矩形边界,但直接由轮廓头的 rect 域得到。
  5. update= 1, contour ~ CvContour*: 计算矩形边界,而且将结果写入到轮廓头的 rect 域中 header。
  6. update= 0, contour ~ CvSeq* or CvMat*: 计算并返回边界矩形。
  7. update= 1, contour ~ CvSeq* or CvMat*: 产生运行错误 (runtime error is raised)。
  8. 函数 cvBoundingRect 返回二维点集的最外面 (up-right)矩形边界。 [ 1]

所需结构体:

1. CvMemStorage

结构体介绍:


    
    
  1. typedef struct CvMemStorage
  2. {
  3. int signature;
  4. CvMemBlock* bottom; /**< 第一分配块。 */
  5. CvMemBlock* top; /**< 当前内存块——堆栈的顶部。 */
  6. struct CvMemStorage* parent; /**< 根据需要从父块获取新的块。 */
  7. int block_size; /**< 块的大小。 */
  8. int free_space; /**< 当前块中的剩余空闲空间。 */
  9. }CvMemStorage;

2. CvSeq


    
    
  1. typedef struct CvSeq
  2. {
  3. CV_SEQUENCE_FIELDS()
  4. } CvSeq;
  5. #define CV_SEQUENCE_FIELDS()
  6. int flags; /* micsellaneous flags */
  7. int header_size; /* 序列头的大小 */
  8. struct CvSeq* h_prev; /* 前一个序列 */
  9. struct CvSeq* h_next; /* 后一个序列 */
  10. struct CvSeq* v_prev; /* 第二级前一个序列 */
  11. struct CvSeq* v_next; /* 第二级后一个序列 */
  12. int total; /* 元素的总个数 */
  13. int elem_size; /* 元素的尺寸 */
  14. char* block_max; /* 上一块的最大块 */
  15. char* ptr; /* 当前写指针 */
  16. int delta_elems; /*序列中快的大小
  17. (序列粒度) */
  18. CvMemStorage* storage; /*序列的存储位置 */
  19. CvSeqBlock* free_blocks; /* 未分配的块序列 */
  20. CvSeqBlock* first; /* 指向第一个快序列 */
相关理论知识:在图像处理中阈值是什么意思?


二. 开始编写代码

编写代码前需要准备一张实验图像!


可自行保存到本地,png格式!

1. 加载测试图像


    
    
  1. //打开要识别字符的图像
  2. IplImage *image = cvLoadImage( "d:\\1.png");
  3. if (image == NULL){
  4. printf( "错误:无法打开该图像文件!");
  5. }

2.图像二值化


    
    
  1. //转换到灰度图
  2. IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);
  3. cvCvtColor(image, img_gray, CV_BGR2GRAY);
  4. //二值化
  5. IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);
  6. cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);

3. 在二值化图像中寻找轮廓


    
    
  1. //寻找轮廓
  2. CvMemStorage *pStorage = cvCreateMemStorage( 0);
  3. CvSeq *pConInner = NULL;
  4. int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),
  5. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

4. 获取轮廓在图像中的矩阵坐标


    
    
  1. //根据轮廓坐标使用方框标出来
  2. for ( int i = 0; i < num; i++)
  3. {
  4. CvRect rc = cvBoundingRect(pConInner);
  5. cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB( 255, 0, 0));
  6. pConInner = pConInner->h_next;
  7. }

5. 显示图像


    
    
  1. //显示图像
  2. cvNamedWindow( "image", 0);
  3. cvNamedWindow( "image_gray", 0);
  4. cvNamedWindow( "img_value", 0);
  5. cvShowImage( "image", image);
  6. cvShowImage( "image_gray", img_gray);
  7. cvShowImage( "img_value", img_value);
  8. cvWaitKey( 0);

运行结果:


二值化图像是很利用我们做轮廓检测的,因为二值化的图像中不会有其他掺杂颜色在里面影响轮廓检测准确!

完整代码:


    
    
  1. //打开要识别字符的图像
  2. IplImage *image = cvLoadImage( "d:\\1.png");
  3. if (image == NULL){
  4. printf( "错误:无法打开该图像文件!");
  5. }
  6. //转换到灰度图
  7. IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);
  8. cvCvtColor(image, img_gray, CV_BGR2GRAY);
  9. //二值化
  10. IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);
  11. cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);
  12. //寻找轮廓
  13. CvMemStorage *pStorage = cvCreateMemStorage( 0);
  14. CvSeq *pConInner = NULL;
  15. int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),
  16. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
  17. //根据轮廓坐标使用方框标出来
  18. for ( int i = 0; i < num; i++)
  19. {
  20. CvRect rc = cvBoundingRect(pConInner);
  21. cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB( 255, 0, 0));
  22. pConInner = pConInner->h_next;
  23. }
  24. //显示图像
  25. cvNamedWindow( "image", 0);
  26. cvNamedWindow( "image_gray", 0);
  27. cvNamedWindow( "img_value", 0);
  28. cvShowImage( "image", image);
  29. cvShowImage( "image_gray", img_gray);
  30. cvShowImage( "img_value", img_value);
  31. cvWaitKey( 0);

三. 字符提取

已经将字符轮廓用矩形给标出来了,那么接下来字符提取就较为简单了!

我们可以复用上面的代码,在绘制轮廓前加入:

IplImage* imgNo[9] = { NULL };
    
    

用于存储分割出来的图像

在使用cvCopy函数对其进行裁剪即可!


    
    
  1. //根据轮廓坐标使用方框标出来
  2. for ( int i = 0; i < num; i++)
  3. {
  4. CvRect rc = cvBoundingRect(pConInner);
  5. //cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!
  6. imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
  7. cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height)); //设置源图像ROI
  8. cvCopy(image, imgNo[i]); //裁剪,将裁剪的字符图像放入到imgNo数组中去
  9. pConInner = pConInner->h_next;
  10. }
最后在将其显示出来

     
     
  1. char a[ 9];
  2. for ( int i = 0; i < 9; ++i){
  3. sprintf(a, "%d", i);
  4. cvShowImage(a, imgNo[i]);
  5. }
运行结果:


完整代码:

    
    
  1. //打开要识别字符的图像
  2. IplImage *image = cvLoadImage( "d:\\1.png");
  3. if (image == NULL){
  4. printf( "错误:无法打开该图像文件!");
  5. }
  6. //转换到灰度图
  7. IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);
  8. cvCvtColor(image, img_gray, CV_BGR2GRAY);
  9. //二值化
  10. IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);
  11. cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);
  12. //寻找轮廓
  13. CvMemStorage *pStorage = cvCreateMemStorage( 0);
  14. CvSeq *pConInner = NULL;
  15. int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),
  16. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
  17. IplImage* imgNo[ 9] = { NULL };
  18. //根据轮廓坐标使用方框标出来
  19. for ( int i = 0; i < num; i++)
  20. {
  21. CvRect rc = cvBoundingRect(pConInner);
  22. //cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!
  23. imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
  24. cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height)); //设置源图像ROI
  25. cvCopy(image, imgNo[i]); //裁剪
  26. pConInner = pConInner->h_next;
  27. }
  28. //显示图像
  29. cvNamedWindow( "image", 0);
  30. cvNamedWindow( "image_gray", 0);
  31. cvNamedWindow( "img_value", 0);
  32. char a[ 9];
  33. for ( int i = 0; i < 9; ++i){
  34. sprintf(a, "%d", i);
  35. cvShowImage(a, imgNo[i]);
  36. }
  37. cvShowImage( "image_gray", img_gray);
  38. cvShowImage( "img_value", img_value);
  39. cvWaitKey( 0);

四. 文字识别

在文字识别,你可以使用其他方法,本博客中使用最简单的直方图匹配,后续的车牌识别中,我会写一篇通过Opencv训练分练器,通过样本文件来识别提高识别率!

相关链接: 使用Opencv绘制灰度直方图/对比

注意前提,你要有模板图像,这里我把样本图像分享给各位!


由于模板图像较多,我上传到csdn上,供需要的人下载正规字符模板,下载需要两积分,如果不想下载可以使用下列图像,然后复用上面的代码裁剪出字符然后使用cvSeveImage函数保存到本地即可!


然后使用下列代码:


    
    
  1. //打开要识别字符的图像
  2. IplImage *image = cvLoadImage( "d:\\1.png");
  3. if (image == NULL){
  4. printf( "错误:无法打开该图像文件!");
  5. }
  6. //转换到灰度图
  7. IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);
  8. cvCvtColor(image, img_gray, CV_BGR2GRAY);
  9. //二值化
  10. IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);
  11. cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);
  12. //寻找轮廓
  13. CvMemStorage *pStorage = cvCreateMemStorage( 0);
  14. CvSeq *pConInner = NULL;
  15. int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),
  16. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
  17. IplImage* imgNo[ 53] = { NULL };
  18. //根据轮廓坐标使用方框标出来
  19. for ( int i = 0; i < num; i++)
  20. {
  21. CvRect rc = cvBoundingRect(pConInner);
  22. //cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!
  23. imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
  24. cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height)); //设置源图像ROI
  25. cvCopy(image, imgNo[i]); //裁剪
  26. pConInner = pConInner->h_next;
  27. }
  28. //显示图像文件
  29. char a[ 53];
  30. for ( int i = 0; i < 53; ++i){
  31. sprintf(a, "d:\\img\\%d.jpg", i);
  32. cvSaveImage(a, imgNo[i]);
  33. }
  34. cvWaitKey( 0);

运行之后你的D盘下就会出现一个img文件里面包含了所有的正规字符模板图!

开始编写识别代码

这里我们不复用上面的代码,重新编写一份,因为模板匹配算法有些地方有问题,所以这里重写一遍给大家注明一下!

注意上面的模型建议用在其他识别算法上,不建议用在模板图片上,matchTemplate模板匹配函数要求尺寸一致,这里为了方便就不编写尺寸变换的代码了,只是做一个示例,所以我就直接用这张图的字符样本做模板了,


拿出来分享给各位:

      

有需要可自行保存到本地,jpg格式!

下面开始编写代码:

1. 定义一个结构体用于关联图像与字符


    
    
  1. typedef struct p_image{
  2. char c_name;
  3. CvHistogram *img_zft;
  4. }p_image;

2. 打开测试图像


    
    
  1. //打开要识别字符的图像
  2. IplImage *image = cvLoadImage( "d:\\1.png");
  3. if (image == NULL){
  4. printf( "错误:无法打开该图像文件!");
  5. }

3. 图像二值化


    
    
  1. //转换到灰度图
  2. IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);
  3. cvCvtColor(image, img_gray, CV_BGR2GRAY);
  4. //二值化
  5. IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);
  6. cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);

4. 寻找轮廓


    
    
  1. //寻找轮廓
  2. CvMemStorage *pStorage = cvCreateMemStorage( 0);
  3. CvSeq *pConInner = NULL;
  4. int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),
  5. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

5. 申请变量用于字符提取

IplImage* imgNo[9] = { NULL };
    
    

6. 申请变量用于转化字符提取的直方图

IplImage *i1[9] = { NULL };
    
    

7. 字符裁剪


    
    
  1. //字符提取
  2. for ( int i = 0; i < num; i++)
  3. {
  4. CvRect rc = cvBoundingRect(pConInner);
  5. //cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!
  6. imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
  7. cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height)); //设置源图像ROI
  8. cvCopy(image, imgNo[i]); //裁剪
  9. pConInner = pConInner->h_next;
  10. }

8. 重新保存与读取

为什么要加这段重复代码?

原因:经过我测试,Opencv里面的数据是没有经过压缩的,所以当你打开模板图时模板图格式为jpg,经过jpeg算法压缩,当你使用直方图比对时比对会有差异,注意加这段代码仅针对直方图的情况来做修改的!

其他匹配方法无需增加这段代码!

吐槽一下:直方图匹配真的是太烂了,直方图只能用于计算!

//jpg格式压缩!!


    
    
  1. //保存与读取
  2. char name[ 256];
  3. for ( int i = 0; i < 9; ++i){
  4. sprintf(name, "d:\\img\\%d.jpg", i);
  5. cvSaveImage(name, imgNo[i]);
  6. }
  7. char b[ 256];
  8. for ( int i = 0; i < 9; ++i){
  9. sprintf(b, "d:\\img\\%d.jpg", i);
  10. imgNo[i] = cvLoadImage(b);
  11. }

9. 灰度图转换


    
    
  1. //灰度转换
  2. for ( int i = 0; i < 9; ++i){
  3. i1[i] = cvCreateImage(cvSize(imgNo[i]->width, imgNo[i]->height), 8, 1);
  4. cvCvtColor(imgNo[i], i1[i], CV_BGR2GRAY); //CV_BGR2GRAY
  5. }

10. 计算原图的直方图


    
    
  1. //计算原图的直方图
  2. int arr_size = 255; //定义一个变量用于表示直方图行宽
  3. float hranges_arr[] = { 0, 255 }; //图像方块范围数组
  4. float *phranges_arr = hranges_arr; //cvCreateHist参数是一个二级指针,所以要用指针指向数组然后传参
  5. //创建直方图
  6. CvHistogram *hist[ 9];
  7. for ( int i = 0; i < 9; ++i){
  8. hist[i] = cvCreateHist( 1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1); //创建一个一维的直方图,行宽为255,多维密集数组,方块范围为0-255,bin均化
  9. }
  10. //计算直方图
  11. for ( int i = 0; i < 9; ++i){
  12. cvCalcHist(&i1[i], hist[i], 0, 0);
  13. }

11. 加载模板图


    
    
  1. IplImage* abcd_img[ 9] = { NULL };
  2. char img_name[ 256] = { 0 };
  3. for ( int i = 0; i < 9; ++i){
  4. sprintf(img_name, "d:\\img\\%d.jpg", i);
  5. abcd_img[i] = cvLoadImage(img_name);
  6. }

12. 转换成灰度图


    
    
  1. //转换灰度图
  2. IplImage* abcd_img_hdt[ 9] = { NULL };
  3. for ( int i = 0; i < 9; ++i){
  4. abcd_img_hdt[i] = cvCreateImage(cvSize(abcd_img[i]->width, abcd_img[i]->height), 8, 1);
  5. cvCvtColor(abcd_img[i], abcd_img_hdt[i], CV_BGR2GRAY); //CV_BGR2GRAY
  6. }

13. 计算模板图的直方图


    
    
  1. //创建直方图
  2. CvHistogram *hist_abcd[ 9];
  3. for ( int i = 0; i < 9; ++i){
  4. hist_abcd[i] = cvCreateHist( 1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1); //创建一个一维的直方图,行宽为255,多维密集数组,方块范围为0-255,bin均化
  5. }
  6. //计算模板图的直方图
  7. for ( int i = 0; i < 9; ++i){
  8. cvCalcHist(&abcd_img_hdt[i], hist_abcd[i], 0, 0);
  9. }

14. 申请结构体,方便数据化管理


    
    
  1. //申请结构体
  2. p_image *p[ 9] = { NULL };
  3. for ( int i = 0; i <= num; ++i){
  4. p[i] = (p_image *) malloc( sizeof(p_image));
  5. }

15. 字符关联


    
    
  1. //字符关联
  2. char abcd[ 9] = { 'H', 'E', 'L', 'L', 'O', 'W', 'O', 'R', 'D' };
  3. for ( int i = 0; i <= num; ++i){
  4. p[i]->c_name = abcd[i];
  5. }

16. 结构体与模板直方图管理,形成结构化


    
    
  1. //直方图关联
  2. for ( int i = 0; i < num; ++i){
  3. p[i]->img_zft = hist_abcd[i];
  4. }

17. 匹配图像


    
    
  1. //匹配图像
  2. char j_c[ 9]; //用于存储匹配到的字符
  3. for ( int i = 0; i < num; ++i){
  4. for ( int j = 0; j < num; ++j){
  5. double Compare = cvCompareHist(hist[i], p[j]->img_zft, 0); //使用CV_COMP_CORREL方法进行对比
  6. if (Compare == 1.){ //100%正确 这里我们精准度调整高一点,因为直方图比对真的是太差劲了,相似度很高
			j_c[i] = p[j]->c_name;
    
    
}}}

18. 打印匹配结果


    
    
  1. printf( "检测到%d个字母,分别是", num);
  2. for ( int i = 0; i < num; ++i){
  3. printf( "%c", j_c[i]);
  4. if (i != 8){
  5. printf( ",");
  6. }
  7. }
  8. for ( int i = 0; i < num; ++i){
  9. p[i]->c_name = abcd[i];
  10. }

19. 显示图像


    
    
  1. //显示图像
  2. cvNamedWindow( "image", 0);
  3. cvNamedWindow( "image_gray", 0);
  4. cvNamedWindow( "img_value", 0);
  5. char a[ 9];
  6. for ( int i = 0; i < num; ++i){
  7. sprintf(a, "%d", i);
  8. cvShowImage(a, imgNo[i]);
  9. }
  10. cvShowImage( "image", image);
  11. cvShowImage( "image_gray", img_gray);
  12. cvShowImage( "img_value", img_value);
  13. cvWaitKey( 0);

运行结果:


完整代码:


    
    
  1. typedef struct p_image{
  2. char c_name;
  3. CvHistogram *img_zft;
  4. }p_image;
  5. int main()
  6. {
  7. //打开要识别字符的图像
  8. IplImage *image = cvLoadImage( "d:\\1.png");
  9. if (image == NULL){
  10. printf( "错误:无法打开该图像文件!");
  11. }
  12. //转换到灰度图
  13. IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);
  14. cvCvtColor(image, img_gray, CV_BGR2GRAY);
  15. //二值化
  16. IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);
  17. cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);
  18. //寻找轮廓
  19. CvMemStorage *pStorage = cvCreateMemStorage( 0);
  20. CvSeq *pConInner = NULL;
  21. int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),
  22. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
  23. IplImage* imgNo[ 9] = { NULL };
  24. IplImage *i1[ 9] = { NULL };
  25. //字符提取
  26. for ( int i = 0; i < num; i++)
  27. {
  28. CvRect rc = cvBoundingRect(pConInner);
  29. //cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!
  30. imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
  31. cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height)); //设置源图像ROI
  32. cvCopy(image, imgNo[i]); //裁剪
  33. pConInner = pConInner->h_next;
  34. }
  35. //保存与读取
  36. char name[ 256];
  37. for ( int i = 0; i < 9; ++i){
  38. sprintf(name, "d:\\img\\%d.jpg", i);
  39. cvSaveImage(name, imgNo[i]);
  40. }
  41. char b[ 256];
  42. for ( int i = 0; i < 9; ++i){
  43. sprintf(b, "d:\\img\\%d.jpg", i);
  44. imgNo[i] = cvLoadImage(b);
  45. }
  46. //灰度转换
  47. for ( int i = 0; i < 9; ++i){
  48. i1[i] = cvCreateImage(cvSize(imgNo[i]->width, imgNo[i]->height), 8, 1);
  49. cvCvtColor(imgNo[i], i1[i], CV_BGR2GRAY); //CV_BGR2GRAY
  50. }
  51. //计算原图的直方图
  52. int arr_size = 255; //定义一个变量用于表示直方图行宽
  53. float hranges_arr[] = { 0, 255 }; //图像方块范围数组
  54. float *phranges_arr = hranges_arr; //cvCreateHist参数是一个二级指针,所以要用指针指向数组然后传参
  55. //创建直方图
  56. CvHistogram *hist[ 9];
  57. for ( int i = 0; i < 9; ++i){
  58. hist[i] = cvCreateHist( 1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1); //创建一个一维的直方图,行宽为255,多维密集数组,方块范围为0-255,bin均化
  59. }
  60. //计算直方图
  61. for ( int i = 0; i < 9; ++i){
  62. cvCalcHist(&i1[i], hist[i], 0, 0);
  63. }
  64. //字符与图像关联
  65. //加载图像
  66. IplImage* abcd_img[ 9] = { NULL };
  67. char img_name[ 256] = { 0 };
  68. for ( int i = 0; i < 9; ++i){
  69. sprintf(img_name, "d:\\img\\%d.jpg", i);
  70. abcd_img[i] = cvLoadImage(img_name);
  71. }
  72. //转换灰度图
  73. IplImage* abcd_img_hdt[ 9] = { NULL };
  74. for ( int i = 0; i < 9; ++i){
  75. abcd_img_hdt[i] = cvCreateImage(cvSize(abcd_img[i]->width, abcd_img[i]->height), 8, 1);
  76. cvCvtColor(abcd_img[i], abcd_img_hdt[i], CV_BGR2GRAY); //CV_BGR2GRAY
  77. }
  78. //创建直方图
  79. CvHistogram *hist_abcd[ 9];
  80. for ( int i = 0; i < 9; ++i){
  81. hist_abcd[i] = cvCreateHist( 1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1); //创建一个一维的直方图,行宽为255,多维密集数组,方块范围为0-255,bin均化
  82. }
  83. //计算模板图的直方图
  84. for ( int i = 0; i < 9; ++i){
  85. cvCalcHist(&abcd_img_hdt[i], hist_abcd[i], 0, 0);
  86. }
  87. //关联图像
  88. p_image *p[ 9] = { NULL };
  89. for ( int i = 0; i <= num; ++i){
  90. p[i] = (p_image *) malloc( sizeof(p_image));
  91. }
  92. //字符关联
  93. char abcd[ 9] = { 'H', 'E', 'L', 'L', 'O', 'W', 'O', 'R', 'D' };
  94. for ( int i = 0; i <= num; ++i){
  95. p[i]->c_name = abcd[i];
  96. }
  97. //直方图关联
  98. for ( int i = 0; i < num; ++i){
  99. p[i]->img_zft = hist_abcd[i];
  100. }
  101. //匹配图像
  102. //用于结果
  103. char j_c[ 9];
  104. for ( int i = 0; i < num; ++i){
  105. for ( int j = 0; j < num; ++j){
  106. double Compare = cvCompareHist(hist[i], p[j]->img_zft, 0); //使用CV_COMP_CORREL方法进行对比
  107. if (Compare == 1.){ //100%正确 这里我们精准度调整高一点,因为直方图比对真的是太差劲了,相似度很高
  108. j_c[i] = p[j]->c_name;
  109. }
  110. }
  111. }
  112. printf( "检测到%d个字母,分别是", num);
  113. for ( int i = 0; i < num; ++i){
  114. printf( "%c", j_c[i]);
  115. if (i != 8){
  116. printf( ",");
  117. }
  118. }
  119. for ( int i = 0; i < num; ++i){
  120. p[i]->c_name = abcd[i];
  121. }
  122. //显示图像
  123. cvNamedWindow( "image", 0);
  124. cvNamedWindow( "image_gray", 0);
  125. cvNamedWindow( "img_value", 0);
  126. char a[ 9];
  127. for ( int i = 0; i < num; ++i){
  128. sprintf(a, "%d", i);
  129. cvShowImage(a, imgNo[i]);
  130. }
  131. cvShowImage( "image", image);
  132. cvShowImage( "image_gray", img_gray);
  133. cvShowImage( "img_value", img_value);
  134. cvWaitKey( 0);
  135. return 0;
  136. }

 
  

一.使用Opencv进行轮廓检测!

所需函数:

1. cvFindContours

函数功能:从二值图像中检索轮廓,并返回检测到的轮廓的个数

函数原型:


  
  
  1. int) cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq** first_contour,
  2. int header_size = sizeof(CvContour),
  3. int mode = CV_RETR_LIST,
  4. int method = CV_CHAIN_APPROX_SIMPLE,
  5. CvPoint offset = cvPoint( 0, 0));

参数介绍:

CvArr* image:要检测轮廓的图像,必须为二值图
  
  
CvMemStorage* storage:存储轮廓的容器
  
  
 CvSeq** first_contour:输出参数,用于存储指向第一个外接轮廓。是一个链表,h_next用于指向下一个外接轮廓
  
  

  
  
  1. int header_size:header序列的尺寸.如果选择method = CV_CHAIN_CODE, 则header_size >= sizeof(CvChain);其他,则
  2. header_size >= sizeof(CvContour)。

  
  
  1. int mode:
  2. CV_RETR_EXTERNAL:只检索最外面的轮廓;
  3. CV_RETR_LIST:检索所有的轮廓,并将其放入 list中;
  4. CV_RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
  5. CV_RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次。

  
  
  1. int method
  2. 边缘近似方法(除了CV_RETR_RUNS使用内置的近似,其他模式均使用此设定的近似算法)。可取值如下:
  3. CV_CHAIN_CODE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
  4. CV_CHAIN_APPROX_NONE:将所有的连码点,转换成点。
  5. CV_CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。
  6. CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS:使用the flavors of Teh-Chin chain近似算法
  7. 的一种。
  8. CV_LINK_RUNS:通过连接水平段的 1,使用完全不同的边缘提取算法。使用CV_RETR_LIST检索模式能使用此方法。

  
  
CvPoint offset
   
   
偏移量,用于移动所有轮廓点。当轮廓是从图像的ROI提取的,并且需要在整个图像中分析时,这个参数将很有用。
  
  

返回值:检测到的轮廓数量

2.cvThreshold

函数功能:对单通道数组应用固定阈值操作。该函数的典型应用是对灰度图像进行阈值操作得到二值图像。

函数原型:


  
  
  1. void cvThreshold
  2. ( const CvArr* src,
  3. CvArr* dst,
  4. double threshold,
  5. double max_value,
  6. int threshold_type );

参数介绍:


  
  
  1. src:原始数组 (单通道 , 8-bit of 32-bit 浮点数)。
  2. dst:输出数组,必须与 src 的类型一致,或者为 8-bit。
  3. threshold:阈值
  4. max_value:使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值。
  5. threshold_type:阈值类型
  6. threshold_type=CV_THRESH_BINARY:如果 src(x,y)>threshold ,dst(x,y) = max_value; 否则,dst(x,y)= 0;
  7. threshold_type=CV_THRESH_BINARY_INV:如果 src(x,y)>threshold,dst(x,y) = 0; 否则,dst(x,y) = max_value.
  8. threshold_type=CV_THRESH_TRUNC:如果 src(x,y)>threshold,dst(x,y) = max_value; 否则dst(x,y) = src(x,y).
  9. threshold_type=CV_THRESH_TOZERO:如果src(x,y)>threshold,dst(x,y) = src(x,y) ; 否则 dst(x,y) = 0
  10. threshold_type=CV_THRESH_TOZERO_INV:如果 src(x,y)>threshold,dst(x,y) = 0 ; 否则dst(x,y) = src(x,y).

3. cvCreateMemStorage

函数功能:用来创建一个内存存储器,来统一管理各种动态对象的内存

函数原型:

CvMemStorage*  cvCreateMemStorage( int block_size = 0);
  
  

参数介绍:

int block_size = 0:对应内存器中每个内存块的大小,为0时内存块默认大小为64k。
  
  

返回值:返回一个新创建的内存存储器指针。

4. cvBoundingRect

函数功能:计算点集的最外面(up-right)矩形边界。

函数原型:

CvRect  cvBoundingRect( CvArr* points, int update = 0);
  
  

参数介绍:

CvArr* points:二级点矩阵
  
  

  
  
  1. int update:
  2. 更新标识。
  3. 下面是轮廓类型和标识的一些可能组合:
  4. update= 0, contour ~ CvContour*: 不计算矩形边界,但直接由轮廓头的 rect 域得到。
  5. update= 1, contour ~ CvContour*: 计算矩形边界,而且将结果写入到轮廓头的 rect 域中 header。
  6. update= 0, contour ~ CvSeq* or CvMat*: 计算并返回边界矩形。
  7. update= 1, contour ~ CvSeq* or CvMat*: 产生运行错误 (runtime error is raised)。
  8. 函数 cvBoundingRect 返回二维点集的最外面 (up-right)矩形边界。 [ 1]

所需结构体:

1. CvMemStorage

结构体介绍:


  
  
  1. typedef struct CvMemStorage
  2. {
  3. int signature;
  4. CvMemBlock* bottom; /**< 第一分配块。 */
  5. CvMemBlock* top; /**< 当前内存块——堆栈的顶部。 */
  6. struct CvMemStorage* parent; /**< 根据需要从父块获取新的块。 */
  7. int block_size; /**< 块的大小。 */
  8. int free_space; /**< 当前块中的剩余空闲空间。 */
  9. }CvMemStorage;

2. CvSeq


  
  
  1. typedef struct CvSeq
  2. {
  3. CV_SEQUENCE_FIELDS()
  4. } CvSeq;
  5. #define CV_SEQUENCE_FIELDS()
  6. int flags; /* micsellaneous flags */
  7. int header_size; /* 序列头的大小 */
  8. struct CvSeq* h_prev; /* 前一个序列 */
  9. struct CvSeq* h_next; /* 后一个序列 */
  10. struct CvSeq* v_prev; /* 第二级前一个序列 */
  11. struct CvSeq* v_next; /* 第二级后一个序列 */
  12. int total; /* 元素的总个数 */
  13. int elem_size; /* 元素的尺寸 */
  14. char* block_max; /* 上一块的最大块 */
  15. char* ptr; /* 当前写指针 */
  16. int delta_elems; /*序列中快的大小
  17. (序列粒度) */
  18. CvMemStorage* storage; /*序列的存储位置 */
  19. CvSeqBlock* free_blocks; /* 未分配的块序列 */
  20. CvSeqBlock* first; /* 指向第一个快序列 */
相关理论知识:在图像处理中阈值是什么意思?


二. 开始编写代码

编写代码前需要准备一张实验图像!


可自行保存到本地,png格式!

1. 加载测试图像


  
  
  1. //打开要识别字符的图像
  2. IplImage *image = cvLoadImage( "d:\\1.png");
  3. if (image == NULL){
  4. printf( "错误:无法打开该图像文件!");
  5. }

2.图像二值化


  
  
  1. //转换到灰度图
  2. IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);
  3. cvCvtColor(image, img_gray, CV_BGR2GRAY);
  4. //二值化
  5. IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);
  6. cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);

3. 在二值化图像中寻找轮廓


  
  
  1. //寻找轮廓
  2. CvMemStorage *pStorage = cvCreateMemStorage( 0);
  3. CvSeq *pConInner = NULL;
  4. int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),
  5. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

4. 获取轮廓在图像中的矩阵坐标


  
  
  1. //根据轮廓坐标使用方框标出来
  2. for ( int i = 0; i < num; i++)
  3. {
  4. CvRect rc = cvBoundingRect(pConInner);
  5. cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB( 255, 0, 0));
  6. pConInner = pConInner->h_next;
  7. }

5. 显示图像


  
  
  1. //显示图像
  2. cvNamedWindow( "image", 0);
  3. cvNamedWindow( "image_gray", 0);
  4. cvNamedWindow( "img_value", 0);
  5. cvShowImage( "image", image);
  6. cvShowImage( "image_gray", img_gray);
  7. cvShowImage( "img_value", img_value);
  8. cvWaitKey( 0);

运行结果:


二值化图像是很利用我们做轮廓检测的,因为二值化的图像中不会有其他掺杂颜色在里面影响轮廓检测准确!

完整代码:


  
  
  1. //打开要识别字符的图像
  2. IplImage *image = cvLoadImage( "d:\\1.png");
  3. if (image == NULL){
  4. printf( "错误:无法打开该图像文件!");
  5. }
  6. //转换到灰度图
  7. IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);
  8. cvCvtColor(image, img_gray, CV_BGR2GRAY);
  9. //二值化
  10. IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);
  11. cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);
  12. //寻找轮廓
  13. CvMemStorage *pStorage = cvCreateMemStorage( 0);
  14. CvSeq *pConInner = NULL;
  15. int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),
  16. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
  17. //根据轮廓坐标使用方框标出来
  18. for ( int i = 0; i < num; i++)
  19. {
  20. CvRect rc = cvBoundingRect(pConInner);
  21. cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB( 255, 0, 0));
  22. pConInner = pConInner->h_next;
  23. }
  24. //显示图像
  25. cvNamedWindow( "image", 0);
  26. cvNamedWindow( "image_gray", 0);
  27. cvNamedWindow( "img_value", 0);
  28. cvShowImage( "image", image);
  29. cvShowImage( "image_gray", img_gray);
  30. cvShowImage( "img_value", img_value);
  31. cvWaitKey( 0);

三. 字符提取

已经将字符轮廓用矩形给标出来了,那么接下来字符提取就较为简单了!

我们可以复用上面的代码,在绘制轮廓前加入:

IplImage* imgNo[9] = { NULL };
  
  

用于存储分割出来的图像

在使用cvCopy函数对其进行裁剪即可!


  
  
  1. //根据轮廓坐标使用方框标出来
  2. for ( int i = 0; i < num; i++)
  3. {
  4. CvRect rc = cvBoundingRect(pConInner);
  5. //cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!
  6. imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
  7. cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height)); //设置源图像ROI
  8. cvCopy(image, imgNo[i]); //裁剪,将裁剪的字符图像放入到imgNo数组中去
  9. pConInner = pConInner->h_next;
  10. }
最后在将其显示出来

   
   
  1. char a[ 9];
  2. for ( int i = 0; i < 9; ++i){
  3. sprintf(a, "%d", i);
  4. cvShowImage(a, imgNo[i]);
  5. }
运行结果:


完整代码:

  
  
  1. //打开要识别字符的图像
  2. IplImage *image = cvLoadImage( "d:\\1.png");
  3. if (image == NULL){
  4. printf( "错误:无法打开该图像文件!");
  5. }
  6. //转换到灰度图
  7. IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);
  8. cvCvtColor(image, img_gray, CV_BGR2GRAY);
  9. //二值化
  10. IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);
  11. cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);
  12. //寻找轮廓
  13. CvMemStorage *pStorage = cvCreateMemStorage( 0);
  14. CvSeq *pConInner = NULL;
  15. int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),
  16. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
  17. IplImage* imgNo[ 9] = { NULL };
  18. //根据轮廓坐标使用方框标出来
  19. for ( int i = 0; i < num; i++)
  20. {
  21. CvRect rc = cvBoundingRect(pConInner);
  22. //cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!
  23. imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
  24. cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height)); //设置源图像ROI
  25. cvCopy(image, imgNo[i]); //裁剪
  26. pConInner = pConInner->h_next;
  27. }
  28. //显示图像
  29. cvNamedWindow( "image", 0);
  30. cvNamedWindow( "image_gray", 0);
  31. cvNamedWindow( "img_value", 0);
  32. char a[ 9];
  33. for ( int i = 0; i < 9; ++i){
  34. sprintf(a, "%d", i);
  35. cvShowImage(a, imgNo[i]);
  36. }
  37. cvShowImage( "image_gray", img_gray);
  38. cvShowImage( "img_value", img_value);
  39. cvWaitKey( 0);

四. 文字识别

在文字识别,你可以使用其他方法,本博客中使用最简单的直方图匹配,后续的车牌识别中,我会写一篇通过Opencv训练分练器,通过样本文件来识别提高识别率!

相关链接: 使用Opencv绘制灰度直方图/对比

注意前提,你要有模板图像,这里我把样本图像分享给各位!


由于模板图像较多,我上传到csdn上,供需要的人下载正规字符模板,下载需要两积分,如果不想下载可以使用下列图像,然后复用上面的代码裁剪出字符然后使用cvSeveImage函数保存到本地即可!


然后使用下列代码:


  
  
  1. //打开要识别字符的图像
  2. IplImage *image = cvLoadImage( "d:\\1.png");
  3. if (image == NULL){
  4. printf( "错误:无法打开该图像文件!");
  5. }
  6. //转换到灰度图
  7. IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);
  8. cvCvtColor(image, img_gray, CV_BGR2GRAY);
  9. //二值化
  10. IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);
  11. cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);
  12. //寻找轮廓
  13. CvMemStorage *pStorage = cvCreateMemStorage( 0);
  14. CvSeq *pConInner = NULL;
  15. int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),
  16. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
  17. IplImage* imgNo[ 53] = { NULL };
  18. //根据轮廓坐标使用方框标出来
  19. for ( int i = 0; i < num; i++)
  20. {
  21. CvRect rc = cvBoundingRect(pConInner);
  22. //cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!
  23. imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
  24. cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height)); //设置源图像ROI
  25. cvCopy(image, imgNo[i]); //裁剪
  26. pConInner = pConInner->h_next;
  27. }
  28. //显示图像文件
  29. char a[ 53];
  30. for ( int i = 0; i < 53; ++i){
  31. sprintf(a, "d:\\img\\%d.jpg", i);
  32. cvSaveImage(a, imgNo[i]);
  33. }
  34. cvWaitKey( 0);

运行之后你的D盘下就会出现一个img文件里面包含了所有的正规字符模板图!

开始编写识别代码

这里我们不复用上面的代码,重新编写一份,因为模板匹配算法有些地方有问题,所以这里重写一遍给大家注明一下!

注意上面的模型建议用在其他识别算法上,不建议用在模板图片上,matchTemplate模板匹配函数要求尺寸一致,这里为了方便就不编写尺寸变换的代码了,只是做一个示例,所以我就直接用这张图的字符样本做模板了,


拿出来分享给各位:

      

有需要可自行保存到本地,jpg格式!

下面开始编写代码:

1. 定义一个结构体用于关联图像与字符


  
  
  1. typedef struct p_image{
  2. char c_name;
  3. CvHistogram *img_zft;
  4. }p_image;

2. 打开测试图像


  
  
  1. //打开要识别字符的图像
  2. IplImage *image = cvLoadImage( "d:\\1.png");
  3. if (image == NULL){
  4. printf( "错误:无法打开该图像文件!");
  5. }

3. 图像二值化


  
  
  1. //转换到灰度图
  2. IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);
  3. cvCvtColor(image, img_gray, CV_BGR2GRAY);
  4. //二值化
  5. IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);
  6. cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);

4. 寻找轮廓


  
  
  1. //寻找轮廓
  2. CvMemStorage *pStorage = cvCreateMemStorage( 0);
  3. CvSeq *pConInner = NULL;
  4. int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),
  5. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

5. 申请变量用于字符提取

IplImage* imgNo[9] = { NULL };
  
  

6. 申请变量用于转化字符提取的直方图

IplImage *i1[9] = { NULL };
  
  

7. 字符裁剪


  
  
  1. //字符提取
  2. for ( int i = 0; i < num; i++)
  3. {
  4. CvRect rc = cvBoundingRect(pConInner);
  5. //cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!
  6. imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
  7. cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height)); //设置源图像ROI
  8. cvCopy(image, imgNo[i]); //裁剪
  9. pConInner = pConInner->h_next;
  10. }

8. 重新保存与读取

为什么要加这段重复代码?

原因:经过我测试,Opencv里面的数据是没有经过压缩的,所以当你打开模板图时模板图格式为jpg,经过jpeg算法压缩,当你使用直方图比对时比对会有差异,注意加这段代码仅针对直方图的情况来做修改的!

其他匹配方法无需增加这段代码!

吐槽一下:直方图匹配真的是太烂了,直方图只能用于计算!

//jpg格式压缩!!


  
  
  1. //保存与读取
  2. char name[ 256];
  3. for ( int i = 0; i < 9; ++i){
  4. sprintf(name, "d:\\img\\%d.jpg", i);
  5. cvSaveImage(name, imgNo[i]);
  6. }
  7. char b[ 256];
  8. for ( int i = 0; i < 9; ++i){
  9. sprintf(b, "d:\\img\\%d.jpg", i);
  10. imgNo[i] = cvLoadImage(b);
  11. }

9. 灰度图转换


  
  
  1. //灰度转换
  2. for ( int i = 0; i < 9; ++i){
  3. i1[i] = cvCreateImage(cvSize(imgNo[i]->width, imgNo[i]->height), 8, 1);
  4. cvCvtColor(imgNo[i], i1[i], CV_BGR2GRAY); //CV_BGR2GRAY
  5. }

10. 计算原图的直方图


  
  
  1. //计算原图的直方图
  2. int arr_size = 255; //定义一个变量用于表示直方图行宽
  3. float hranges_arr[] = { 0, 255 }; //图像方块范围数组
  4. float *phranges_arr = hranges_arr; //cvCreateHist参数是一个二级指针,所以要用指针指向数组然后传参
  5. //创建直方图
  6. CvHistogram *hist[ 9];
  7. for ( int i = 0; i < 9; ++i){
  8. hist[i] = cvCreateHist( 1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1); //创建一个一维的直方图,行宽为255,多维密集数组,方块范围为0-255,bin均化
  9. }
  10. //计算直方图
  11. for ( int i = 0; i < 9; ++i){
  12. cvCalcHist(&i1[i], hist[i], 0, 0);
  13. }

11. 加载模板图


  
  
  1. IplImage* abcd_img[ 9] = { NULL };
  2. char img_name[ 256] = { 0 };
  3. for ( int i = 0; i < 9; ++i){
  4. sprintf(img_name, "d:\\img\\%d.jpg", i);
  5. abcd_img[i] = cvLoadImage(img_name);
  6. }

12. 转换成灰度图


  
  
  1. //转换灰度图
  2. IplImage* abcd_img_hdt[ 9] = { NULL };
  3. for ( int i = 0; i < 9; ++i){
  4. abcd_img_hdt[i] = cvCreateImage(cvSize(abcd_img[i]->width, abcd_img[i]->height), 8, 1);
  5. cvCvtColor(abcd_img[i], abcd_img_hdt[i], CV_BGR2GRAY); //CV_BGR2GRAY
  6. }

13. 计算模板图的直方图


  
  
  1. //创建直方图
  2. CvHistogram *hist_abcd[ 9];
  3. for ( int i = 0; i < 9; ++i){
  4. hist_abcd[i] = cvCreateHist( 1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1); //创建一个一维的直方图,行宽为255,多维密集数组,方块范围为0-255,bin均化
  5. }
  6. //计算模板图的直方图
  7. for ( int i = 0; i < 9; ++i){
  8. cvCalcHist(&abcd_img_hdt[i], hist_abcd[i], 0, 0);
  9. }

14. 申请结构体,方便数据化管理


  
  
  1. //申请结构体
  2. p_image *p[ 9] = { NULL };
  3. for ( int i = 0; i <= num; ++i){
  4. p[i] = (p_image *) malloc( sizeof(p_image));
  5. }

15. 字符关联


  
  
  1. //字符关联
  2. char abcd[ 9] = { 'H', 'E', 'L', 'L', 'O', 'W', 'O', 'R', 'D' };
  3. for ( int i = 0; i <= num; ++i){
  4. p[i]->c_name = abcd[i];
  5. }

16. 结构体与模板直方图管理,形成结构化


  
  
  1. //直方图关联
  2. for ( int i = 0; i < num; ++i){
  3. p[i]->img_zft = hist_abcd[i];
  4. }

17. 匹配图像


  
  
  1. //匹配图像
  2. char j_c[ 9]; //用于存储匹配到的字符
  3. for ( int i = 0; i < num; ++i){
  4. for ( int j = 0; j < num; ++j){
  5. double Compare = cvCompareHist(hist[i], p[j]->img_zft, 0); //使用CV_COMP_CORREL方法进行对比
  6. if (Compare == 1.){ //100%正确 这里我们精准度调整高一点,因为直方图比对真的是太差劲了,相似度很高
			j_c[i] = p[j]->c_name;
  
  
}}}

18. 打印匹配结果


  
  
  1. printf( "检测到%d个字母,分别是", num);
  2. for ( int i = 0; i < num; ++i){
  3. printf( "%c", j_c[i]);
  4. if (i != 8){
  5. printf( ",");
  6. }
  7. }
  8. for ( int i = 0; i < num; ++i){
  9. p[i]->c_name = abcd[i];
  10. }

19. 显示图像


  
  
  1. //显示图像
  2. cvNamedWindow( "image", 0);
  3. cvNamedWindow( "image_gray", 0);
  4. cvNamedWindow( "img_value", 0);
  5. char a[ 9];
  6. for ( int i = 0; i < num; ++i){
  7. sprintf(a, "%d", i);
  8. cvShowImage(a, imgNo[i]);
  9. }
  10. cvShowImage( "image", image);
  11. cvShowImage( "image_gray", img_gray);
  12. cvShowImage( "img_value", img_value);
  13. cvWaitKey( 0);

运行结果:


完整代码:


  
  
  1. typedef struct p_image{
  2. char c_name;
  3. CvHistogram *img_zft;
  4. }p_image;
  5. int main()
  6. {
  7. //打开要识别字符的图像
  8. IplImage *image = cvLoadImage( "d:\\1.png");
  9. if (image == NULL){
  10. printf( "错误:无法打开该图像文件!");
  11. }
  12. //转换到灰度图
  13. IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);
  14. cvCvtColor(image, img_gray, CV_BGR2GRAY);
  15. //二值化
  16. IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);
  17. cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);
  18. //寻找轮廓
  19. CvMemStorage *pStorage = cvCreateMemStorage( 0);
  20. CvSeq *pConInner = NULL;
  21. int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),
  22. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
  23. IplImage* imgNo[ 9] = { NULL };
  24. IplImage *i1[ 9] = { NULL };
  25. //字符提取
  26. for ( int i = 0; i < num; i++)
  27. {
  28. CvRect rc = cvBoundingRect(pConInner);
  29. //cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!
  30. imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
  31. cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height)); //设置源图像ROI
  32. cvCopy(image, imgNo[i]); //裁剪
  33. pConInner = pConInner->h_next;
  34. }
  35. //保存与读取
  36. char name[ 256];
  37. for ( int i = 0; i < 9; ++i){
  38. sprintf(name, "d:\\img\\%d.jpg", i);
  39. cvSaveImage(name, imgNo[i]);
  40. }
  41. char b[ 256];
  42. for ( int i = 0; i < 9; ++i){
  43. sprintf(b, "d:\\img\\%d.jpg", i);
  44. imgNo[i] = cvLoadImage(b);
  45. }
  46. //灰度转换
  47. for ( int i = 0; i < 9; ++i){
  48. i1[i] = cvCreateImage(cvSize(imgNo[i]->width, imgNo[i]->height), 8, 1);
  49. cvCvtColor(imgNo[i], i1[i], CV_BGR2GRAY); //CV_BGR2GRAY
  50. }
  51. //计算原图的直方图
  52. int arr_size = 255; //定义一个变量用于表示直方图行宽
  53. float hranges_arr[] = { 0, 255 }; //图像方块范围数组
  54. float *phranges_arr = hranges_arr; //cvCreateHist参数是一个二级指针,所以要用指针指向数组然后传参
  55. //创建直方图
  56. CvHistogram *hist[ 9];
  57. for ( int i = 0; i < 9; ++i){
  58. hist[i] = cvCreateHist( 1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1); //创建一个一维的直方图,行宽为255,多维密集数组,方块范围为0-255,bin均化
  59. }
  60. //计算直方图
  61. for ( int i = 0; i < 9; ++i){
  62. cvCalcHist(&i1[i], hist[i], 0, 0);
  63. }
  64. //字符与图像关联
  65. //加载图像
  66. IplImage* abcd_img[ 9] = { NULL };
  67. char img_name[ 256] = { 0 };
  68. for ( int i = 0; i < 9; ++i){
  69. sprintf(img_name, "d:\\img\\%d.jpg", i);
  70. abcd_img[i] = cvLoadImage(img_name);
  71. }
  72. //转换灰度图
  73. IplImage* abcd_img_hdt[ 9] = { NULL };
  74. for ( int i = 0; i < 9; ++i){
  75. abcd_img_hdt[i] = cvCreateImage(cvSize(abcd_img[i]->width, abcd_img[i]->height), 8, 1);
  76. cvCvtColor(abcd_img[i], abcd_img_hdt[i], CV_BGR2GRAY); //CV_BGR2GRAY
  77. }
  78. //创建直方图
  79. CvHistogram *hist_abcd[ 9];
  80. for ( int i = 0; i < 9; ++i){
  81. hist_abcd[i] = cvCreateHist( 1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1); //创建一个一维的直方图,行宽为255,多维密集数组,方块范围为0-255,bin均化
  82. }
  83. //计算模板图的直方图
  84. for ( int i = 0; i < 9; ++i){
  85. cvCalcHist(&abcd_img_hdt[i], hist_abcd[i], 0, 0);
  86. }
  87. //关联图像
  88. p_image *p[ 9] = { NULL };
  89. for ( int i = 0; i <= num; ++i){
  90. p[i] = (p_image *) malloc( sizeof(p_image));
  91. }
  92. //字符关联
  93. char abcd[ 9] = { 'H', 'E', 'L', 'L', 'O', 'W', 'O', 'R', 'D' };
  94. for ( int i = 0; i <= num; ++i){
  95. p[i]->c_name = abcd[i];
  96. }
  97. //直方图关联
  98. for ( int i = 0; i < num; ++i){
  99. p[i]->img_zft = hist_abcd[i];
  100. }
  101. //匹配图像
  102. //用于结果
  103. char j_c[ 9];
  104. for ( int i = 0; i < num; ++i){
  105. for ( int j = 0; j < num; ++j){
  106. double Compare = cvCompareHist(hist[i], p[j]->img_zft, 0); //使用CV_COMP_CORREL方法进行对比
  107. if (Compare == 1.){ //100%正确 这里我们精准度调整高一点,因为直方图比对真的是太差劲了,相似度很高
  108. j_c[i] = p[j]->c_name;
  109. }
  110. }
  111. }
  112. printf( "检测到%d个字母,分别是", num);
  113. for ( int i = 0; i < num; ++i){
  114. printf( "%c", j_c[i]);
  115. if (i != 8){
  116. printf( ",");
  117. }
  118. }
  119. for ( int i = 0; i < num; ++i){
  120. p[i]->c_name = abcd[i];
  121. }
  122. //显示图像
  123. cvNamedWindow( "image", 0);
  124. cvNamedWindow( "image_gray", 0);
  125. cvNamedWindow( "img_value", 0);
  126. char a[ 9];
  127. for ( int i = 0; i < num; ++i){
  128. sprintf(a, "%d", i);
  129. cvShowImage(a, imgNo[i]);
  130. }
  131. cvShowImage( "image", image);
  132. cvShowImage( "image_gray", img_gray);
  133. cvShowImage( "img_value", img_value);
  134. cvWaitKey( 0);
  135. return 0;
  136. }

 

猜你喜欢

转载自blog.csdn.net/l641208111/article/details/106222363