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

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);
问题修复方案

错误根源

  1. Tesseract.js参数类型不兼容:直接传递getImageData返回的ImageData对象给Tesseract,但Tesseract不支持该类型,它仅接受图片URL、img元素、canvas元素或Blob作为输入。
  2. State初始化不匹配:初始state定义的是clickableRegions,但后续逻辑统一使用regions,导致首次渲染时this.state.regions为undefined,触发map函数调用错误。
  3. 未初始化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: "
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 12:15:43