天天看点

使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!

目录

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

二. 开始编写代码

三. 字符提取

四. 文字识别

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

所需函数:

1. cvFindContours

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

函数原型:

int cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq** first_contour,

    int header_size = sizeof(CvContour),

    int mode = CV_RETR_LIST,

    int method = CV_CHAIN_APPROX_SIMPLE,

    CvPoint offset = cvPoint(0,0));
           

参数介绍:

CvArr* image:要检测轮廓的图像,必须为二值图

CvMemStorage* storage:存储轮廓的容器

​CvSeq** first_contour:输出参数,用于存储指向第一个外接轮廓。是一个链表,h_next用于指向下一个外接轮廓

int header_size:

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

int mode:

CV_RETR_EXTERNAL:只检索最外面的轮廓;

CV_RETR_LIST:检索所有的轮廓,并将其放入list中;

CV_RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;

CV_RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次。

int method

边缘近似方法(除了CV_RETR_RUNS使用内置的近似,其他模式均使用此设定的近似算法)。可取值如下:

CV_CHAIN_CODE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。

CV_CHAIN_APPROX_NONE:将所有的连码点,转换成点。

CV_CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。

CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS:使用the flavors of Teh-Chin chain近似算法

的一种。

CV_LINK_RUNS:通过连接水平段的1,使用完全不同的边缘提取算法。使用CV_RETR_LIST检索模式能使用此方法。

CvPoint offset

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

​
           

2.cvThreshold

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

函数原型:

void cvThreshold( const CvArr* src,

                CvArr* dst,

                double threshold,

                double max_value,

                int threshold_type );
           

参数介绍:

src:原始数组 (单通道 , 8-bit of 32-bit 浮点数)。

dst:输出数组,必须与 src 的类型一致,或者为 8-bit。

threshold:阈值

max_value:使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值。

threshold_type:阈值类型

threshold_type=CV_THRESH_BINARY:如果 src(x,y)>threshold ,dst(x,y) = max_value; 否则,dst(x,y)=0;

threshold_type=CV_THRESH_BINARY_INV:如果 src(x,y)>threshold,dst(x,y) = 0; 否则,dst(x,y) = max_value.

threshold_type=CV_THRESH_TRUNC:如果 src(x,y)>threshold,dst(x,y) = max_value; 否则dst(x,y) = src(x,y).

threshold_type=CV_THRESH_TOZERO:如果src(x,y)>threshold,dst(x,y) = src(x,y) ; 否则 dst(x,y) = 0。

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:二级点矩阵


int update:

更新标识。

下面是轮廓类型和标识的一些可能组合:

update=0, contour ~ CvContour*: 不计算矩形边界,但直接由轮廓头的 rect 域得到。

update=1, contour ~ CvContour*: 计算矩形边界,而且将结果写入到轮廓头的 rect 域中 header。

update=0, contour ~ CvSeq* or CvMat*: 计算并返回边界矩形。

update=1, contour ~ CvSeq* or CvMat*: 产生运行错误 (runtime error is raised)。

函数 cvBoundingRect 返回二维点集的最外面 (up-right)矩形边界。 [1]
           

所需结构体:

1. CvMemStorage

结构体介绍:

typedef struct CvMemStorage


{


    int signature;


    CvMemBlock* bottom; /**< 第一分配块。 */


    CvMemBlock* top; /**< 当前内存块——堆栈的顶部。 */


    struct CvMemStorage* parent; /**< 根据需要从父块获取新的块。 */


    int block_size; /**< 块的大小。 */


    int free_space; /**< 当前块中的剩余空闲空间。 */


}CvMemStorage;
           

2. CvSeq

typedef struct CvSeq

{

CV_SEQUENCE_FIELDS()

} CvSeq;


#define CV_SEQUENCE_FIELDS()

int flags; /* micsellaneous flags */

int header_size; /* 序列头的大小 */

struct CvSeq* h_prev; /* 前一个序列 */

struct CvSeq* h_next; /* 后一个序列 */

struct CvSeq* v_prev; /* 第二级前一个序列 */

