You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React项目中Tesseract.js处理多列图片OCR文本混排问题求解

解决Tesseract OCR处理多列布局图片时的文本混排问题

问题场景

在React项目中使用Tesseract.js识别两/三列布局的图片时,不同列的文本出现混排,无法按列顺序输出正确内容,尝试过OpenCV.js分割列但未成功。

可行解决方案

方案1:分析Tesseract的文本坐标,按列分组

Tesseract识别时会返回每个文本块的边界框(BBox)坐标,我们可以通过这些坐标判断文本块属于哪一列,再按行顺序拼接同一列的文本。

修改你的handleSubmit函数,利用result.data.words中的坐标信息实现列分组:

const handleSubmit = async () => {
  // 原有校验逻辑不变
  if (images.length === 0) {
    setErrorMessage("Select an image to convert.");
    return;
  }
  if (selectedLanguages.length === 0) {
    setErrorLanguagesMessage("Select any language.");
    return;
  }

  setIsLoading(true);
  setProgress(0);
  setTexts([]);
  setCurrentImageIndex(0);
  setErrorMessage("");
  setErrorLanguagesMessage("");

  const totalImages = images.length;
  let processedImages = 0;

  if (Array.isArray(images)) {
    for (const [index, image] of images?.entries()) {
      setCurrentImageIndex(index + 1);

      try {
        const result = await Tesseract.recognize(
          image,
          selectedLanguages.map((lang) => lang.value).join("+"),
          {
            tessedit_pageseg_mode: 3, // 启用自动页面布局检测
          }
        );

        // 提取所有带坐标的文本块
        const textBlocks = result.data.words.map(word => ({
          text: word.text,
          x: word.bbox.x0, // 文本块左侧x坐标
          y: word.bbox.y0, // 文本块顶部y坐标
          width: word.bbox.x1 - word.bbox.x0
        }));

        // 按x坐标分组(判断列)
        const columnThreshold = 50; // 列之间的最小间距,可根据图片调整
        textBlocks.sort((a, b) => a.x - b.x);
        const columns = [];
        let currentColumn = [textBlocks[0]];
        for (let i = 1; i < textBlocks.length; i++) {
          const diff = textBlocks[i].x - (currentColumn[currentColumn.length-1].x + currentColumn[currentColumn.length-1].width);
          if (diff > columnThreshold) {
            columns.push(currentColumn);
            currentColumn = [textBlocks[i]];
          } else {
            currentColumn.push(textBlocks[i]);
          }
        }
        columns.push(currentColumn);

        // 每列按y坐标排序,再拼接文本
        const columnTexts = columns.map(col => {
          col.sort((a, b) => a.y - b.y);
          return col.map(block => block.text).join(" ");
        });

        // 按列顺序合并文本(第一列完了第二列,以此类推)
        setTexts(prev => [...prev, ...columnTexts]);
      } catch (err) {
        console.error(err);
        setTexts([]);
        setProgress(0);
        setIsLoading(false);
        return;
      } finally {
        processedImages++;
        const currentProgress = (processedImages / totalImages) * 100;
        setProgress(currentProgress);
      }
    }
  } else {
    console.error("Images is not an array.");
  }

  setIsLoading(false);
};

方案2:修复OpenCV.js图片预处理,分割列后单独OCR

如果之前用OpenCV.js失败,可能是预处理步骤缺失,正确的流程是:灰度化→二值化→检测垂直边缘→提取列区域→分割图片后分别识别。

在React中集成OpenCV.js的预处理示例:

// 先确保OpenCV.js已加载完成
const processImageWithOpenCV = async (imageFile) => {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => {
      const src = cv.imread(img);
      const gray = new cv.Mat();
      cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY);

      // 二值化处理
      const binary = new cv.Mat();
      cv.threshold(gray, binary, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU);

      // 检测垂直边缘(提取列分割线)
      const kernel = cv.getStructuringElement(cv.MORPH_RECT, new cv.Size(3, 15));
      const vertical = new cv.Mat();
      cv.morphologyEx(binary, vertical, cv.MORPH_OPEN, kernel);

      // 查找轮廓获取列区域
      const contours = new cv.MatVector();
      const hierarchy = new cv.Mat();
      cv.findContours(vertical, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);

      // 提取列的x坐标范围并排序
      const columnRegions = [];
      for (let i = 0; i < contours.size(); i++) {
        const rect = cv.boundingRect(contours.get(i));
        columnRegions.push({ x: rect.x, width: rect.width });
      }
      columnRegions.sort((a, b) => a.x - b.x);

      // 分割图片为单列,转为Blob用于Tesseract识别
      const columnBlobs = [];
      columnRegions.forEach(region => {
        const roi = src.roi(new cv.Rect(region.x, 0, region.width, src.rows));
        const canvas = document.createElement('canvas');
        cv.imshow(canvas, roi);
        canvas.toBlob(blob => columnBlobs.push(blob));
        roi.delete();
      });

      // 清理OpenCV资源
      src.delete(); gray.delete(); binary.delete(); vertical.delete(); contours.delete(); hierarchy.delete();
      resolve(columnBlobs);
    };
    img.onerror = reject;
    img.src = URL.createObjectURL(imageFile);
  });
};

// 在handleSubmit中调用预处理逻辑
const handleSubmit = async () => {
  // 原有校验逻辑不变
  if (images.length === 0) {
    setErrorMessage("Select an image to convert.");
    return;
  }
  if (selectedLanguages.length === 0) {
    setErrorLanguagesMessage("Select any language.");
    return;
  }

  setIsLoading(true);
  setProgress(0);
  setTexts([]);
  setCurrentImageIndex(0);
  setErrorMessage("");
  setErrorLanguagesMessage("");

  const totalImages = images.length;
  let processedImages = 0;

  if (Array.isArray(images)) {
    for (const [index, image] of images?.entries()) {
      setCurrentImageIndex(index + 1);

      try {
        // 先用OpenCV分割列
        const columnBlobs = await processImageWithOpenCV(image);
        // 对每个列单独OCR
        const columnTexts = [];
        for (const blob of columnBlobs) {
          const result = await Tesseract.recognize(
            blob,
            selectedLanguages.map((lang) => lang.value).join("+")
          );
          columnTexts.push(result.data.text.trim());
        }
        setTexts(prev => [...prev, ...columnTexts]);
      } catch (err) {
        console.error(err);
        setTexts([]);
        setProgress(0);
        setIsLoading(false);
        return;
      } finally {
        processedImages++;
        const currentProgress = (processedImages / totalImages) * 100;
        setProgress(currentProgress);
      }
    }
  } else {
    console.error("Images is not an array.");
  }

  setIsLoading(false);
};

方案3:调整Tesseract页面分割模式(PSM)

Tesseract的页面分割模式(PSM)会影响文本识别的顺序,默认模式可能不适合多列布局,尝试修改PSM参数:

const result = await Tesseract.recognize(
  image,
  selectedLanguages.map((lang) => lang.value).join("+"),
  {
    tessedit_pageseg_mode: 1, // 自动检测页面布局
    // 或 tessedit_pageseg_mode: 4, // 适合手动分割后的单列图片
  }
);

注意事项

  • 方案1中的columnThreshold(列间距阈值)需要根据图片实际情况调整,避免误判列归属。
  • OpenCV.js需正确加载,建议通过CDN引入并等待初始化完成后再调用处理函数。
  • 复杂多列布局场景下,方案1+方案3结合使用能获得更稳定的效果。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 14:24:59