树莓派4上Python OpenCV多摄像头流交替读取帧的问题排查
树莓派4多路视频流交替帧读取优化
问题背景
我在树莓派4上播放多路摄像头流,为降低CPU负载,计划采用交替读取不同视频帧的策略:比如第一个视频仅显示奇数帧、第二个视频仅显示偶数帧,迭代逻辑为第1次读取视频1的帧1,第2次读取视频2的帧2,后续打算扩展至6路。但测试本地视频时,只有第一个视频能正常播放,第二个视频从一开始就卡顿;删除代码中%3==0的条件后,第二个视频才可正常播放。
原测试代码:
import numpy as np import cv2 names = ['C:/Users/viv/test.mp4', 'C:/Users/viv/parking_lot_1.mp4']; window_titles = ['first', 'second'] cap = [cv2.VideoCapture(i) for i in names] frames = [None] * len(names); gray = [None] * len(names); ret = [None] * len(names); while True: for i,c in enumerate(cap): #print (i) if c is not None: if (c.get(cv2.CAP_PROP_POS_FRAMES)%2==0) & i==0: ret[i], frames[i] = c.read(); elif (c.get(cv2.CAP_PROP_POS_FRAMES)%3==0) & i==1: ret[i], frames[i] = c.read(); print(i,c.get(cv2.CAP_PROP_POS_FRAMES)) for i,f in enumerate(frames): if ret[i] is True: gray[i] = cv2.cvtColor(f, cv2.COLOR_BGR2GRAY) cv2.imshow(window_titles[i], gray[i]); if cv2.waitKey(1) & 0xFF == ord('q'): break for c in cap: if c is not None: c.release(); cv2.destroyAllWindows()
问题分析
- 帧指针停滞:每次循环遍历所有视频,但仅满足特定帧位置条件时才调用
read(),未满足条件的视频帧指针停留在原地,后续判断永远卡在同一帧,导致视频卡顿。 - 逻辑运算符错误:用位运算
&代替了逻辑与and,虽暂时可能生效,但属于错误用法,易引发隐性问题。 - 条件匹配不合理:第二个视频的
%3==0条件过于严苛,且未与全局迭代节奏关联,导致大部分循环都不读取该视频帧,视觉上表现为卡顿。
修复方案
- 引入全局迭代计数器,用其奇偶性控制交替读取逻辑,保证按预期顺序读取不同视频的帧。
- 对未选中读取的视频,调用轻量的
grab()方法推进帧指针,避免停滞且不增加过多CPU负载。 - 替换位运算
&为逻辑与and,修正逻辑判断方式。
优化后代码
import numpy as np import cv2 names = ['C:/Users/viv/test.mp4', 'C:/Users/viv/parking_lot_1.mp4'] window_titles = ['first', 'second'] cap = [cv2.VideoCapture(i) for i in names] frames = [None] * len(names) gray = [None] * len(names) ret = [False] * len(names) # 全局迭代计数器,控制交替逻辑 iteration = 0 while True: iteration += 1 for i, c in enumerate(cap): if not c.isOpened(): continue # 根据全局迭代次数决定是否读取当前视频的帧 # 第一个视频:奇数迭代读取(对应奇数帧) if i == 0 and iteration % 2 == 1: ret[i], frames[i] = c.read() # 第二个视频:偶数迭代读取(对应偶数帧) elif i == 1 and iteration % 2 == 0: ret[i], frames[i] = c.read() # 不读取时,用grab()推进帧指针 else: c.grab() # 更新显示窗口 for i, f in enumerate(frames): if ret[i]: gray[i] = cv2.cvtColor(f, cv2.COLOR_BGR2GRAY) cv2.imshow(window_titles[i], gray[i]) # 若未读取到新帧,保持之前的画面(可选) elif gray[i] is not None: cv2.imshow(window_titles[i], gray[i]) if cv2.waitKey(1) & 0xFF == ord('q'): break # 释放资源 for c in cap: if c.isOpened(): c.release() cv2.destroyAllWindows()
扩展到6路视频的思路
若要扩展至6路,只需调整迭代计数器的判断逻辑,比如按iteration % 6的结果对应不同视频的读取时机,例如:
iteration % 6 == 0:读取视频0的帧iteration % 6 == 1:读取视频1的帧- ...以此类推,保证每6次循环依次读取6路视频各一帧,既控制CPU负载,又维持视频播放的连贯性。
内容的提问来源于stack exchange,提问作者vinita
相关产品推荐
相关产品推荐

