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
相关产品推荐
相关产品推荐

