一.使用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;
-
}
一.使用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;
-
}