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的时候先到的就先得到,这样虽然归并错了,但是就是一两行的问题,影响不大,结果使得交叠的能够得到分割。!!!!完毕。

原文地址:https://www.cnblogs.com/yanghailin/p/11576504.html