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

OpenCV JS在原生JS正常运行,迁移React后报“cv未定义”求解决

React中集成OpenCV.js实现ARUCO标记检测的问题解决

问题背景

原生HTML/JS代码可正常调用摄像头并检测ARUCO标记,但迁移到React后,出现两种问题:

  • 直接引入script标签或import时,报错cv未定义
  • 加载耗时过长,检测逻辑无法正常执行

原生代码:

<!DOCTYPE html>
<html>

<head>
    <title>Learning</title>
</head>

<body>
    <video id="input_video" height="480" width="640"></video>
    <canvas id="output_video" height="480" width="640"></canvas>

</body>
<!-- CALLING OPEN CV JAVASCRIPT -->
<script async
    src="/Frontend/src/assets/js/version_3/opencv.js"></script>

<script>
    async function playVideo() {

        // playing the video from webcam
        let video = document.getElementById("input_video"); //add a video tag with id videoElement
        video.srcObject = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
        video.play();

        // space for output
        let canvas = document.getElementById('output_video');

        // detecting ARUCO markers
        let arucoDictionary = new cv.Dictionary(cv.DICT_6X6_50);
        let parameters = new cv.DetectorParameters();
        // new vectors
        let src = new cv.Mat(video.height, video.width, cv.CV_8UC4);
        let cap = new cv.VideoCapture(video);

        cap.read(src)
        cv.imshow("output_video", src)


        console.log(arucoDictionary)

        console.log('successful execution')
    };
    playVideo()
</script>

</html>

尝试的React代码:

import React, { useEffect, useRef, Component } from "react";
import { Helmet } from "react-helmet";

// import "Frontend\\src\\assets\\js\\version_3\\opencv.js"

const Video = () => {
  const playVideo = async () => {
    // playing the video from webcam
    let video = document.getElementById("input_video"); //add a video tag with id videoElement
    video.srcObject = await navigator.mediaDevices.getUserMedia({
      video: true,
      audio: false,
    });
    video.play();

    // space for output
    let canvas = document.getElementById("output_video");

    // detecting ARUCO markers
    let arucoDictionary = new cv.Dictionary(cv.DICT_6X6_50);
    let parameters = new cv.DetectorParameters();
    // new vectors
    let src = new cv.Mat(video.height, video.width, cv.CV_8UC4);
    let cap = new cv.VideoCapture(video);

    cap.read(src);
    cv.imshow("output_video", src);

    console.log(arucoDictionary);

    console.log("successful execution");
  };

  return (
    <div>
      <script
        src="\\Frontend\\src\\assets\\js\\version_3\\opencv.js"
        id="opencvjs"
        onload="main()"
      />

      <video id="input_video" height="480" width="640"></video>
      <canvas id="output_video" height="480" width="640"></canvas>
      {playVideo()}
    </div>
  );
};

export default Video;

问题原因

  1. React DOM渲染机制冲突:直接在组件return中插入script标签,React无法保证其加载顺序和时机,playVideo会在OpenCV.js未加载完成时执行,导致cv未定义。
  2. 模块类型不兼容:OpenCV.js是自执行的全局脚本,不是ES模块,直接import会导致加载异常或耗时过长。
  3. 执行逻辑不完整:原生代码只执行了一次帧读取,实际ARUCO检测需要持续循环捕获视频帧。

解决方法

1. 正确加载OpenCV.js

使用useEffect动态创建script标签,监听加载完成事件,确保OpenCV初始化后再执行检测逻辑;也可借助Helmet管理全局脚本。

2. 采用React规范操作DOM

使用useRef获取video和canvas元素,避免直接document.getElementById。

3. 实现持续帧检测

用requestAnimationFrame循环捕获视频帧,保证ARUCO标记的实时检测。

4. 添加加载状态

处理OpenCV加载耗时问题,给用户显示加载提示。

修改后的React代码

import React, { useEffect, useRef, useState } from "react";
import { Helmet } from "react-helmet";

const Video = () => {
  const videoRef = useRef(null);
  const canvasRef = useRef(null);
  const [isLoading, setIsLoading] = useState(true);
  // 存储OpenCV相关实例,避免重复创建
  const cvInstancesRef = useRef({});

  // 加载OpenCV.js
  useEffect(() => {
    const script = document.createElement('script');
    script.src = '/Frontend/src/assets/js/version_3/opencv.js';
    script.async = true;
    script.onload = () => {
      // 初始化OpenCV相关参数
      cvInstancesRef.current.arucoDictionary = new cv.Dictionary(cv.DICT_6X6_50);
      cvInstancesRef.current.parameters = new cv.DetectorParameters();
      cvInstancesRef.current.cap = new cv.VideoCapture(videoRef.current);
      cvInstancesRef.current.src = new cv.Mat(videoRef.current.height, videoRef.current.width, cv.CV_8UC4);
      setIsLoading(false);
      // 初始化摄像头并开始检测
      initCamera();
    };
    script.onerror = () => {
      console.error('OpenCV.js加载失败');
      setIsLoading(false);
    };
    document.body.appendChild(script);

    // 组件卸载时清理资源
    return () => {
      document.body.removeChild(script);
      if (cvInstancesRef.current.src) {
        cvInstancesRef.current.src.delete();
      }
    };
  }, []);

  // 初始化摄像头
  const initCamera = async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
      videoRef.current.srcObject = stream;
      videoRef.current.play();
      // 开始循环检测
      startDetectionLoop();
    } catch (err) {
      console.error('摄像头获取失败:', err);
    }
  };

  // 持续检测ARUCO标记
  const startDetectionLoop = () => {
    const detect = () => {
      if (!videoRef.current || !canvasRef.current || isLoading) {
        requestAnimationFrame(detect);
        return;
      }

      // 读取视频帧
      cvInstancesRef.current.cap.read(cvInstancesRef.current.src);
      // 检测ARUCO标记
      const corners = new cv.MatVector();
      const ids = new cv.Mat();
      cv.aruco.detectMarkers(cvInstancesRef.current.src, cvInstancesRef.current.arucoDictionary, corners, ids, cvInstancesRef.current.parameters);
      
      // 在画布上绘制检测结果
      cv.aruco.drawDetectedMarkers(cvInstancesRef.current.src, corners, ids);
      cv.imshow(canvasRef.current, cvInstancesRef.current.src);

      // 释放临时资源
      corners.delete();
      ids.delete();

      requestAnimationFrame(detect);
    };
    detect();
  };

  if (isLoading) {
    return <div>加载OpenCV中...</div>;
  }

  return (
    <div>
      <Helmet>
        {/* 可选:用Helmet管理脚本,和动态创建二选一即可 */}
        {/* <script async src="/Frontend/src/assets/js/version_3/opencv.js" onload="handleOpenCVLoad()"></script> */}
      </Helmet>
      <video ref={videoRef} height="480" width="640" muted playsInline></video>
      <canvas ref={canvasRef} height="480" width="640"></canvas>
    </div>
  );
};

export default Video;

关键优化点

  • 动态加载script并监听onload,确保cv全局对象存在后再执行逻辑
  • 使用useRef存储视频/画布引用和OpenCV实例,避免重复创建
  • 用requestAnimationFrame实现实时帧检测,符合浏览器渲染机制
  • 组件卸载时释放OpenCV的Mat资源,避免内存泄漏
  • 添加加载状态提示,提升用户体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 17:40:55