今天上午,朋友发来一张图片如下。没错,这就是原图,他希望可以通过一些简单的算法将图中这条穿过单词间的直线去掉,使得到的结果能够通过他的文字识别算法并得出正确结果——The Techniques of Machine Vision。
乍一看这似乎挺简单,(1)将图像二值化;(2)找出这条直线;(3)将直线区域填成背景色(即白色);(4)再通过膨胀、腐蚀等操作将单词缺失的部分给补全。以上4步似乎可以满足要求,但测试发现,效果不尽人意。
一、按上述方法实现过程
二值化结果如图1.1所示,可以看到图像并不标准,直线粗细也不一,我们尝试用霍夫变换找一下直线,代码如下
void findLines(IplImage* raw, IplImage* dst) { IplImage* src = cvCloneImage(raw); IplImage* canny = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); cvCanny(src, canny, 20, 200, 3); CvMemStorage* stor = cvCreateMemStorage(0); CvSeq* lines = NULL; lines = cvHoughLines2(canny, stor, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 80, 200, 30); cvZero(dst); CvPoint maxStart, maxEnd; int maxDistance = 0; for (int i = 0; i < lines->total; i++) { CvPoint* line = (CvPoint*)cvGetSeqElem(lines, i); if (abs(line[0].x - line[1].x) > maxDistance) { maxDistance = abs(line[0].x - line[1].x); maxStart = line[0]; maxEnd = line[1]; } } cvLine(dst, maxStart, maxEnd, cvScalar(255), 1); cvReleaseImage(&src); cvReleaseMemStorage(&stor); }
简要解释一下这段代码。函数的功能是在输入图像中找出一条直线,输入的图像是灰度图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中直线的粗线可以通过改变cvLine(dst, maxStart, maxEnd, cvScalar(255), 1);最后一个参数来调整,这里用的是1。
接下来步骤就是在二值化图(图1.1)中去掉这条线,代码如下:
void eraseLine(IplImage* src, IplImage* flag) {/<div style="color:transparent">本文来源gaodai.ma#com搞##代!^码@网*</div>/ flag为图1.2所示的图片,src为图1.1所示的二值化图片 for (int row = 0; row < src->height; row++) for (int col = 0; col < src->width; col++) { // 如果在白色线段上,则将二值化图片填为白色 if (cvGet2D(flag, row, col).val[0] == 255) cvSet2D(src, row, col, cvScalar(255)); } }
当直线的宽度分别为2、3个像素时,二值化图去掉直线后的效果如下
可以看到,效果很差,如果要膨胀(黑色部分减小),单词下边部分都会消失了,直接腐蚀(黑色部分增大),线又不能完全去掉。