struct CvSeq* v_next; /* 第二级后一个序列 */

int total; /* 元素的总个数 */

int elem_size;/* 元素的尺寸 */

char* block_max;/* 上一块的最大块 */

char* ptr; /* 当前写指针 */

int delta_elems; /*序列中快的大小

(序列粒度) */

CvMemStorage* storage; /*序列的存储位置 */

CvSeqBlock* free_blocks; /* 未分配的块序列 */

CvSeqBlock* first; /* 指向第一个快序列 */
           

相关理论知识:在图像处理中阈值是什么意思?

二. 开始编写代码

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

使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!

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

1. 加载测试图像

//打开要识别字符的图像

IplImage *image = cvLoadImage("d:\\1.png");

if (image == NULL){

printf("错误:无法打开该图像文件!");

}
           

2.图像二值化

//转换到灰度图

IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);

cvCvtColor(image, img_gray, CV_BGR2GRAY);

//二值化

IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);

cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);
           

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

//寻找轮廓

CvMemStorage *pStorage = cvCreateMemStorage(0);

CvSeq *pConInner = NULL;

int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),

CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
           

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

//根据轮廓坐标使用方框标出来

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0));

pConInner = pConInner->h_next;

}
           

5. 显示图像

//显示图像

cvNamedWindow("image", 0);

cvNamedWindow("image_gray", 0);

cvNamedWindow("img_value", 0);

cvShowImage("image", image);

cvShowImage("image_gray", img_gray);

cvShowImage("img_value", img_value);

cvWaitKey(0);
           

运行结果:

使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!

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

完整代码:

//打开要识别字符的图像

IplImage *image = cvLoadImage("d:\\1.png");

if (image == NULL){

printf("错误:无法打开该图像文件!");

}

//转换到灰度图

IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);

cvCvtColor(image, img_gray, CV_BGR2GRAY);

//二值化

IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);

cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);

//寻找轮廓

CvMemStorage *pStorage = cvCreateMemStorage(0);

CvSeq *pConInner = NULL;

int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),

CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

//根据轮廓坐标使用方框标出来

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0));

pConInner = pConInner->h_next;

}

//显示图像

cvNamedWindow("image", 0);

cvNamedWindow("image_gray", 0);

cvNamedWindow("img_value", 0);

cvShowImage("image", image);

cvShowImage("image_gray", img_gray);

cvShowImage("img_value", img_value);

cvWaitKey(0);
           

三. 字符提取

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

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

IplImage* imgNo[9] = { NULL };
           

用于存储分割出来的图像

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

//根据轮廓坐标使用方框标出来

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

//cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!

imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);

cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//设置源图像ROI

cvCopy(image, imgNo[i]); //裁剪,将裁剪的字符图像放入到imgNo数组中去

pConInner = pConInner->h_next;

}
           

//为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉! imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3); cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//设置源图像ROI cvCopy(image, imgNo[i]); //裁剪,将裁剪的字符图像放入到imgNo数组中去 pConInner = pConInner->h_next; }

最后在将其显示出来

char a[9];

for (int i = 0; i < 9; ++i){

sprintf(a, "%d", i);

cvShowImage(a, imgNo[i]);

}
           

运行结果:

使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!

完整代码:

//打开要识别字符的图像

IplImage *image = cvLoadImage("d:\\1.png");

if (image == NULL){

printf("错误:无法打开该图像文件!");

}

//转换到灰度图

IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);

cvCvtColor(image, img_gray, CV_BGR2GRAY);

//二值化

IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);

cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);

//寻找轮廓

CvMemStorage *pStorage = cvCreateMemStorage(0);

CvSeq *pConInner = NULL;

int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),

CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

IplImage* imgNo[9] = { NULL };

//根据轮廓坐标使用方框标出来

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

//cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!

imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);

cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//设置源图像ROI

cvCopy(image, imgNo[i]); //裁剪

pConInner = pConInner->h_next;

}

//显示图像

cvNamedWindow("image", 0);

cvNamedWindow("image_gray", 0);

cvNamedWindow("img_value", 0);

