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

2023-05-16

一.使用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; /* 指向第一个快序列 */
相关理论知识:在图像处理中阈值是什么意思?


二. 开始编写代码

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

可自行保存到本地,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);

运行结果:


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

完整代码:

//打开要识别字符的图像
    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;
    }
最后在将其显示出来
char a[9];
    for (int i = 0; i < 9; ++i){
        sprintf(a, "%d", i);
        cvShowImage(a, imgNo[i]);
    }
运行结果:


完整代码:
//打开要识别字符的图像
    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绘制灰度直方图/对比

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

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

然后使用下列代码:

//打开要识别字符的图像
    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模板匹配函数要求尺寸一致,这里为了方便就不编写尺寸变换的代码了,只是做一个示例,所以我就直接用

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

拿出来分享给各位:

    

有需要可自行保存到本地,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);
运行结果:

完整代码:

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 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • c# 调用c库dll ,char*转string的解决办法

    最近由于有个未知的设备需要用到modbus通讯协议 xff0c 底层需要与PLC通讯 xff0c 坤跌 xff0c PLC啥型号也不清楚封在里面不能拆 前人只留了几个不能运行的QT代码以及不完整的文档 用惯了C 想要重新学QT xff0c
  • C++多线程编程(入门实例)

    多线程在编程中有相当重要的地位 xff0c 我们在实际开发时或者找工作面试时总能遇到多线程的问题 xff0c 对多线程的理解程度从一个侧面反映了程序员的编程水平 其实C 43 43 语言本身并没有提供多线程机制 xff08 当然目前C 43
  • Android Studio 使用Log

    Android使用log来记录信息 xff0c 测试了下 xff0c 和system out println区别不大 xff0c 主要优势在于能使用过滤器过滤日志 本文记录基础的log使用方法 xff0c 来自 第一行代码 xff0c 以及
  • 指针强制转换问题

    void ff void abc 任意类型数据指针 xff08 指针即内存地址 xff09 int z 61 int abc 强制转换成int 指针变量 int zz 61 z 获取内存中的值
  • 新手git教程

    本文转载自 xff1a http igeekbar com igeekbar post 82 htm Git近些年的火爆程度非同一般 xff0c 这个版本控制系统被广泛地用在大型开源项目 xff08 比如Linux xff09 xff0c
  • 使用Project进行项目管理

    下面开始介绍Project的使用 1 从下列地址获取Project 2010的副本 版权问题 xff0c 已删除地址 2 安装 2 1 版权页 2 2 自定义安装页 2 3 安装完毕 3 使用该软件进行项目管理 3 1 打开Project
  • Marshal在C#中的应用(void *指针到IntPtr的转化)

    C 调用C语言的API时一般把void 指针转换成IntPtr xff0c 但这经常远远不够的 在C语言中void 是个万金油 xff0c 尤其是一些老的c语言程序 xff0c 所有的参数就一个void 指针 xff0c 里面包罗万象 xf
  • VS2012 2013 无法显示查找功能 无法具体定位 解决方法

    xfeff xfeff 问题的现象 通过使用 Ctrl 43 Shift 43 F 也就是Find In Files功能 xff0c 使用之后只能显示统计结果 不显示具体行 如下图 regedit 中在注册表中查找 xff1a HKEY C
  • C#中使用指针转换数据类型[C#/unsafe]

    今日因为一个同事说起 xff0c 在原来的旧系统中使用指针做数据转换很方便 xff0c 比如要把浮点数转化为数组 xff0c 也或者是字符串的相互转换 xff1b 当然 xff0c 大家都知道c 中实现指针只需要写入unsafe 编译选项把
  • c#指针的使用例程

    unsafe double value 61 888888 byte v1 61 BitConverter GetBytes value byte v2 61 new byte v1 Length double pv 61 amp valu
  • MPAndroidChart LineChart 折线图 你要的都在这里了

    前言 MPAndroidChart已经出了很长的一段时间 xff0c 相信大家也有所耳闻 xff0c 自己也使用了有一段时间 xff0c 固在此写下文章 xff0c 根据项目的需求 xff0c 记录一些见解与问题 xff0c 作为参考 望大
  • Android Chart框架 MPAndroidChart 坐标轴设置

    1 轴线的绘制 设置轴线就先必须取得轴线类Axis 在一个图标中有三个轴线 xff1a x轴 xff1a 调用 getXAxis 获取左边y轴 xff1a 调用 getAxisLeft 获取右边y轴 xff1a 调用 getAxisRigh
  • Android图表控件MPAndroidChart——曲线图LineChart的使用(财富收益图)

    目录 前言 本文涉及文章 其他相关文章 1 数据准备 1 1 数据来源 2 曲线展示 2 1 MPAndroidChart获取 2 2 数据对象获取 2 3 数据展示 3 曲线完善 3 1 图表背景 边框 网格线修改 3 2 X Y轴值的自
  • Android资源文件在配置文件中的使用

    Android资源文件大致可以分为两种 xff1a 第一种是res目录下存放的可编译的资源文件 xff1a 这种资源文件系统会在R Java里面自动生成该资源文件的ID xff0c 所以访问这种资源文件比较简单 xff0c 通过R XXX
  • android 如何创建配置文件和读配置文件

    因为一些配置信息 xff0c 多处用到的 且以后可能变更的 xff0c 我想写个 prorperties配置文件给管理起来 在studio中新建一个Assets文件 gt 新建一个file文件类型为properties文件 该文件可以与re
  • android上如何写配置文件

    android上如何写配置文件 xff1a 使用SharedPreferences SharedPreferences是Android平台上一个轻量级的存储类 xff0c 用来保存应用的一些常用配置 xff0c 比如Activity状态 x
  • android SharedPreferences的用法

    之前做应用时碰到这样一个问题 xff1a 在 A Activity 要与 B Activity的Fragment进行通信传值 xff0c 但是忽然发现无法拿到B中Fragment的Handler xff0c 又不能发送广播 xff0c 短暂
  • Android中数据库的一些操作(增删改查)

    提起Android的开发 xff0c 就不得不提数据库 xff0c 几乎每个App中都会用到Sqlit数据库存储一些数据 xff0c 小编闲暇时期 xff0c 写了一个小demo关于数据库的增删改查 xff0c 之前也介绍过数据库的一个开源
  • VC2008 无法调试,无法断点,断点无效的最终解决方法

    今天VC2008忽然又出现断点无效了 按上次博客说的格式化了源文件 也删了编译工程数据库文件 问题依就 经过测试找到了终解决方法 其实出现这个问题常常出现在工程中某一个源文件中 多是文件内的一些不可视符出了问题 比较多的是由于制表符Tab
  • 计蒜客 蓝桥杯模拟赛1 马的管辖

    代码来自https blog csdn net weixin 41793113 article details 86721181 在中国象棋中 xff0c 马是走日字的 一个马的管辖范围指的是当前位置以及一步之内能走到的位置 xff0c 下

