psenet的后處理


深度學習ocr交流qq群:1020395892

這是作者提供的圖,對應的代碼如下:

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 &kernals 是分割出來的核,一般取前三個,就是說vector的size=3
vector<vector > &text_line是傳出去的,和圖片大小一樣,比如一個圖有10個文本,那個這個傳出去的像素范圍就是0-10,0代表背景,1代表第一個文本輪廓,第一個文本輪廓內像素值都為1,類推。其中,vector &kernals中的每個圖片也是這樣
min_area 是閾值(300),過濾小的干擾點。

  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鄰域

改注釋的都在代碼里面了,其實一開始理解這個循環也理解了好久,有的地方怎么想都沒有想明白,比如,這個代碼是如何處理兩個邊界融合在一起的。下面分簡單和容易的來:第一種簡單的情況:

比如這兩張圖,左邊是瘦的那個,右邊是輪廓稍微擴張了一點,黃色區域就是比左邊的稍微外擴一點的。這是沒有交疊的情況,
if (kernals[kernal_id].at (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 (tmp_x, tmp_y) == 0) continue;
if (text_line[tmp_x][tmp_y] > 0) continue;
// 能夠下來的需要滿足兩個條件: 1. (kernals[kernal_id].at (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的時候先到的就先得到,這樣雖然歸並錯了,但是就是一兩行的問題,影響不大,結果使得交疊的能夠得到分割。!!!!完畢。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM