You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

基于OpenCV提取文本及边界框:单词语义分组问题求助

解决OpenCV文本检测中单词级边界框的问题

我来帮你搞定OpenCV文本检测里的漏检和多词合并问题,让每个单词都能拥有自己独立的边界框!咱们从梯度法的优化和单词级分割两个核心方向入手:

一、优化梯度检测的基础流程

你当前用的梯度法,容易在边缘保留、阈值处理和形态学操作上出问题,先调整这几个关键环节:

  • 精细计算文本梯度
    直接处理RGB图不如先转灰度图再计算梯度靠谱,建议用Sobel算子分别计算x、y方向的梯度,再合并成梯度幅值图,算子大小可以根据文本粗细调整(3x3是常用的):

    Mat gray;
    cvtColor(large, gray, COLOR_BGR2GRAY);
    Mat grad_x, grad_y;
    Sobel(gray, grad_x, CV_32F, 1, 0, 3); // 计算x方向梯度
    Sobel(gray, grad_y, CV_32F, 0, 1, 3); // 计算y方向梯度
    Mat grad_mag;
    magnitude(grad_x, grad_y, grad_mag);
    convertScaleAbs(grad_mag, grad_mag); // 转换为8位可显示图像
    
  • 用自适应阈值替代固定阈值
    固定阈值很容易受光照不均影响,导致部分文本漏检或者相邻单词被合并。改用自适应阈值处理梯度图,能更好地适配不同区域的光照:

    Mat thresh;
    adaptiveThreshold(grad_mag, thresh, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 11, 2);
    
  • 小核形态学操作避免合并
    别用太大的结构元素做形态学操作!用2x2的小核先腐蚀再膨胀,既能消除细小噪点,又不会把相邻的单词连在一起:

    Mat kernel = getStructuringElement(MORPH_RECT, Size(2, 2));
    morphologyEx(thresh, thresh, MORPH_ERODE, kernel);
    morphologyEx(thresh, thresh, MORPH_DILATE, kernel);
    

二、实现单词级的边界框分割

解决多词合并的核心是把大的连通区域拆成单个单词,这里给你两种实用方法:

方法1:基于垂直投影分割大框

对于检测到的宽高比过大的连通框(大概率是多个单词),计算垂直方向的像素投影,找到单词之间的空隙来分割:

vector<vector<Point>> contours;
findContours(thresh.clone(), contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

for (auto &contour : contours) {
    Rect rect = boundingRect(contour);
    // 过滤过小的噪点框,宽高阈值根据你的文本大小调整
    if (rect.width < 10 || rect.height < 10) continue;
    
    // 判断是否是多词合并的大框(这里假设单词宽高比不超过3,可按需调整)
    if (rect.width > rect.height * 3) {
        Mat roi = thresh(rect);
        vector<int> vertical_proj(roi.cols, 0);
        // 计算每一列的非零像素数量(垂直投影)
        for (int col = 0; col < roi.cols; col++) {
            vertical_proj[col] = countNonZero(roi.col(col));
        }
        
        int start_col = 0;
        for (int col = 0; col < roi.cols; col++) {
            // 找到连续的空白列作为分割线
            if (vertical_proj[col] == 0 && start_col < col) {
                Rect word_rect(rect.x + start_col, rect.y, col - start_col, rect.height);
                rectangle(rgb, word_rect, Scalar(0, 255, 0), 2); // 绘制单词框
                start_col = col + 1;
            }
        }
        // 处理最后一个单词
        if (start_col < roi.cols) {
            Rect word_rect(rect.x + start_col, rect.y, roi.cols - start_col, rect.height);
            rectangle(rgb, word_rect, Scalar(0, 255, 0), 2);
        }
    } else {
        // 单个单词直接绘制边界框
        rectangle(rgb, rect, Scalar(0, 255, 0), 2);
    }
}

方法2:距离变换+漫水填充分割单词

距离变换能突出文本的核心区域,提取种子点后用漫水填充就能精准分割出每个单词:

Mat dist;
distanceTransform(thresh, dist, DIST_L2, 3);
normalize(dist, dist, 0, 1.0, NORM_MINMAX);
// 阈值提取单词核心区域作为种子点
Mat seeds;
threshold(dist, seeds, 0.4, 1, THRESH_BINARY);
seeds.convertTo(seeds, CV_8U);

// 查找种子点的轮廓,每个轮廓对应一个单词
vector<vector<Point>> word_contours;
findContours(seeds, word_contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (auto &word_contour : word_contours) {
    Rect word_rect = boundingRect(word_contour);
    // 过滤噪点框
    if (word_rect.width >= 10 && word_rect.height >= 10) {
        rectangle(rgb, word_rect, Scalar(0, 255, 0), 2);
    }
}

三、额外的小技巧

  • 预处理去噪:在梯度计算前加一步高斯模糊,能有效减少噪点干扰:
    GaussianBlur(gray, gray, Size(3, 3), 0);
    
  • 调整参数适配你的图像:所有的阈值(比如自适应阈值的窗口大小、距离变换的阈值、宽高比阈值)都需要根据你的实际文本图像来微调,多试几次就能找到最合适的数值。

内容的提问来源于stack exchange,提问作者Kailash

火山引擎 最新活动