用openCV去除文字中乱入的线条

本文介绍了一种利用OpenCV去除图像中干扰线条的方法,通过对二值化图像进行上下扫描,判断并移除线条,避免影响文字识别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

用openCV去除文字中乱入的线条

原文:http://blog.youkuaiyun.com/sinat_28309919/article/details/73604109

目录(?)[+]

      今天上午,朋友发来一张图片如下。没错,这就是原图,他希望可以通过一些简单的算法将图中这条穿过单词间的直线去掉,使得到的结果能够通过他的文字识别算法并得出正确结果——The Techniques of Machine Vision。

      乍一看这似乎挺简单,(1)将图像二值化;(2)找出这条直线;(3)将直线区域填成背景色(即白色);(4)再通过膨胀、腐蚀等操作将单词缺失的部分给补全。以上4步似乎可以满足要求,但测试发现,效果不尽人意。

一、按上述方法实现过程

图1.1 对原图使用大津阈值的结果

二值化结果如图1.1所示,可以看到图像并不标准,直线粗细也不一,我们尝试用霍夫变换找一下直线,代码如下

[cpp]  view plain  copy
  1. <span style="font-size:12px;">void findLines(IplImage* raw, IplImage* dst)  
  2. {  
  3.     IplImage* src = cvCloneImage(raw);  
  4.     IplImage* canny = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);  
  5.     cvCanny(src, canny, 20, 200, 3);  
  6.     CvMemStorage* stor = cvCreateMemStorage(0);  
  7.     CvSeq* lines = NULL;  
  8.     lines = cvHoughLines2(canny, stor, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 80, 200, 30);  
  9.     cvZero(dst);  
  10.     CvPoint maxStart, maxEnd;  
  11.     int maxDistance = 0;  
  12.     for (int i = 0; i < lines->total; i++)  
  13.     {  
  14.         CvPoint* line = (CvPoint*)cvGetSeqElem(lines, i);  
  15.         if (abs(line[0].x - line[1].x) > maxDistance)  
  16.         {  
  17.             maxDistance = abs(line[0].x - line[1].x);  
  18.             maxStart = line[0];  
  19.             maxEnd = line[1];  
  20.         }  
  21.     }  
  22.     cvLine(dst, maxStart, maxEnd, cvScalar(255), 1);  
  23.     cvReleaseImage(&src);  
  24.     cvReleaseMemStorage(&stor);  
  25. }</span>  

      简要解释一下这段代码。函数的功能是在输入图像中找出一条直线,输入的图像是灰度图raw,返回值为dst,返回值是以图片的形式,将找到的直线画上图中。
      函数lines = cvHoughLines2(canny, stor, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 80, 200, 30);的参数表明,要求直线长度在200个像素以上,且两条在同一直线上的线段,如果相隔不到30个像素,就把它们连起来【注:图片尺寸为1066×148】。对于找到的多条直线,认为最长的一条是我们要找的那条。找距离时用了abs(line[0].x - line[1].x);是不严格的,严格来讲应该是
sqrt((line[0].x - line[1].x)*(line[0].x - line[1].x)+(line[0].y - line[1].y)*(line[0].x - line[1].x))
不过图中的直线接近水平,这里就简化一下啦。

      所以将运行这段代码后,返回的图片dst应该是这样子的

图1.2 通过霍夫变换找到的直线

      图1.2中直线的粗线可以通过改变cvLine(dst, maxStart, maxEnd, cvScalar(255), 1);最后一个参数来调整,这里用的是1。

      接下来步骤就是在二值化图(图1.1)中去掉这条线,代码如下:

[cpp]  view plain  copy
  1. <span style="font-size:12px;">void eraseLine(IplImage* src, IplImage* flag)  
  2. {// flag为图1.2所示的图片,src为图1.1所示的二值化图片  
  3.     for (int row = 0; row < src->height; row++)  
  4.         for (int col = 0; col < src->width; col++)  
  5.         {   // 如果在白色线段上,则将二值化图片填为白色  
  6.             if (cvGet2D(flag, row, col).val[0] == 255)  
  7.                 cvSet2D(src, row, col, cvScalar(255));  
  8.         }  
  9. }</span>  
