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

异步协程无法切换布尔触发器,如何实现OpenCV相机与定时任务同步?

问题描述

需要完成以下操作:

  • 在OpenCV中启动相机实例
  • 每2秒向外部源发送相机数据,同时视频流不能中断

为此编写了两个异步函数:flip_trigger每2秒切换布尔变量send_image,camera_feed消费该触发器,期望二者同时运行。代码如下:

send_image = False

async def flip_trigger():
    global send_image
    while True:
        await asyncio.sleep(2)
        send_image = not send_image
        print("Awaiting image")

async def camera_feed():
    global send_image
    face_names = []
    face_usernames = []
    video_capture = cv2.VideoCapture(0)
    while True:
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
        if(send_image):
            ret, frame = video_capture.read()
            #(...) some other code
        else:
            ret, frame = video_capture.read()
            cv2.imshow('Video', frame)
            continue
        #(...) some other code
        ret, frame = video_capture.read()
        cv2.imshow('Video', frame)
        video_capture.release()
        cv2.destroyAllWindows()
        break

async def start_camera():
    task1 = asyncio.create_task(flip_trigger())
    task2 = asyncio.create_task(camera_feed())
    await asyncio.wait({task1, task2}, return_when=asyncio.FIRST_COMPLETED) 

asyncio.run(start_camera())

遇到的问题:在VSCode调试时,代码无法越过await asyncio.sleep(2)行;若移除await,则会卡在flip_trigger函数内。需要让两个函数同时运行,并让camera_feed实时捕获send_image的布尔值切换。


解决方案

问题根源分析

  1. 阻塞操作占用事件循环:cv2.waitKey()、cv2.imshow()都是同步阻塞调用,会独占asyncio事件循环,导致flip_trigger的await asyncio.sleep(2)无法获得执行机会,事件循环被卡住。
  2. 相机逻辑错误:camera_feed中当send_image为True时,执行完部分代码后直接调用video_capture.release()、cv2.destroyAllWindows()并break,导致相机被关闭、循环终止,无法持续运行。
  3. 任务等待逻辑错误:asyncio.wait使用return_when=asyncio.FIRST_COMPLETED,意味着只要一个任务结束就终止程序,但flip_trigger是无限循环任务,只有camera_feed会提前结束,导致整个程序直接终止。

修正步骤与代码

改用线程池处理OpenCV阻塞操作,替换全局变量为安全的异步事件,修正任务等待逻辑:

import asyncio
import cv2
from concurrent.futures import ThreadPoolExecutor

async def flip_trigger(send_event):
    while True:
        await asyncio.sleep(2)
        send_event.set()  # 标记需要发送图像
        print("标记需发送图像")
        await asyncio.sleep(0.1)  # 预留处理时间,可按需调整
        send_event.clear()  # 清除标记

async def camera_feed(send_event, executor):
    face_names = []
    face_usernames = []
    video_capture = cv2.VideoCapture(0)
    
    if not video_capture.isOpened():
        print("无法打开相机")
        return
    
    try:
        while True:
            # 在线程池执行read,避免阻塞事件循环
            ret, frame = await asyncio.get_event_loop().run_in_executor(executor, video_capture.read)
            if not ret:
                print("无法读取帧")
                break
            
            # 处理图像发送逻辑
            if send_event.is_set():
                # (...) 此处添加向外部源发送数据的代码
                print("正在发送图像到外部源")
            
            # 在线程池执行imshow和waitKey
            await asyncio.get_event_loop().run_in_executor(executor, cv2.imshow, 'Video', frame)
            key = await asyncio.get_event_loop().run_in_executor(executor, cv2.waitKey, 1)
            if key & 0xFF == ord('q'):
                break
    finally:
        video_capture.release()
        await asyncio.get_event_loop().run_in_executor(executor, cv2.destroyAllWindows)

async def start_camera():
    send_event = asyncio.Event()
    # 创建线程池处理OpenCV阻塞操作
    with ThreadPoolExecutor() as executor:
        task1 = asyncio.create_task(flip_trigger(send_event))
        task2 = asyncio.create_task(camera_feed(send_event, executor))
        
        try:
            # 等待两个任务完成,flip_trigger为无限循环,直到被取消
            await asyncio.gather(task1, task2)
        except asyncio.CancelledError:
            # 用户按q退出时,取消flip_trigger任务
            task1.cancel()
            await task1

asyncio.run(start_camera())

修改说明

  • 用asyncio.Event替代全局变量send_image,实现更安全的异步任务间通信。
  • 将所有OpenCV阻塞操作放到线程池中执行,通过run_in_executor避免阻塞asyncio事件循环,确保flip_trigger能按时切换标记状态。
  • 修正camera_feed逻辑,移除错误的release和break,保证相机持续运行,每次循环都处理帧与发送逻辑。
  • 使用asyncio.gather等待任务,当用户按q退出时,优雅取消flip_trigger任务并完成清理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 09:45:43