使用openCV去除文字中亂入的線條實(shí)例
今天上午,朋友發(fā)來一張圖片如下。沒錯(cuò),這就是原圖,他希望可以通過一些簡單的算法將圖中這條穿過單詞間的直線去掉,使得到的結(jié)果能夠通過他的文字識別算法并得出正確結(jié)果——The Techniques of Machine Vision。

乍一看這似乎挺簡單,(1)將圖像二值化;(2)找出這條直線;(3)將直線區(qū)域填成背景色(即白色);(4)再通過膨脹、腐蝕等操作將單詞缺失的部分給補(bǔ)全。以上4步似乎可以滿足要求,但測試發(fā)現(xiàn),效果不盡人意。
一、按上述方法實(shí)現(xiàn)過程

二值化結(jié)果如圖1.1所示,可以看到圖像并不標(biāo)準(zhǔn),直線粗細(xì)也不一,我們嘗試用霍夫變換找一下直線,代碼如下
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);
}
簡要解釋一下這段代碼。函數(shù)的功能是在輸入圖像中找出一條直線,輸入的圖像是灰度圖raw,返回值為dst,返回值是以圖片的形式,將找到的直線畫上圖中。
函數(shù)lines = cvHoughLines2(canny, stor, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 80, 200, 30);的參數(shù)表明,要求直線長度在200個(gè)像素以上,且兩條在同一直線上的線段,如果相隔不到30個(gè)像素,就把它們連起來【注:圖片尺寸為1066×148】。對于找到的多條直線,認(rèn)為最長的一條是我們要找的那條。找距離時(shí)用了abs(line[0].x - line[1].x);是不嚴(yán)格的,嚴(yán)格來講應(yīng)該是
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))
不過圖中的直線接近水平,這里就簡化一下啦。
所以將運(yùn)行這段代碼后,返回的圖片dst應(yīng)該是這樣子的

圖1.2中直線的粗線可以通過改變cvLine(dst, maxStart, maxEnd, cvScalar(255), 1);最后一個(gè)參數(shù)來調(diào)整,這里用的是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));
}
}
當(dāng)直線的寬度分別為2、3個(gè)像素時(shí),二值化圖去掉直線后的效果如下

可以看到,效果很差,如果要膨脹(黑色部分減?。瑔卧~下邊部分都會消失了,直接腐蝕(黑色部分增大),線又不能完全去掉。
后來,我采用的辦法是,對圖1.3重新找一次直線(減去一次直線后,中間還殘留一部分短些的直線),再減掉,再找再減掉。后面再對圖像進(jìn)行腐蝕(黑色部分增長)。最終效果最好這就如下圖所示

但這種方法用時(shí)長、針對不同的直線,找直線-減直線 的重復(fù)次數(shù)還不一樣,不具有可移植性。而且啊,這個(gè)圖片識別出來的結(jié)果是
The Technique_sJ_otMachine Vision
所以需要采用新的辦法來解決這個(gè)問題。
二、新的辦法
源代碼如下
#include <cv.h>
#include <highgui.h>
#include <iostream>
using namespace std;
/*
函數(shù)功能:在輸入圖像中找一條直線
輸入輸出:輸入的圖像是灰度圖raw,返回值為dst,返回值是一條白色的線
lines = cvHoughLines2(canny, stor, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 80, 200, 30);
參數(shù)中的200是指要找的直線長度要在200個(gè)像素以上;
參數(shù)中的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);
}
/*
函數(shù)功能:擦除面積小于【15個(gè)像素】的小塊兒
輸入輸出:無返回值,直接對輸入的圖像進(jìn)行操作
*/
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("結(jié)果", 1);
cvShowImage("結(jié)果", 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;
}
這個(gè)方法很簡單的,就是在找到直線(直線寬度為1)后,沿著直線從左到右對二值化圖進(jìn)行上下掃描,如果這個(gè)直線的寬度(黑色的寬度)小于8個(gè)像素,則認(rèn)為它只是直線,而不是文字的一部分,那么將它填成白色;反之,對于直線是文字的一部分這種情況,則不對它進(jìn)行任何操作。
這樣得到的結(jié)果如下圖2.1所示

當(dāng)然這個(gè)結(jié)果有點(diǎn)差強(qiáng)人意,如果你有更好的想法,請?jiān)谙旅媪粞?,我們交流交流?/p>
以上這篇使用openCV去除文字中亂入的線條實(shí)例就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
keras實(shí)現(xiàn)圖像預(yù)處理并生成一個(gè)generator的案例
這篇文章主要介紹了keras實(shí)現(xiàn)圖像預(yù)處理并生成一個(gè)generator的案例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-06-06
Python深度學(xué)習(xí)TensorFlow神經(jīng)網(wǎng)絡(luò)基礎(chǔ)概括
這篇文章主要為大家介紹了Python深度學(xué)習(xí)中TensorFlow神經(jīng)網(wǎng)絡(luò)基礎(chǔ)概括,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-10-10
利用Python實(shí)現(xiàn)一鍵將頭像轉(zhuǎn)成動漫風(fēng)
小編今天將為大家詳細(xì)介紹如何利用Python語言制作一個(gè)UI界面,大家可以通過一鍵點(diǎn)擊就實(shí)現(xiàn)頭像照片轉(zhuǎn)化成動漫風(fēng)格的功能,感興趣的可以動手嘗試一下2022-07-07
python 解決flask 圖片在線瀏覽或者直接下載的問題
今天小編就為大家分享一篇python 解決flask 圖片在線瀏覽或者直接下載的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-01-01
pytorch中model.named_parameters()與model.parameters()解讀
這篇文章主要介紹了pytorch中model.named_parameters()與model.parameters()使用及說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
利用python打印出菱形、三角形以及矩形的方法實(shí)例
最近在開發(fā)中遇到一個(gè)問題,需要利用python實(shí)現(xiàn)菱形、三角形以及矩形等形狀,發(fā)現(xiàn)網(wǎng)上這方面的資料較少,所以總結(jié)分享下,這篇文章主要給大家介紹了關(guān)于利用python打印出菱形、三角形以及矩形的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-08-08
解決Django數(shù)據(jù)庫makemigrations有變化但是migrate時(shí)未變動問題
今天小編就為大家分享一篇解決Django數(shù)據(jù)庫makemigrations有變化但是migrate時(shí)未變動的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-05-05

