异步协程无法切换布尔触发器,如何实现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的布尔值切换。
解决方案
问题根源分析
- 阻塞操作占用事件循环:
cv2.waitKey()、cv2.imshow()都是同步阻塞调用,会独占asyncio事件循环,导致flip_trigger的await asyncio.sleep(2)无法获得执行机会,事件循环被卡住。 - 相机逻辑错误:
camera_feed中当send_image为True时,执行完部分代码后直接调用video_capture.release()、cv2.destroyAllWindows()并break,导致相机被关闭、循环终止,无法持续运行。 - 任务等待逻辑错误:
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
相关产品推荐
相关产品推荐

