這是做者提供的圖,對應的代碼以下:函數
void growing_text_line(vector<Mat> &kernals, vector<vector<int>> &text_line, float min_area) { Mat label_mat; int label_num = connectedComponents(kernals[kernals.size() - 1], label_mat, 4); // cout << "label num: " << label_num << endl; int area[label_num + 1];//統計每一個文字塊像素的個數即面積 memset(area, 0, sizeof(area)); for (int x = 0; x < label_mat.rows; ++x) { for (int y = 0; y < label_mat.cols; ++y) { int label = label_mat.at<int>(x, y); if (label == 0) continue; area[label] += 1; } } queue<Point> queue, next_queue;//重要:隊列,先進先出 for (int x = 0; x < label_mat.rows; ++x) { vector<int> row(label_mat.cols); for (int y = 0; y < label_mat.cols; ++y) { int label = label_mat.at<int>(x, y); if (label == 0) continue; if (area[label] < min_area) continue; Point point(x, y); queue.push(point);//重要:隊列保存非0位置 row[y] = label;//非0的label保存 } text_line.emplace_back(row); } // text_line: 傳出去的text_line先保存了最瘦的那個分割圖各個label // cout << "ok" << endl; //4鄰域 int dx[] = {-1, 1, 0, 0}; int dy[] = {0, 0, -1, 1}; // 從倒數第二個開始,由於是以倒數第一個最瘦的爲基礎的 for (int kernal_id = kernals.size() - 2; kernal_id >= 0; --kernal_id) { while (!queue.empty()) { Point point = queue.front(); queue.pop(); int x = point.x; int y = point.y; int label = text_line[x][y]; // cout << text_line.size() << ' ' << text_line[0].size() << ' ' << x << ' ' << y << endl; bool is_edge = true; for (int d = 0; d < 4; ++d) { int tmp_x = x + dx[d]; int tmp_y = y + dy[d]; if (tmp_x < 0 || tmp_x >= (int)text_line.size()) continue; if (tmp_y < 0 || tmp_y >= (int)text_line[1].size()) continue; if (kernals[kernal_id].at<char>(tmp_x, tmp_y) == 0) continue; if (text_line[tmp_x][tmp_y] > 0) continue; // 可以下來的須要知足兩個條件: 1. (kernals[kernal_id].at<char>(tmp_x, tmp_y) != 0) 2. (text_line[tmp_x][tmp_y] == 0) // 即: 1. 上個分割圖對應位置上有東西 2. 本位置無東西 // 知足這兩個條件就放到隊列最後(queue.push(point));,同時把該位置歸化爲本身的label( text_line[tmp_x][tmp_y] = label;) Point point(tmp_x, tmp_y); queue.push(point); text_line[tmp_x][tmp_y] = label; is_edge = false; } if (is_edge) {//注:當前點都是有東西的 若是當前點任一鄰域有東西(文字塊內)或者當前點任一鄰域對應的上一個分割圖位置上沒有東西(文字塊邊界) next_queue.push(point); } } swap(queue, next_queue); } }
其中參數,vector<Mat> &kernals 是分割出來的核,通常取前三個,就是說vector的size=3 vector<vector<int>> &text_line是傳出去的,和圖片大小同樣,好比一個圖有10個文本,那個這個傳出去的像素範圍就是0-10,0表明背景,1表明第一個文本輪廓,第一個文本輪廓內像素值都爲1,類推。其中,vector<Mat> &kernals中的每一個圖片也是這樣 min_area 是閾值(300),過濾小的干擾點。code
int label_num = connectedComponents(kernals[kernals.size() - 1], label_mat, 4);
connectedComponents是opencv函數,傳入的kernals[kernals.size() - 1]是最小的核,就是最瘦的那個,label_mat是傳出,傳出的是標籤圖,好比kernals[kernals.size() - 1]有6個文字塊,那個label_mat就把每一個文字塊裏面編號,好比第一個文字塊裏面像素全爲1,第二個文字塊裏面像素全爲2,類推。其他非文字塊區域爲0.同時,返回值label_num爲文字塊個數6. 還有一個參數4是4鄰域blog
改註釋的都在代碼裏面了,其實一開始理解這個循環也理解了很久,有的地方怎麼想都沒有想明白,好比,這個代碼是如何處理兩個邊界融合在一塊兒的。下面分簡單和容易的來:第一種簡單的狀況: 好比這兩張圖,左邊是瘦的那個,右邊是輪廓稍微擴張了一點,黃色區域就是比左邊的稍微外擴一點的。這是沒有交疊的狀況, if (kernals[kernal_id].at<char>(tmp_x, tmp_y) == 0) continue; if (text_line[tmp_x][tmp_y] > 0) continue; text_line是左邊這張圖,queue從上到下從左到右記錄了左邊這張圖非0區域, (kernals[kernal_id]是右邊這張圖,好比左邊上面第一個輪廓內,都會知足 if (text_line[tmp_x][tmp_y] > 0) continue;這個條件,(即輪廓內的任一鄰域都有東西),從而is_edge=true;就會把當前點 next_queue.push(point);給到下一個隊列。這裏彷佛沒有啥難理解的。 第二種狀況,有交疊了: 按照上面的: if (kernals[kernal_id].at<char>(tmp_x, tmp_y) == 0) continue; if (text_line[tmp_x][tmp_y] > 0) continue; // 可以下來的須要知足兩個條件: 1. (kernals[kernal_id].at<char>(tmp_x, tmp_y) != 0) 2. (text_line[tmp_x][tmp_y] == 0) // 即: 1. 上個分割圖對應位置上有東西 2. 本位置無東西 就是看本圖位置鄰域沒有東西,同時上一個分割圖對應位置有東西,咱們就把該鄰域歸一化爲本身,按照我一開始這樣的邏輯,想,那麼第二個輪廓擴展的都要被第一個輪廓吞併了?成下面這樣: ,再來一個圖: ,最左邊圖,而後下邊有多出1,那麼原本最左邊圖下面沒有1,第二個圖下面有東西,那個我就要把你歸併,按照我想的,最後歸併後應該是最右邊那個圖了,困擾了我很久很久。。。(其實這個圖還有一個錯誤的就是歸一化後第一張圖也要改變,須要把歸一化的也改成本身的)後來在同事的提醒下,隊列,先進先出!!!其實並非我想的這樣子,它是有順序的,好比剛剛最簡單的狀況: 隊列
這個其實一開始queue壓入的順序是從上到下,從左到右,完成一次迭代是最右邊那個圖的樣子,並非一個輪廓擴張完了再擴另一個的。一樣的,交疊的狀況: 如圖最右邊是假設已經擴到交疊處了,那麼上面的最右邊要處理交疊的狀況。 好比,中間圖擴張,會把以前的text_line歸併,成第三個圖,上面,好比最下面的1,1,下面的鄰域,發現text_line沒有東西,而kernals[kernal_id]有東西,就把2歸併爲本身的1,而後第一個輪廓歸併結束,同時,第二個下面的輪廓,準備歸併一樣位置的時候,發現text_line有東西,就不能再擴了,這樣就是先到先得,1歸併2的時候先到的就先獲得,這樣雖然歸併錯了,可是就是一兩行的問題,影響不大,結果使得交疊的可以獲得分割。!!!!完畢。圖片