FFmpeg和video4linux2参数-如何更快地捕捉静态图像



问题摘要

我已经建立了一个18摄像头的USB网络摄像头阵列,连接到树莓派400作为控制器。我的Python 3.8代码用于从每个网络摄像头捕获图像,速度很慢,我正在努力寻找加快速度的方法。

FFMPEGvideo4linux2命令行选项让我感到困惑,所以我不确定延迟是否是由于我对参数的选择不当,而一组更好的选项可以解决问题。

目标

我正试图从每台相机中尽可能快地捕捉一张图像。

我使用FFMPEGvideo4linux2命令行选项来捕获所有相机循环中的每个图像,如下所示。

预期结果

我只想要每个相机的一帧。帧速率是每秒30帧,所以我预计拍摄时间大约是第二坏情况的1/30到1/10。但性能计时器告诉我,每次捕获需要2-3秒。

此外,我并不真正理解ffmpeg的输出,但这个输出让我担心:

frame=    0 fps=0.0 q=0.0 size=N/A time=00:00:00.00 bitrate=N/A speed=   0x    
frame=    0 fps=0.0 q=0.0 size=N/A time=00:00:00.00 bitrate=N/A speed=   0x    
frame=    0 fps=0.0 q=0.0 size=N/A time=00:00:00.00 bitrate=N/A speed=   0x    
frame=    1 fps=0.5 q=8.3 Lsize=N/A time=00:00:00.06 bitrate=N/A speed=0.0318x    
video:149kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing  

我不明白为什么;帧="帧";行重复4次。在第四次迭代中,fps为0.5,我将其解释为每2秒一帧,而不是我指定的30FPS。

具体问题:

有人能向我解释一下这个ffmpeg输出的含义吗?为什么每个拍摄的图像需要2秒,而不是接近1/30秒

有人能向我解释一下如何在每次拍摄时间更短的情况下拍摄图像吗

我是否应该为每个ffmpeg调用生成一个单独的线程,以便它们异步运行,而不是串行运行?或者这真的不会在实践中节省时间吗

实际结果

Input #0, video4linux2,v4l2, from '/dev/video0':
Duration: N/A, start: 6004.168748, bitrate: N/A
Stream #0:0: Video: mjpeg, yuvj422p(pc, bt470bg/unknown/unknown), 1920x1080, 30 fps, 30 tbr, 1000k tbn, 1000k tbc
Stream mapping:
Stream #0:0 -> #0:0 (mjpeg (native) -> mjpeg (native))
Press [q] to stop, [?] for help
Output #0, image2, to '/tmp/video1.jpg':
Metadata:
encoder         : Lavf58.20.100
Stream #0:0: Video: mjpeg, yuvj422p(pc), 1920x1080, q=2-31, 200 kb/s, 30 fps, 30 tbn, 30 tbc
Metadata:
encoder         : Lavc58.35.100 mjpeg
Side data:
cpb: bitrate max/min/avg: 0/0/200000 buffer size: 0 vbv_delay: -1
frame=    0 fps=0.0 q=0.0 size=N/A time=00:00:00.00 bitrate=N/A speed=   0x    
frame=    0 fps=0.0 q=0.0 size=N/A time=00:00:00.00 bitrate=N/A speed=   0x    
frame=    0 fps=0.0 q=0.0 size=N/A time=00:00:00.00 bitrate=N/A speed=   0x    
frame=    1 fps=0.5 q=8.3 Lsize=N/A time=00:00:00.06 bitrate=N/A speed=0.0318x    
video:149kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
Captured /dev/video0 image in: 3 seconds
Input #0, video4linux2,v4l2, from '/dev/video2':
Duration: N/A, start: 6007.240871, bitrate: N/A
Stream #0:0: Video: mjpeg, yuvj422p(pc, bt470bg/unknown/unknown), 1920x1080, 30 fps, 30 tbr, 1000k tbn, 1000k tbc
Stream mapping:
Stream #0:0 -> #0:0 (mjpeg (native) -> mjpeg (native))
Press [q] to stop, [?] for help
Output #0, image2, to '/tmp/video2.jpg':
Metadata:
encoder         : Lavf58.20.100
Stream #0:0: Video: mjpeg, yuvj422p(pc), 1920x1080, q=2-31, 200 kb/s, 30 fps, 30 tbn, 30 tbc
Metadata:
encoder         : Lavc58.35.100 mjpeg
Side data:
cpb: bitrate max/min/avg: 0/0/200000 buffer size: 0 vbv_delay: -1
frame=    0 fps=0.0 q=0.0 size=N/A time=00:00:00.00 bitrate=N/A speed=   0x    
frame=    0 fps=0.0 q=0.0 size=N/A time=00:00:00.00 bitrate=N/A speed=   0x    
frame=    0 fps=0.0 q=0.0 size=N/A time=00:00:00.00 bitrate=N/A speed=   0x    
frame=    1 fps=0.5 q=8.3 Lsize=N/A time=00:00:00.06 bitrate=N/A speed=0.0318x    
video:133kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
Captured /dev/video2 image in: 3 seconds
...

代码:

list_of_camera_ids = ["/dev/video1","/dev/video2", "/dev/video3", "/dev/video4",
"/dev/video5","/dev/video6", "/dev/video7", "/dev/video8",
"/dev/video9","/dev/video10", "/dev/video11", "/dev/video12",
"/dev/video13","/dev/video14", "/dev/video15", "/dev/video16",
"/dev/video17","/dev/video18"
]
for this_camera_id in list_of_camera_ids:
full_image_file_name = '/tmp/' + os.path.basename(this_camera_id) + 'jpg'
image_capture_tic = time.perf_counter()

