今天上午,朋友发来一张图片如下。没错,这就是原图,他希望可以通过一些简单的算法将图中这条穿过单词间的直线去掉,使得到的结果能够通过他的文字识别算法并得出正确结果——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) {// 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个像素时,二值化图去掉直线后的效果如下
可以看到,效果很差,如果要膨胀(黑色部分减小),单词下边部分都会消失了,直接腐蚀(黑色部分增大),线又不能完全去掉。
后来,我采用的办法是,对图1.3重新找一次直线(减去一次直线后,中间还残留一部分短些的直线),再减掉,再找再减掉。后面再对图像进行腐蚀(黑色部分增长)。最终效果最好这就如下图所示
但这种方法用时长、针对不同的直线,找直线-减直线 的重复次数还不一样,不具有可移植性。而且啊,这个图片识别出来的结果是
The Technique_sJ_otMachine Vision
所以需要采用新的办法来解决这个问题。
二、新的办法
源代码如下
#include <cv.h> #include <highgui.h> #include <iostream> using namespace std; /* 函数功能:在输入图像中找一条直线 输入输出:输入的图像是灰度图raw,返回值为dst,返回值是一条白色的线 lines = cvHoughLines2(canny, stor, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 80, 200, 30); 参数中的200是指要找的直线长度要在200个像素以上; 参数中的30指的是两条在同一直线上的线段,如果相隔不到30,则把它们连起来 */ void findLines(IplImage* raw, IplImage* dst) { IplImage* src = cvCloneImage(raw); // clone the input image IplImage* canny = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); // create a tmp image head to save gradient image cvCanny(src, canny, 20, 200, 3); // Generate its gradient image CvMemStorage* stor = cvCreateMemStorage(0); CvSeq* lines = NULL; // find a line whose length bigger than 200 pixels lines = cvHoughLines2(canny, stor, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 80, 200, 30); cvZero(dst); CvPoint maxStart, maxEnd; // save the coordinate of the head and rear of the line we want int maxDistance = 0; // The maximum distance of all lines found by [cvHoughLines2] for (int i = 0; i < lines->total; i++) // lines->total: the number of lines { // variable 'lines' is a sequence, [cvGetSeqElem] gets the (i)th line, and it returns its head and rear. CvPoint* line = (CvPoint*)cvGetSeqElem(lines, i); // line[0] and line[1] is respectively the line's coordinate of its head and rear if (abs(line[0].x - line[1].x) > maxDistance) {/* It's a trick because the line is almost horizontal. strictly, it should be 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)) */ maxDistance = abs(line[0].x - line[1].x); maxStart = line[0]; maxEnd = line[1]; } } cvLine(dst, maxStart, maxEnd, cvScalar(255), 1); // draw the white line[cvScalar(255)] in a black background cvReleaseImage(&src); // free the memory cvReleaseMemStorage(&stor); } /* 函数功能:擦除面积小于【15个像素】的小块儿 输入输出:无返回值,直接对输入的图像进行操作 */ void erase(IplImage* raw) { IplImage* src = cvCloneImage(raw); /*Binarization and inverse the black and white because the function next only find white area while the word in image is black.*/ cvThreshold(src, src, 120, 255, CV_THRESH_BINARY_INV); // create some space to save the white areas but we access it via variable 'cont' CvMemStorage* stor = cvCreateMemStorage(0); CvSeq* cont; cvFindContours(src, stor, &cont, sizeof(CvContour), CV_RETR_EXTERNAL); // find the white regions for (; cont; cont = cont->h_next) // Traversal { if (fabs(cvContourArea(cont)) < 15) // if its Area smaller than 15, we fill it with white[cvScalar(255)] cvDrawContours(raw, cont, cvScalar(255), cvScalar(255), 0, CV_FILLED, 8); } cvReleaseImage(&src); } int main() { IplImage* src = cvLoadImage("D:/test.png"); cvNamedWindow("原图", 1); cvShowImage("原图", src); IplImage* gray = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); IplImage* canny = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); IplImage* dst = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); IplImage* binary = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); cvCvtColor(src, gray, CV_RGB2GRAY); cvThreshold(gray, binary, 120, 255, CV_THRESH_OTSU); findLines(gray, dst); cvNamedWindow("dst", 1); cvShowImage("dst", dst); for (int row = 0; row < binary->height; row++) for (int col = 0; col < binary->width; col++) { if (cvGet2D(dst, row, col).val[0] == 255) { int up = 0, down = 0; int white = 0; for (int i = row; i >= 0; i--) { if (cvGet2D(binary, i, col).val[0] == 0) { up++; white = 0; } else white++; if(white > 2) break; } white = 0; for (int i = row; i < binary->height; i++) { if (cvGet2D(binary, i, col).val[0] == 0) { down++; white = 0; } else white++; if (white > 2) break; } if (up + down < 8) { for (int i = -up; i <= down; i++) cvSet2D(binary, row + i, col, cvScalar(255)); } } } cvNamedWindow("结果", 1); cvShowImage("结果", binary); erase(binary); //cvDilate(binary, binary, NULL, 1); cvErode(binary, binary, NULL, 1); cvNamedWindow("膨胀腐蚀", 1); cvShowImage("膨胀腐蚀", binary); cvSaveImage("D:/result.png", binary); cvReleaseImage(&src); cvReleaseImage(&canny); cvReleaseImage(&gray); cvReleaseImage(&dst); cvReleaseImage(&binary); cvWaitKey(0); return 0; }
这个方法很简单的,就是在找到直线(直线宽度为1)后,沿着直线从左到右对二值化图进行上下扫描,如果这个直线的宽度(黑色的宽度)小于8个像素,则认为它只是直线,而不是文字的一部分,那么将它填成白色;反之,对于直线是文字的一部分这种情况,则不对它进行任何操作。
这样得到的结果如下图2.1所示
当然这个结果有点差强人意,如果你有更好的想法,请在下面留言,我们交流交流。
以上这篇使用openCV去除文字中乱入的线条实例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
openCV,文字,乱入线条
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 【雨果唱片】中国管弦乐《鹿回头》WAV
- APM亚流新世代《一起冒险》[FLAC/分轨][106.77MB]
- 崔健《飞狗》律冻文化[WAV+CUE][1.1G]
- 罗志祥《舞状元 (Explicit)》[320K/MP3][66.77MB]
- 尤雅.1997-幽雅精粹2CD【南方】【WAV+CUE】
- 张惠妹.2007-STAR(引进版)【EMI百代】【WAV+CUE】
- 群星.2008-LOVE情歌集VOL.8【正东】【WAV+CUE】
- 罗志祥《舞状元 (Explicit)》[FLAC/分轨][360.76MB]
- Tank《我不伟大,至少我能改变我。》[320K/MP3][160.41MB]
- Tank《我不伟大,至少我能改变我。》[FLAC/分轨][236.89MB]
- CD圣经推荐-夏韶声《谙2》SACD-ISO
- 钟镇涛-《百分百钟镇涛》首批限量版SACD-ISO
- 群星《继续微笑致敬许冠杰》[低速原抓WAV+CUE]
- 潘秀琼.2003-国语难忘金曲珍藏集【皇星全音】【WAV+CUE】
- 林东松.1997-2039玫瑰事件【宝丽金】【WAV+CUE】