char a[9];

for (int i = 0; i < 9; ++i){

sprintf(a, "%d", i);

cvShowImage(a, imgNo[i]);

}

cvShowImage("image_gray", img_gray);

cvShowImage("img_value", img_value);

cvWaitKey(0);
           

四. 文字识别

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

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

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

使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!

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

使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!

然后使用下列代码:

//打开要识别字符的图像

IplImage *image = cvLoadImage("d:\\1.png");

if (image == NULL){

printf("错误:无法打开该图像文件!");

}

//转换到灰度图

IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);

cvCvtColor(image, img_gray, CV_BGR2GRAY);

//二值化

IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);

cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);

//寻找轮廓

CvMemStorage *pStorage = cvCreateMemStorage(0);

CvSeq *pConInner = NULL;

int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),

CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

IplImage* imgNo[53] = { NULL };

//根据轮廓坐标使用方框标出来

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

//cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!

imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);

cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//设置源图像ROI

cvCopy(image, imgNo[i]); //裁剪

pConInner = pConInner->h_next;

}

//显示图像文件

char a[53];

for (int i = 0; i < 53; ++i){

sprintf(a, "d:\\img\\%d.jpg", i);

cvSaveImage(a, imgNo[i]);

}

cvWaitKey(0);
           

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

开始编写识别代码

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

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

使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!

这张图的字符样本做模板了,

使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!

拿出来分享给各位:

使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!
使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!
使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!
使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!
使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!
使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!
使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!
使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!
使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!

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

下面开始编写代码:

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

typedef struct p_image{

char c_name;

CvHistogram *img_zft;

}p_image;
           

2. 打开测试图像

//打开要识别字符的图像

IplImage *image = cvLoadImage("d:\\1.png");

if (image == NULL){

printf("错误:无法打开该图像文件!");

}
           

3. 图像二值化

//转换到灰度图

IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);

cvCvtColor(image, img_gray, CV_BGR2GRAY);

//二值化

IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);

cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);
           

4. 寻找轮廓

//寻找轮廓

CvMemStorage *pStorage = cvCreateMemStorage(0);

CvSeq *pConInner = NULL;

int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),

CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
           

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

IplImage* imgNo[9] = { NULL };
           

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

IplImage *i1[9] = { NULL };
           

7. 字符裁剪

//字符提取

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

//cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!

imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);

cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//设置源图像ROI

cvCopy(image, imgNo[i]); //裁剪

pConInner = pConInner->h_next;

}
           

8. 重新保存与读取

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

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

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

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

//jpg格式压缩!!

//保存与读取

char name[256];

for (int i = 0; i < 9; ++i){

sprintf(name, "d:\\img\\%d.jpg", i);

cvSaveImage(name, imgNo[i]);

}

char b[256];

for (int i = 0; i < 9; ++i){

sprintf(b, "d:\\img\\%d.jpg", i);

imgNo[i] = cvLoadImage(b);

}
           

9. 灰度图转换

//灰度转换

for (int i = 0; i < 9; ++i){

i1[i] = cvCreateImage(cvSize(imgNo[i]->width, imgNo[i]->height), 8, 1);

cvCvtColor(imgNo[i], i1[i], CV_BGR2GRAY);//CV_BGR2GRAY

}
           

10. 计算原图的直方图

//计算原图的直方图

int arr_size = 255; //定义一个变量用于表示直方图行宽

float hranges_arr[] = { 0, 255 }; //图像方块范围数组

float *phranges_arr = hranges_arr; //cvCreateHist参数是一个二级指针,所以要用指针指向数组然后传参

//创建直方图

CvHistogram *hist[9];

for (int i = 0; i < 9; ++i){

hist[i] = cvCreateHist(1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1);//创建一个一维的直方图,行宽为255,多维密集数组,方块范围为0-255,bin均化

}

//计算直方图

for (int i = 0; i < 9; ++i){

cvCalcHist(&i1[i], hist[i], 0, 0);

}
           

11. 加载模板图

IplImage* abcd_img[9] = { NULL };

