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

基于MERN+Google Vision API的交互式视频点击无响应求助

解决思路与修复步骤

1. 核心问题:无点击元素可触发事件

你的前端代码里objects数组初始为空,且从未发起请求获取物体标注列表,导致annotationsContainer里的循环根本不会渲染任何可点击的元素——这就是点击无反应、无请求的直接原因。

修复:添加物体列表获取逻辑

在现有的useEffect中,新增获取物体列表的请求(需要后端先实现对应的接口):

useEffect(() => {
  // 先获取视频
  fetch('http://localhost:3001/videourl')
    .then(response => {
      if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
      const contentType = response.headers.get('content-type');
      if (contentType && contentType.startsWith('video')) {
        return response.blob();
      } else {
        console.error('Unexpected response type:', contentType);
        setError(new Error('Unexpected response type'));
      }
    })
    .then(videoBlob => {
      setVideoUrl(URL.createObjectURL(videoBlob));
      // 视频加载后,获取物体标注列表
      return fetch('http://localhost:3001/annotation');
    })
    .then(response => {
      if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
      return response.json();
    })
    .then(objectList => {
      setObjects(objectList);
      setIsLoading(false); // 加载完成
    })
    .catch(error => {
      console.error('Error fetching data:', error);
      setError(error);
      setIsLoading(false);
    });
}, []);

后端补充列表接口

在annotationroute.js中添加获取所有物体的路由:

// 新增获取所有物体的接口
router.get('/', async (req, res) => {
  try {
    const objects = await ObjectModel.find();
    res.json(objects);
  } catch (error) {
    console.error('Error fetching objects:', error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

2. 字段不匹配:数据渲染失败

你的MongoDB模型字段是objectDescription、objectConfidence,但前端渲染时用的是object.entity.description、object.confidence——就算拿到数据,也会因为字段不匹配显示空白,甚至报错。

修复:统一前后端字段

方案一:修改前端渲染逻辑

把前端里的object.entity.description改成object.objectDescription,object.confidence改成object.objectConfidence,同时注意MongoDB的文档ID是_id而非id:

// 渲染物体列表时
<div
  key={index}
  className="annotation"
  onClick={() => handleObjectClick(object._id)}
>
  <strong>{object.objectDescription}</strong>
  <br />
  Confidence: {object.objectConfidence}
</div>

// 渲染选中物体时
<div className="selectedObject">
  <strong>Selected Object:</strong> {selectedObject.objectDescription}
  <br />
  Confidence: {selectedObject.objectConfidence}
</div>

方案二:后端返回时映射字段(如果不想改前端)

在单个物体接口中修改返回格式:

router.get('/:objectId', async (req, res) => {
  const objectId = req.params.objectId;
  try {
    const object = await ObjectModel.findById(objectId);
    if (!object) return res.status(404).json({ error: 'Object not found' });
    // 映射成前端期望的字段格式
    res.json({
      id: object._id,
      entity: { description: object.objectDescription },
      confidence: object.objectConfidence
    });
  } catch (error) {
    console.error('Error fetching object:', error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

3. 实现视频内物体点击(你提到的帧位置问题)

目前你只是渲染了文字列表,用户需要点击视频里的物体而非文字,这需要:

  • 从Google Vision API获取物体的边界框坐标(boundingPoly)和出现的时间区间,存入MongoDB
  • 前端根据视频当前播放时间,渲染对应时间区间内的物体边界框(叠加在视频上方的透明可点击div)

步骤:

  1. 更新数据模型,添加边界框字段:
const objectSchema = new Schema({
  objectDescription: { type: String },
  objectEntityId: { type: String },
  objectStartTime: { type: Number }, // 建议存数字类型的时间戳(秒)
  objectEndTime: { type: Number },
  objectConfidence: { type: Number },
  // 添加边界框坐标(Google Vision返回的boundingPoly.vertices转换为百分比)
  boundingBox: {
    top: Number,
    left: Number,
    width: Number,
    height: Number
  }
});
  1. 前端渲染视频点击区域,监听视频播放时间,动态显示当前帧的物体边界框:
// 新增视频播放状态监听
const [currentTime, setCurrentTime] = useState(0);

// 视频时间更新时触发
function handleTimeUpdate(e) {
  setCurrentTime(e.target.currentTime);
}

// 过滤当前时间可见的物体
const visibleObjects = objects.filter(obj => {
  return currentTime >= obj.objectStartTime && currentTime <= obj.objectEndTime;
});

// 在视频上方叠加点击区域
return (
  <div style={{ position: 'relative' }}>
    <video
      src={videoUrl}
      muted
      autoPlay
      playsInline
      loop
      style={{ width: '100%', height: '300pt' }}
      onTimeUpdate={handleTimeUpdate} // 监听播放时间
    />
    {/* 渲染当前帧的物体边界框 */}
    <div style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
      {visibleObjects.map(obj => (
        <div
          key={obj._id}
          style={{
            position: 'absolute',
            top: `${obj.boundingBox.top}%`,
            left: `${obj.boundingBox.left}%`,
            width: `${obj.boundingBox.width}%`,
            height: `${obj.boundingBox.height}%`,
            border: '2px solid blue',
            backgroundColor: 'rgba(0,0,255,0.1)',
            cursor: 'pointer',
            pointerEvents: 'auto' // 允许点击
          }}
          onClick={() => handleObjectClick(obj._id)}
          title={`${obj.objectDescription} (${obj.objectConfidence})`}
        />
      ))}
    </div>
    {/* 原有的文字列表和选中物体展示 */}
    <div className="annotationsContainer">...</div>
    {selectedObject && <div className="selectedObject">...</div>}
  </div>
);

4. 调试小技巧

  • 先确认objects数组是否有数据:在setObjects(objectList)后加console.log('Fetched objects:', objectList)
  • 检查MongoDB中存储的_id是否正确传递:点击元素时看handleObjectClick里的console.log是否输出正确的ID
  • 确认CORS配置:如果前后端端口不同,后端需要配置CORS(比如用cors中间件),否则fetch会被浏览器拦截

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 17:29:55