当直线的宽度分别为2、3个像素时,二值化图去掉直线后的效果如下


图1.3 当线宽分别为2、3像素时,二值化图去掉直线后的结果

可以看到,效果很差,如果要膨胀(黑色部分减小),单词下边部分都会消失了,直接腐蚀(黑色部分增大),线又不能完全去掉。

      后来,我采用的办法是,对图1.3重新找一次直线(减去一次直线后,中间还残留一部分短些的直线),再减掉,再找再减掉。后面再对图像进行腐蚀(黑色部分增长)。最终效果最好这就如下图所示

图1.4 此方法效果最好的结果

      但这种方法用时长、针对不同的直线,找直线-减直线 的重复次数还不一样,不具有可移植性。而且啊,这个图片识别出来的结果是

The Technique_sJ_otMachine Vision

所以需要采用新的办法来解决这个问题。

二、新的办法

源代码如下

[cpp]  view plain  copy
  1. #include <cv.h>  
  2. #include <highgui.h>  
  3. #include <iostream>  
  4. using namespace std;  
  5. /* 
  6. 函数功能:在输入图像中找一条直线 
  7. 输入输出:输入的图像是灰度图raw,返回值为dst,返回值是一条白色的线 
  8. lines = cvHoughLines2(canny, stor, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 80, 200, 30); 
  9. 参数中的200是指要找的直线长度要在200个像素以上; 
  10. 参数中的30指的是两条在同一直线上的线段,如果相隔不到30,则把它们连起来 
  11. */  
  12. void findLines(IplImage* raw, IplImage* dst)  
  13. {  
  14.     IplImage* src = cvCloneImage(raw); // clone the input image  
  15.     IplImage* canny = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);   // create a tmp image head to save gradient image  
  16.     cvCanny(src, canny, 20, 200, 3);    // Generate its gradient image  
  17.     CvMemStorage* stor = cvCreateMemStorage(0);  
  18.     CvSeq* lines = NULL;  
  19.     // find a line whose length bigger than 200 pixels  
  20.     lines = cvHoughLines2(canny, stor, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 80, 200, 30);  
  21.     cvZero(dst);  
  22.     CvPoint maxStart, maxEnd;   // save the coordinate of the head and rear of the line we want  
  23.     int maxDistance = 0;        // The maximum distance of all lines found by [cvHoughLines2]  
  24.     for (int i = 0; i < lines->total; i++)    // lines->total: the number of lines   
  25.     {  
  26.         // variable 'lines' is a sequence, [cvGetSeqElem] gets the (i)th line, and it returns its head and rear.  
  27.         CvPoint* line = (CvPoint*)cvGetSeqElem(lines, i);     
  28.         // line[0] and line[1] is respectively the line's coordinate of its head and rear  
  29.         if (abs(line[0].x - line[1].x) > maxDistance)  
  30.         {/*  It's a trick because the line is almost horizontal. 
  31.          strictly, it should be  
  32.         sqrt((line[0].x - line[1].x)*(line[0].x - line[1].x)+(line[0].y - line[1].y)*(line[0].x - line[1].x)) 
  33.         */  
  34.             maxDistance = abs(line[0].x - line[1].x);  
  35.             maxStart = line[0];  
  36.             maxEnd = line[1];  
  37.         }  
  38.     }  
  39.     cvLine(dst, maxStart, maxEnd, cvScalar(255), 1);    // draw the white line[cvScalar(255)] in a black background  
  40.     cvReleaseImage(&src);                               // free the memory  
  41.     cvReleaseMemStorage(&stor);  
  42. }  
  43. /* 
  44. 函数功能:擦除面积小于【15个像素】的小块儿 
  45. 输入输出:无返回值,直接对输入的图像进行操作 
  46. */  
  47. void erase(IplImage* raw)  
  48. {  
  49.     IplImage* src = cvCloneImage(raw);  
  50.     /*Binarization and inverse the black and white because the function next only find white area while 
  51.     the word in image is black.*/  
  52.     cvThreshold(src, src, 120, 255, CV_THRESH_BINARY_INV);    
  53.     // create some space to save the white areas but we access it via variable 'cont'  
  54.     CvMemStorage* stor = cvCreateMemStorage(0);   
  55.     CvSeq* cont;  
  56.     cvFindContours(src, stor, &cont, sizeof(CvContour), CV_RETR_EXTERNAL); // find the white regions  
  57.     for (; cont; cont = cont->h_next) // Traversal  
  58.     {  
  59.         if (fabs(cvContourArea(cont)) < 15)  // if its Area smaller than 15, we fill it with white[cvScalar(255)]  
  60.             cvDrawContours(raw, cont, cvScalar(255), cvScalar(255), 0, CV_FILLED, 8);  
  61.     }  
  62.     cvReleaseImage(&src);  
  63. }  
  64.   
  65. int main()  
  66. {  
  67.     IplImage* src = cvLoadImage("D:/test.png");  
  68.     cvNamedWindow("原图", 1);  
  69.     cvShowImage("原图", src);  
  70.     IplImage* gray = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);  
  71.     IplImage* canny = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);  
  72.     IplImage* dst = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);  
  73.     IplImage* binary = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);  
  74.   
  75.     cvCvtColor(src, gray, CV_RGB2GRAY);  
  76.     cvThreshold(gray, binary, 120, 255, CV_THRESH_OTSU);  
  77.   
  78.     findLines(gray, dst);  
  79.     cvNamedWindow("dst", 1);  
  80.     cvShowImage("dst", dst);  
  81.       
  82.     for (int row = 0; row < binary->height; row++)  
  83.     for (int col = 0; col < binary->width; col++)  
  84.     {  
  85.         if (cvGet2D(dst, row, col).val[0] == 255)  
  86.         {  
  87.             int up = 0, down = 0;  
  88.             int white = 0;  
  89.             for (int i = row; i >= 0; i--)  
  90.             {  
  91.                 if (cvGet2D(binary, i, col).val[0] == 0)  
  92.                 {  
  93.                     up++;   
  94.                     white = 0;  
  95.                 }  
  96.                 else white++;  
  97.                 if(white > 2)    break;  
  98.             }  
  99.             white = 0;  
  100.             for (int i = row; i < binary->height; i++)  
  101.             {  
  102.                 if (cvGet2D(binary, i, col).val[0] == 0)  
  103.                 {  
  104.                     down++;  
  105.                     white = 0;  
  106.                 }  
  107.                 else white++;  
  108.                 if (white > 2)   break;  
  109.             }  
  110.             if (up + down < 8)  
  111.             {  
  112.                 for (int i = -up; i <= down; i++) cvSet2D(binary, row + i, col, cvScalar(255));  
  113.             }  
  114.         }  
  115.     }  
  116.     cvNamedWindow("结果", 1);  
  117.     cvShowImage("结果", binary);  
  118.     erase(binary);  
  119.     //cvDilate(binary, binary, NULL, 1);  
  120.     cvErode(binary, binary, NULL, 1);  
  121.     cvNamedWindow("膨胀腐蚀", 1);  
  122.     cvShowImage("膨胀腐蚀", binary);  
  123.     cvSaveImage("D:/result.png", binary);  
  124.     cvReleaseImage(&src);  
  125.     cvReleaseImage(&canny);  
  126.     cvReleaseImage(&gray);  
  127.     cvReleaseImage(&dst);  
  128.     cvReleaseImage(&binary);  
  129.     cvWaitKey(0);  
  130.     return 0;  
  131. }  


      这个方法很简单的,就是在找到直线(直线宽度为1)后,沿着直线从左到右对二值化图进行上下扫描,如果这个直线的宽度(黑色的宽度)小于8个像素,则认为它只是直线,而不是文字的一部分,那么将它填成白色;反之,对于直线是文字的一部分这种情况,则不对它进行任何操作。

这样得到的结果如下图2.1所示

图2.1 新方法运行结果

      当然这个结果有点差强人意,如果你有更好的想法,请在下面留言,我们交流交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值