char img_name[256] = { 0 };

for (int i = 0; i < 9; ++i){

sprintf(img_name, "d:\\img\\%d.jpg", i);

abcd_img[i] = cvLoadImage(img_name);

}
           

12. 转换成灰度图

//转换灰度图

IplImage* abcd_img_hdt[9] = { NULL };

for (int i = 0; i < 9; ++i){

abcd_img_hdt[i] = cvCreateImage(cvSize(abcd_img[i]->width, abcd_img[i]->height), 8, 1);

cvCvtColor(abcd_img[i], abcd_img_hdt[i], CV_BGR2GRAY);//CV_BGR2GRAY

}
           

13. 计算模板图的直方图

//创建直方图

CvHistogram *hist_abcd[9];

for (int i = 0; i < 9; ++i){

hist_abcd[i] = cvCreateHist(1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1);//创建一个一维的直方图,行宽为255,多维密集数组,方块范围为0-255,bin均化

}

//计算模板图的直方图

for (int i = 0; i < 9; ++i){

cvCalcHist(&abcd_img_hdt[i], hist_abcd[i], 0, 0);

}
           

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

//申请结构体

p_image *p[9] = { NULL };

for (int i = 0; i <= num; ++i){

p[i] = (p_image *)malloc(sizeof(p_image));

}
           

15. 字符关联

//字符关联

char abcd[9] = { 'H', 'E', 'L', 'L', 'O', 'W', 'O', 'R', 'D' };

for (int i = 0; i <= num; ++i){

p[i]->c_name = abcd[i];

}
           

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

//直方图关联

for (int i = 0; i < num; ++i){

p[i]->img_zft = hist_abcd[i];

}
           

17. 匹配图像

//匹配图像

char j_c[9];//用于存储匹配到的字符

for (int i = 0; i < num; ++i){

for (int j = 0; j < num; ++j){

double Compare = cvCompareHist(hist[i], p[j]->img_zft, 0); //使用CV_COMP_CORREL方法进行对比

if (Compare == 1.){//100%正确 这里我们精准度调整高一点,因为直方图比对真的是太差劲了,相似度很高

			j_c[i] = p[j]->c_name;

}}}
           

18. 打印匹配结果

printf("检测到%d个字母,分别是", num);

for (int i = 0; i < num; ++i){

printf("%c", j_c[i]);

if (i != 8){

printf(",");

}

}

for (int i = 0; i < num; ++i){

p[i]->c_name = abcd[i];


}
           

19. 显示图像

//显示图像

cvNamedWindow("image", 0);

cvNamedWindow("image_gray", 0);

cvNamedWindow("img_value", 0);

char a[9];

for (int i = 0; i < num; ++i){

sprintf(a, "%d", i);

cvShowImage(a, imgNo[i]);

}

cvShowImage("image", image);

cvShowImage("image_gray", img_gray);

cvShowImage("img_value", img_value);

cvWaitKey(0);
           

运行结果:

使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!

完整代码:

typedef struct p_image{

char c_name;

CvHistogram *img_zft;

}p_image;

int main()

{

//打开要识别字符的图像

IplImage *image = cvLoadImage("d:\\1.png");

if (image == NULL){

printf("错误:无法打开该图像文件!");

}

//转换到灰度图

IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);

cvCvtColor(image, img_gray, CV_BGR2GRAY);

//二值化

IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);

cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);

//寻找轮廓

CvMemStorage *pStorage = cvCreateMemStorage(0);

CvSeq *pConInner = NULL;

int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),

CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

IplImage* imgNo[9] = { NULL };

IplImage *i1[9] = { NULL };

//字符提取

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

//cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //为了防止裁剪时出现矩阵颜色,所以将这段代码屏蔽掉!

imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);

cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//设置源图像ROI

cvCopy(image, imgNo[i]); //裁剪

pConInner = pConInner->h_next;

}

//保存与读取

char name[256];

for (int i = 0; i < 9; ++i){

sprintf(name, "d:\\img\\%d.jpg", i);

cvSaveImage(name, imgNo[i]);

}

