这是做者提供的图,对应的代码以下:函数
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的时候先到的就先获得,这样虽然归并错了,可是就是一两行的问题,影响不大,结果使得交叠的可以获得分割。!!!!完毕。图片