我正在寻找一个可以从网络摄像头拍照的命令行工具尽可能快并将其写入 stdout。此外,我希望能够指定输入格式、分辨率和输出格式等设置。
我的第一次尝试是 ffmpeg:
ffmpeg -f video4linux2 -video_size 1920x1080 -input_format yuyv422 -i /dev/video0 -f image2 -frames:v 1 -qscale:v 2 pipe:1
然而,这有两个缺点:
将图像写入标准输出大约需要 3 秒钟,这似乎是由于网络摄像头需要初始化的时间。
这样拍摄的照片相当暗,网络摄像头可能需要捕捉视频帧来调整亮度等。
我的下一个想法是连续从网络摄像头捕获视频帧(即“保持网络摄像头处于活动状态”),然后流式传输这些视频帧某处,然后抓取单个视频帧并根据需要将其转换为图像。但是,我不知道如何做到这一点(以及是否有更好的方法)。
编辑:我需要一个一次性命令,将图像写入 stdout,这样我就可以在 http 服务器中使用它来处理 http GET 请求。它需要快速,因为拍照会阻碍机器中的机械过程。
答案1
对于我的网络摄像头,fswebcam
可以在不到半秒的时间内拍摄一张照片:
$ time fswebcam test1.jpg
...
real 0m0.491s
user 0m0.024s
sys 0m0.000s
写入标准输出然后保存需要更长的时间:
$ time fswebcam - > test2.jpg
...
real 0m0.538s
user 0m0.029s
sys 0m0.000s
您还可以每隔n秒(--loop n
),如果这是你需要的(你没有解释为什么您希望它尽可能快,以及循环是否可以提供帮助或如何提供帮助)。
您可以使用 所示的控件来增亮图像,或者调整对比度等fswebcam --list-controls
。
答案2
我自己找到了一个可能的解决方案,尽管它不是最佳的,因为在我想要实现它的 Raspberry Pi 上,它将 FPS 降低到相机支持的值的一半(并且较低的 FPS = 直到下一个可以提取为图片的视频帧的可能延迟)。
我使用 ffmpeg 将视频数据从 /dev/video0 处的硬件设备复制到v4l2-环回设备位于 /dev/video1。这样,摄像头保持活动状态,因为 ffmpeg 正在 /dev/video0 上读取它,而我可以使用 ffmpeg 的另一个实例从 /dev/video1 中提取图片。
要在 Raspbian GNU/Linux 9 (stretch) 上安装 v4l2-loopback:
- 安装内核头文件
apt-get install raspberrypi-kernel-headers
:(回复:如何安装内核头文件) - 安装 v4l2-loopback
apt-get install v4l2loopback-dkms
:(v4l2-loopback 分布) - 添加环回设备:
modprobe v4l2loopback
或者使用更多设备modprobe v4l2loopback devices=2
(v4l2-环回运行)
要将视频从 /dev/video0 复制到 /dev/video1:
ffmpeg -f video4linux2 -video_size 1920x1080 -input_format yuyv422 -i /dev/video0 -codec copy -f video4linux2 /dev/video1
我的相机支持 mjpeg 和 yuyv422 格式,但只能将“原始”格式复制到回环设备,因此我必须使用 yuyv422。相机使用 yuyv422 和 1920x1080 时支持高达 6 fps,但 Raspberry Pi 2 Model B 只能将大约 3 fps 复制到回环设备。
现在我可以使用我在问题中已经提到的命令来提取图片,作为 JPEG:
ffmpeg -f video4linux2 -video_size 1920x1080 -input_format yuyv422 -i /dev/video1 -f image2 -frames:v 1 -qscale:v 2 pipe:1
或者作为位图:
ffmpeg -f video4linux2 -video_size 1920x1080 -input_format yuyv422 -i /dev/video1 -c:v bmp -f image2 -pix_fmt bgr24 -frames:v 1 pipe:1
通过从环回设备读取这些命令,图像不会变暗,并且需要大约 1.3 秒(JPEG)或 1.1 秒(位图)将它们发送到标准输出。
答案3
对于我来说,得票最高的答案需要 4~4.5 秒,挂在系统调用上。使用strace
我发现延迟来自ioctl(3, VIDIOC_STREAMON, [V4L2_BUF_TYPE_VIDEO_CAPTURE]
。
为了能够快速捕获帧,我发现的解决方案是使用连续流streamer
(可能也可以使用ffmpeg
其他工具)并在 Python 中处理它。类似于:
import os, subprocess
subprocess.DEVNULL = open(os.devnull, 'w') # py3 polyfill
# Because the video is 320x240 and the format rgb (3 bytes, one for each color)
framesize = 320 * 240 * 3
proc = subprocess.Popen('streamer -r 2 -s 320x240 -f rgb -t 360'.split(' '), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
buf = ''
while True:
previousbufferlength = len(buf)
buf += proc.stdout.read(framesize)
if len(buf) == previousbufferlength: # read() returned zero bytes: EOF
break
while len(buf) >= framesize:
# Process the first $framesize bytes of the buffer here!
# For example, write the frame to disk:
f = open('your.rgb', 'w')
f.write(buf[ : framesize])
f.close()
buf = buf[framesize : ]
这不使用b''
诸如此类的东西(字节而不是字符串),因为它是为与 一起工作而设计的pypy
。在 Raspberry Pi 2 v1.1 上,pypy
可以创建图像中每个像素的基本统计数据,同时使用不到 3% 的 CPU——当然,这是在 320x240 分辨率和每秒 2 帧的情况下,但这就是我目前所需要的。该streamer
过程使用了类似的数量。
将 rgb24 转换为另一种格式,ffmpeg
可以帮助您(针对一帧或整个流):
ffmpeg -r 2 -f rawvideo -pix_fmt rgb24 -s 320x240 -i your.rgb out.mkv
# For a picture format, just change the extension to jpg:
ffmpeg -f rawvideo -pix_fmt rgb24 -s 320x240 -i your.rgb out.jpg
# Or if you have more than one frame in your video, but you just want one frame:
ffmpeg -f rawvideo -pix_fmt rgb24 -s 320x240 -i your.rgb -vframes 1 out.jpg
OP 的最终目标是让这个功能为网络服务器提供即时图片。要实现这一点,您仍然需要将这些部分结合在一起,但我认为如果您有像我这样的网络摄像头,以上应该是您需要的所有组件。
请注意,许多网络摄像头需要几秒钟来对焦、调整白平衡,甚至可能做其他事情。即使它fswebcam
在不到 0.5 秒的时间内为您工作,也可能产生更好的图像质量,而是从流中获取帧并提供这些帧,而不是在单个帧的持续时间内在网络摄像头上生成新的捕获流。像这样的设置,上面的 Python 代码随意向客户端提供最新的帧,也比每个访问者每次加载页面启动一个进程(无法同时工作)的扩展性要好得多。
如果我们讨论的是从流中提供服务,我可以提供的另一个建议是使用 HLS 或 DASH 之类的东西。ffmpeg
可以再次提供帮助并输出 HLS 格式,您可以任意剪切但仍然可以查看它。