React实现发票OCR选区文本提取遇TypeError求助
问题描述
我需要对一张发票(图片:
)执行OCR识别,打印选区像素并将选区内的文本显示到textarea中,但运行时出现以下错误:
createWorker.js:173 Uncaught Error: TypeError: (intermediate value)(intermediate value)(intermediate value).map is not a function
我是React新手,附上代码求助:
import React,{ useState, useRef, useEffect } from "react"; import ReactDOM from "react-dom"; import RegionSelect from "react-region-select"; import "../src/style.css"; import Tesseract from "tesseract.js"; import * as pdfjsLib from "pdfjs-dist"; pdfjsLib.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/3.3.122/pdf.worker.js`; class App extends React.Component { constructor(props) { super(props); this.state = { clickableRegions: [ { x: 9.565217391304348, y: 34.195402298850574, width: 81.30434782608695, height: 6.3218390804597675, new: false, data: { index: 0 }, isChanging: false } ] }; } onChange = (regions) => { console.log("App -> onChange -> regions", regions); this.setState({ regions: regions }); }; changeRegionData(index, event) { console.log("App -> changeRegionData -> index", index); const region = this.state.regions[index]; console.log( "App -> changeRegionData -> this.state.regions", this.state.regions ); let color; switch (event.target.value) { case "1": color = "rgba(0, 255, 0, 0.5)"; break; case "2": color = "rgba(0, 0, 255, 0.5)"; break; case "3": color = "rgba(255, 0, 0, 0.5)"; break; default: color = "rgba(0, 0, 0, 0.5)"; } region.data.regionStyle = { background: color }; this.onChange([ ...this.state.regions.slice(0, index), Object.assign({}, region, { data: Object.assign({}, region.data, { dataType: event.target.value }) }), ...this.state.regions.slice(index + 1) ]); } actionDeleteRegion = (regionIdx) => { console.log("regionIdx", regionIdx); const filteredRegion = this.state.regions.filter( (reg) => reg.data.index !== regionIdx ); this.setState({ regions: filteredRegion }); }; regionRenderer = (regionProps) => { if (!regionProps.isChanging) { console.log("regionRenderer -> regionProps", regionProps); return ( <div> <div style={{ position: "absolute", right: 0, top: "-25px" }}> <button onClick={() => this.actionDeleteRegion(regionProps.data.index)} > Cancel </button> </div> <div style={{ position: "absolute", right: 0, bottom: "-30px" }}> <textarea disabled id="Mytextarea" style={{ top: "20px", resize: "none" }} value={this.state.extractedText} /> <select> <option value="inv1">inv total</option> <option value="inv2">addre</option> <option value="inv3">inv number</option> </select> <button onClick={this.actionAddRegion}>Save</button> </div> </div> ); } }; actionAddRegion = () => { const selectedRegion = this.state.regions[this.state.regions.length - 1]; console.log(selectedRegion.x, selectedRegion.y, selectedRegion.width); const canvas = document.createElement("canvas"); const img = new Image(); img.onload = () => { const ctx = canvas.getContext("2d"); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0, img.width, img.height); const { x, y, width, height } = selectedRegion; const imageData = ctx.getImageData( (x / 100) * img.width, (y / 100) * img.height, (width / 100) * img.width, (height / 100) * img.height ); console.log("Image data: " + imageData) Tesseract.recognize(imageData, { tessedit_char_whitelist: "0123456789" }) .then((result) => { const text = result.text.trim(); console.log("and here is the text"); console.log(text); this.setState({ extractedText: text }); }) .catch((error) => { console.log("errrp") console.error(error); }); }; img.src = require('../src/invoice.jpg') alert("Extracting text..."); }; renderClickableRegions() { const { regions } = this.state; return regions.map((c) => ( <div style={{ position: "absolute", width: `${c.width}%`, height: `${c.height}%`, top: `${c.y}%`, left: `${c.x}%`, background: c.data.regionStyle?.background ?? "rgba(0, 255, 255, 0.5)" }} /> )); } render() { const regionStyle = { background: "rgba(0, 255, 0, 0.5)" }; return ( <React.Fragment> <br /> <br /> <div style={{ display: "flex" }}> <div style={{ flexGrow: 1, flexShrink: 1, width: "50%", left: "0", // padding: 15, position: "absolute", display: "inline-block" }} > <RegionSelect regions={this.state.regions} regionStyle={regionStyle} constraint onChange={this.onChange} regionRenderer={this.regionRenderer} // style={{ width: "50%" }} > <img src={require('../src/invoice.jpg')} width="500px" height="500px" /> {/* {this.renderClickableRegions()} */} </RegionSelect> </div> <div style={{ flexGrow: 1, flexShrink: 1, width: "50%", left: "50%", // padding: 15, position: "absolute", display: "inline-block" }} ></div> </div> </React.Fragment> ); } } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
问题修复方案
错误根源
- Tesseract.js参数类型不兼容:直接传递
getImageData返回的ImageData对象给Tesseract,但Tesseract不支持该类型,它仅接受图片URL、img元素、canvas元素或Blob作为输入。 - State初始化不匹配:初始state定义的是
clickableRegions,但后续逻辑统一使用regions,导致首次渲染时this.state.regions为undefined,触发map函数调用错误。 - 未初始化
extractedText:state中未提前定义extractedText,导致textarea取值时出现undefined问题。
修复步骤
1. 修正初始State
在constructor中把clickableRegions改为regions,同时初始化extractedText为空字符串:
constructor(props) { super(props); this.state = { regions: [ { x: 9.565217391304348, y: 34.195402298850574, width: 81.30434782608695, height: 6.3218390804597675, new: false, data: { index: 0 }, isChanging: false } ], extractedText: "" }; }
2. 调整OCR的图片输入方式
不再传递ImageData,而是创建新canvas绘制选区内容,再将这个canvas传给Tesseract:
// 修改actionAddRegion中的OCR逻辑 const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); // 设置新canvas尺寸为选区实际像素大小 canvas.width = (width / 100) * img.width; canvas.height = (height / 100) * img.height; // 从原图裁剪选区内容到新canvas ctx.drawImage( img, (x / 100) * img.width, (y / 100) * img.height, (width / 100) * img.width, (height / 100) * img.height, 0, 0, canvas.width, canvas.height ); // 传递新canvas给Tesseract执行OCR Tesseract.recognize(canvas, { tessedit_char_whitelist: "0123456789" }) .then((result) => { const text = result.text.trim(); console.log(text); this.setState({ extractedText: text }); }) .catch((error) => { console.error(error); });
3. 优化图片加载(可选)
将require引入图片改为直接导入,避免路径问题:
import invoiceImg from '../src/invoice.jpg'; // 后续img.src使用该变量 img.src = invoiceImg;
修改后的完整代码
import React from "react"; import ReactDOM from "react-dom"; import RegionSelect from "react-region-select"; import "../src/style.css"; import Tesseract from "tesseract.js"; import * as pdfjsLib from "pdfjs-dist"; import invoiceImg from '../src/invoice.jpg'; pdfjsLib.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/3.3.122/pdf.worker.js`; class App extends React.Component { constructor(props) { super(props); this.state = { regions: [ { x: 9.565217391304348, y: 34.195402298850574, width: 81.30434782608695, height: 6.3218390804597675, new: false, data: { index: 0 }, isChanging: false } ], extractedText: "" }; } onChange = (regions) => { console.log("App -> onChange -> regions", regions); this.setState({ regions }); }; changeRegionData(index, event) { console.log("App -> changeRegionData -> index", index); const region = this.state.regions[index]; let color; switch (event.target.value) { case "1": color = "rgba(0, 255, 0, 0.5)"; break; case "2": color = "rgba(0, 0, 255, 0.5)"; break; case "3": color = "rgba(255, 0, 0, 0.5)"; break; default: color = "rgba(0, 0, 0, 0.5)"; } region.data.regionStyle = { background: color }; this.onChange([ ...this.state.regions.slice(0, index), { ...region, data: { ...region.data, dataType: event.target.value } }, ...this.state.regions.slice(index + 1) ]); } actionDeleteRegion = (regionIdx) => { const filteredRegion = this.state.regions.filter( (reg) => reg.data.index !== regionIdx ); this.setState({ regions: filteredRegion }); }; regionRenderer = (regionProps) => { if (!regionProps.isChanging) { return ( <div> <div style={{ position: "absolute", right: 0, top: "-25px" }}> <button onClick={() => this.actionDeleteRegion(regionProps.data.index)} > Cancel </button> </div> <div style={{ position: "absolute", right: 0, bottom: "-30px" }}> <textarea disabled id="Mytextarea" style={{ top: "20px", resize: "none" }} value={this.state.extractedText} /> <select> <option value="inv1">inv total</option> <option value="inv2">addre</option> <option value="inv3">inv number</option> </select> <button onClick={this.actionAddRegion}>Save</button> </div> </div> ); } }; actionAddRegion = () => { const selectedRegion = this.state.regions[this.state.regions.length - 1]; const img = new Image(); img.onload = () => { const { x, y, width, height } = selectedRegion; // 创建新canvas用于裁剪选区 const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = (width / 100) * img.width; canvas.height = (height / 100) * img.height; // 绘制选区内容到新canvas ctx.drawImage( img, (x / 100) * img.width, (y / 100) * img.height, (width / 100) * img.width, (height / 100) * img.height, 0, 0, canvas.width, canvas.height ); // 打印选区像素数据 const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); console.log("选区像素数据:", imageData); // 使用新canvas执行OCR Tesseract.recognize(canvas, { tessedit_char_whitelist: "0123456789" }) .then((result) => { const text = result.text.trim(); console.log("识别出的文本:", text); this.setState({ extractedText: text }); }) .catch((error) => { console.error("OCR错误:", error); }); }; img.src = invoiceImg; alert("正在提取文本..."); }; renderClickableRegions() { const { regions } = this.state; return regions.map((c) => ( <div key={c.data.index} style={{ position: "absolute", width: `${c.width}%`, height: `${c.height}%`, top: `${c.y}%`, left: `${c.x}%`, background: c.data.regionStyle?.background ?? "rgba(0, 255, 255, 0.5)" }} /> )); } render() { const regionStyle = { background: "rgba(0, 255, 0, 0.5)" }; return ( <React.Fragment> <br /> <br /> <div style={{ display: "flex" }}> <div style={{ flexGrow: 1, flexShrink: 1, width: "
相关产品推荐
相关产品推荐