run_cmd = subprocess.run([
'/usr/bin/ffmpeg', '-y', '-hide_banner',
'-f', 'video4linux2',
'-input_format',  'mjpeg',
'-framerate', '30',
'-i', this_camera_id,
'-frames', '1',
'-f', 'image2',
full_image_file_name
],
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)  
print(run_cmd.stderr)
image_capture_toc = time.perf_counter()       
print(f"Captured {camera_id} image in: {image_capture_toc - image_capture_tic:0.0f} seconds")

其他数据:为了回应Mark Setchell的回答,即需要更多信息来回答这个问题,我现在在这里详细说明所需信息:

摄像头:摄像头是USB-3摄像头,可识别为:

idVendor           0x0bda Realtek Semiconductor Corp.
idProduct          0x5829 

我试图为其中一台相机添加冗长的lsusb转储,但这篇文章超过了 30000个字符的限制

摄像头的连接方式:Pi的USB 3端口连接到主USB-3 7端口集线器,带有3个支线7端口集线器(并非支线集线器中的所有端口都被占用)。

相机分辨率:高清格式1920x1080

如果我只想要一张图像,为什么要设置帧速率

我设置了一个帧速率,这似乎很奇怪,因为它指定了帧之间的时间,但你只想要一帧。我这么做是因为我不知道如何从FFMPEG中获得一张图像。这是我在网上讨论的FFMPEG命令选项的一个例子,我可以成功地捕捉到一张图像。我发现了无数套不起作用的选项我写这篇文章是因为我的网络搜索没有产生一个适合我的例子。我希望有一个比我更了解情况的人能告诉我一种有效的方法!

为什么我要按顺序而不是并行扫描相机

我这样做只是为了让事情变得简单,在列表上循环似乎很容易,也很像蟒蛇。我很清楚,以后我可能能够为每个FFMPEG调用生成一个单独的线程,并可能以这种方式获得并行速度。事实上,我欢迎一个如何做到这一点的例子。

但无论如何,一次3秒的图像捕捉似乎太长了。

为什么我在你的树莓派上只使用4个核心中的一个

我发布的示例代码只是我整个程序中的一个片段。目前,图像捕获在子线程中进行,而带有事件循环的Window GUI正在主线程中运行,因此在成像过程中不会阻止用户输入。

我对Raspberry Pi 400的内核不够了解,也不了解Raspberrry Pi OS(又名Raspbian)如何管理线程到内核的分配,也不知道Python是否可以或应该明确地引导线程在特定内核中运行
我欢迎Mark Setchell(或其他了解这些问题的人)提出建议,推荐最佳实践并包括示例代码。

您可以使用v4l2-ctl直接捕获帧:

v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=1 --stream-to=output.jpg

您的输入已经是JPG了,所以它会捕获它。

请参阅v4l2-ctl --help-streaming了解更多信息。

首先,感谢https://stackoverflow.com/users/1109017/llogan他在下面的评论中为我提供了我需要的线索。

我在这里记录这个解决方案,以便其他可能没有阅读评论的人发现。

这是我修改后的程序:

list_of_camera_ids = ["/dev/video1","/dev/video2", "/dev/video3", "/dev/video4",
"/dev/video5","/dev/video6", "/dev/video7", "/dev/video8",
"/dev/video9","/dev/video10", "/dev/video11", "/dev/video12",
"/dev/video13","/dev/video14", "/dev/video15", "/dev/video16",
"/dev/video17","/dev/video18"
]
for this_camera_id in list_of_camera_ids:
full_image_file_name = '/tmp/' + os.path.basename(this_camera_id) + 'jpg'
image_capture_tic = time.perf_counter()

run_cmd = subprocess.run([
'v4l2-ctl','-d',
this_camera_id,
'--stream-mmap', 
'--stream-count=1',
'--stream-to=' +
full_image_file_name,"&"
],
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)  
print(run_cmd.stderr)
image_capture_toc = time.perf_counter()       
print(f"Captured {camera_id} image in: {image_capture_toc - image_capture_tic:0.0f} seconds")

其他注意事项:此代码大大加快了速度!

用我以前的方法,每个图像需要3-4秒才能捕捉到。在原始帖子中显示的序列化循环中,18个图像通常需要45到60秒才能完成。

根据llogan的建议,我修改了代码,现在每台相机的拍摄时间不到1秒。此外,简单地通过在背景中附加一个""根据命令,它们自动并行运行,18台相机的总时间现在约为10秒,因此在Raspberry Pi 400上,每台相机的平均时间约为.55秒。

我怀疑我可能会因为使用琐碎的""用于并行化的方法。如果我只生成线程而不是完整的进程,那么其中的一些可能会进一步减少。但这是一个我还没有经验的性能调整级别。

这里有很多问题:

  • 你没有说你在用什么相机
  • 它们是如何连接的(USB 2/3端口)?USB集线器
  • 决议也没有
  • 你设置了一个帧速率,这个帧速率似乎很奇怪,因为它指定了帧之间的时间,但你只想要一帧
  • 您正在按顺序而不是并行扫描相机
  • 您在Raspberry Pi上只使用4个核心中的一个

最新更新