随机推荐

  • 菜单项onCreateOptionsMenu()和onOptionsItemSelected()的使用

    函数onCreateOptionsMenu 为创建Menu菜单的项目 函数onOptionsItemSelected 为处理菜单被选中运行后的事件处理 首先看下Activity的内容 xff1a lt span style 61 34 fo
  • C# 解决窗体假死的状态 非常有用!且非常重要!

    异步调用是CLR为开发者提供的一种重要的编程手段 xff0c 它也是构建高性能 可伸缩应用程序的关键 在多核CPU越来越普及的今天 xff0c 异步编程允许使用非常少的线程执行很多操作 我们通常使用异步完成许多计算型 IO型的复杂 耗时操作
  • C# Message 消息处理

    一 消息概述 Windows下应用程序的执行是通过消息驱动的 消息是整个应用程序的工作引擎 xff0c 我们需要理解掌握我们使用的编程语言是如何封装消息的原理 C 自定义消息通信往往采用事件驱动的方式实现 xff0c 但有时候我们不得不采用
  • C++new和delete实现原理(汇编解释)

    new和delete最终调用malloc和free xff0c 关于malloc和free实现原理参见这篇文章 xff1a http blog csdn net passion wu128 article details 38964045
  • c++中创建类型测试

    OpenVCTest cpp 定义控制台应用程序的入口点 include 34 stdafx h 34 class A public int a 61 10 class B public B int a b 61 a B int b 61
  • 离散卷积

    离散卷积 是两个离散序列 f n 和 h n 之间按照一定的规则将它们的有关序列值分别两两相乘再相加的一种特殊的运算 具体可用公式表示为 xff1a 离散函数 png 简记为 g n 61 f n h n 其中 xff1a g n 是经过卷
  • 最容易理解的对卷积(convolution)的解释

    啰嗦开场白 读本科期间 xff0c 信号与系统里面经常讲到卷积 convolution xff0c 自动控制原理里面也会经常有提到卷积 硕士期间又学了线性系统理论与数字信号处理 xff0c 里面也是各种大把大把卷积的概念 至于最近大火的深度
  • 高斯滤波

    高斯滤波 高斯滤波的含义 xff1a 高斯滤波就是对整幅图像进行加权平均的过程 xff0c 每一个像素点的值 xff0c 都由其本身和邻域内的其他像素值经过加权平均后得到 高斯滤波的作用 xff1a 高斯滤波是一种线性平滑滤波 xff0c
  • OpenCV:详解掩膜mask

    在OpenCV中我们经常会遇到一个名字 Mask 掩膜 很多函数都使用到它 xff0c 那么这个Mask到底什么呢 xff1f 一开始我接触到Mask这个东西时 xff0c 我还真是一头雾水啊 xff0c 也对无法理解Mask到底有什么用
  • 图像卷积和图像滤波的一些知识

    转自 http blog csdn net zouxy09 article details 49080029 一 线性滤波与卷积的基本概念 线性滤波可以说是图像处理最基本的方法 xff0c 它可以允许我们对图像进行处理 xff0c 产生很多
  • No MDI forms are currently active 的解决方法

    描述一下运行环境 几个存放MDI Child 子窗体的DLL和一个管理这些子窗体MDI主程序 在主程序中动态调入DLL子窗体 首先 xff0c DLL中的Application和MDI主程序中的Application是不同样的 要将MDI主
  • 图像处理中腐蚀与膨胀的原理

    腐蚀的原理 xff1a 二值图像前景物体为1 xff0c 背景为0 假设原图像中有一个前景物体 xff0c 那么我们用一个结构元素去腐蚀原图的过程是这样的 xff1a 遍历原图像的每一个像素 xff0c 然后用结构元素的中心点对准当前正在遍
  • 卷积为什么如此强大?一文全解深度学习中的卷积

    卷积为什么如此强大 xff1f 一文全解深度学习中的卷积 2018年05月10日 15 52 41 七月在线实验室 阅读数 xff1a 17112 作者 xff1a Tim Dettmers xff08 Understanding Conv
  • 一维卷积详细解释(转载+自己笔记)

    一 定义 离散信号f n g n 的定义如下 xff1a N 为信号f n 的长度 s n 为卷积结果序列 长度为len f n 43 len g n 1 以3个元素的信号为例 xff1a f n 61 1 2 3 g n 61 2 3 1
  • 深度学习---图像卷积与反卷积(最完美的解释)

    转自 xff1a 卷积神经网络CNN xff08 1 xff09 图像卷积与反卷积 xff08 后卷积 xff0c 转置卷积 xff09 动态图 1 前言 传统的CNN网络只能给出图像的LABLE xff0c 但是在很多情况下需要对识别的物
  • 直观理解深度学习卷积部分

    转载自https www leiphone com news 201807 RQ4sBWYqLkGV4ZAW html 有删节 嵌牛导读 xff1a 在本文中 xff0c 我们将逐步分解卷积操作的原理 xff0c 将他与标准的全连接网络联系
  • 边缘检测(5)Canny算法

    边缘检测 1 Sobel 2 Laplace 3 Roberts 4 Canny Canny canny对边缘检测质量进行分析时 xff0c 有3个原则 xff1a 1 信噪比准则 2 定位精度准则 3 单边缘响应准则 canny边缘检测的
  • C++ OpenCV 图像转换,识别图像轮廓,画矩形

    1 include 34 ReadIDCard h 34 include 34 stdafx h 34 include lt iostream gt include lt stdio h gt include lt opencv2 open
  • 一阶导数概念

    定义 一般定义 设有定义域和取值都在实数域中的函数y 61 f x 若f x 在点 的某个邻域内有定义 xff0c 则当自变量x在x0处取得增量 xff08 点 仍在该邻域内 xff09 时 xff0c 相应地y取得增量 xff1b 如果
  • 使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!

    一 使用Opencv进行轮廓检测 xff01 所需函数 xff1a 1 cvFindContours 函数功能 xff1a 从二值图像中检索轮廓 xff0c 并返回检测到的轮廓的个数 函数原型 xff1a int cvFindContour