char b[256];

for (int i = 0; i < 9; ++i){

sprintf(b, "d:\\img\\%d.jpg", i);

imgNo[i] = cvLoadImage(b);

}

//灰度转换

for (int i = 0; i < 9; ++i){

i1[i] = cvCreateImage(cvSize(imgNo[i]->width, imgNo[i]->height), 8, 1);

cvCvtColor(imgNo[i], i1[i], CV_BGR2GRAY);//CV_BGR2GRAY

}

//计算原图的直方图

int arr_size = 255; //定义一个变量用于表示直方图行宽

float hranges_arr[] = { 0, 255 }; //图像方块范围数组

float *phranges_arr = hranges_arr; //cvCreateHist参数是一个二级指针,所以要用指针指向数组然后传参

//创建直方图

CvHistogram *hist[9];

for (int i = 0; i < 9; ++i){

hist[i] = cvCreateHist(1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1);//创建一个一维的直方图,行宽为255,多维密集数组,方块范围为0-255,bin均化

}

//计算直方图

for (int i = 0; i < 9; ++i){

cvCalcHist(&i1[i], hist[i], 0, 0);

}

//字符与图像关联

//加载图像

IplImage* abcd_img[9] = { NULL };

char img_name[256] = { 0 };

for (int i = 0; i < 9; ++i){

sprintf(img_name, "d:\\img\\%d.jpg", i);

abcd_img[i] = cvLoadImage(img_name);

}

//转换灰度图

IplImage* abcd_img_hdt[9] = { NULL };

for (int i = 0; i < 9; ++i){

abcd_img_hdt[i] = cvCreateImage(cvSize(abcd_img[i]->width, abcd_img[i]->height), 8, 1);

cvCvtColor(abcd_img[i], abcd_img_hdt[i], CV_BGR2GRAY);//CV_BGR2GRAY

}

//创建直方图

CvHistogram *hist_abcd[9];

for (int i = 0; i < 9; ++i){

hist_abcd[i] = cvCreateHist(1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1);//创建一个一维的直方图,行宽为255,多维密集数组,方块范围为0-255,bin均化

}

//计算模板图的直方图

for (int i = 0; i < 9; ++i){

cvCalcHist(&abcd_img_hdt[i], hist_abcd[i], 0, 0);

}

//关联图像

p_image *p[9] = { NULL };

for (int i = 0; i <= num; ++i){

p[i] = (p_image *)malloc(sizeof(p_image));

}

//字符关联

char abcd[9] = { 'H', 'E', 'L', 'L', 'O', 'W', 'O', 'R', 'D' };

for (int i = 0; i <= num; ++i){

p[i]->c_name = abcd[i];

}

//直方图关联

for (int i = 0; i < num; ++i){

p[i]->img_zft = hist_abcd[i];

}

//匹配图像

//用于结果

char j_c[9];

for (int i = 0; i < num; ++i){

for (int j = 0; j < num; ++j){

double Compare = cvCompareHist(hist[i], p[j]->img_zft, 0); //使用CV_COMP_CORREL方法进行对比

if (Compare == 1.){ //100%正确 这里我们精准度调整高一点,因为直方图比对真的是太差劲了,相似度很高

j_c[i] = p[j]->c_name;


}

}

}

printf("检测到%d个字母,分别是", num);

for (int i = 0; i < num; ++i){

printf("%c", j_c[i]);

if (i != 8){

printf(",");

}

}

for (int i = 0; i < num; ++i){

p[i]->c_name = abcd[i];


}

//显示图像

cvNamedWindow("image", 0);

cvNamedWindow("image_gray", 0);

cvNamedWindow("img_value", 0);

char a[9];

for (int i = 0; i < num; ++i){

sprintf(a, "%d", i);

cvShowImage(a, imgNo[i]);

}

cvShowImage("image", image);

cvShowImage("image_gray", img_gray);

cvShowImage("img_value", img_value);

cvWaitKey(0);

return 0;

}

           
参考文献:
           

https://blog.csdn.net/bjbz_cxy/article/details/79741725

继续阅读