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

ELP USB摄像头FHD分辨率下帧格式异常引发FFmpeg转码失败及文件传输问题

ELP USB摄像头FHD分辨率下帧格式异常引发FFmpeg转码失败及文件传输问题

问题背景

我在使用ELP-USBFHD06H-SFV(5-50) USB摄像头开发定时抓帧功能时,遇到了两个棘手问题:

  1. 用OpenCV保存的JPG文件无法通过FFmpeg生成延时摄影视频,报错无法识别编码参数
  2. 从Linux将图片传输到Windows时,系统提示复制失败,但跳过错误后文件又能正常打开

我的需求是每隔X秒抓取一帧1920x1080分辨率的画面,当前用Python + OpenCV实现,核心代码和配置如下:


核心实现代码(Python)

import cv2
import subprocess

from time import sleep
from collections import namedtuple

from errors import *

class Camera:
    def __init__(self, cam_index, res_width, res_height, pic_format, day_time_exposure_ms, night_time_exposure_ms):
        Resolution = namedtuple("resolution", ["width", "height"])
        self.manual_mode(True)

        self.cam_index = cam_index
        self.camera_resolution = Resolution(res_width, res_height)
        self.picture_format = pic_format
        self.day_time_exposure_ms = day_time_exposure_ms
        self.night_time_exposure_ms = night_time_exposure_ms

        self.started: bool = False
        self.night_mode = False

        self.cap = cv2.VideoCapture(self.cam_index, cv2.CAP_V4L2)
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.camera_resolution.width)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.camera_resolution.height)
        self.cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*self.picture_format))

    def start(self):
        sleep(1)
        if not self.cap.isOpened():
            return CameraCupError()

        self.set_exposure_time(self.day_time_exposure_ms)
        self.set_brightness(0)
        sleep(0.1)
        
        self.started = True

    def shot(self, picture_name, is_night):
        if not self.started:
            return InitializationError()

        self.configure_mode(is_night)

        # 尝试清理缓冲区
        for _ in range(5):
            ret, _ = self.cap.read()
        
        # 获取最新帧
        ret, frame = self.cap.read()

        sleep(0.1)

        if ret:
            print(picture_name)
            cv2.imwrite(picture_name, frame)
            return True

        else:
            print("No photo")
            return False

    def release(self):
        self.set_exposure_time(156)
        self.set_brightness(0)
        self.manual_mode(False)
        self.cap.release()

    def manual_mode(self, switch: bool):
        if switch:
            subprocess.run(["v4l2-ctl", "--set-ctrl=auto_exposure=1"])
        else:
            subprocess.run(["v4l2-ctl", "--set-ctrl=auto_exposure=3"])
        sleep(1)

    def configure_mode(self, is_night):
        if is_night == self.night_mode:
            return

        if is_night:
            self.night_mode = is_night
            self.set_exposure_time(self.night_time_exposure_ms)
            self.set_brightness(64)
        else:
            self.night_mode = is_night
            self.set_exposure_time(self.day_time_exposure_ms)
            self.set_brightness(0)
        sleep(0.1)

    def set_exposure_time(self, ms: int):
        ms = int(ms)
        default_val = 156

        if ms < 1 or ms > 5000:
            ms = default_val

        self.cap.set(cv2.CAP_PROP_EXPOSURE, ms)

    def set_brightness(self, value: int):
        value = int(value)
        default_val = 0

        if value < -64 or value > 64:
            value = default_val

        self.cap.set(cv2.CAP_PROP_BRIGHTNESS, value)

摄像头配置(YAML)

camera:
  camera_index: 0
  res_width: 1920
  res_height: 1080
  picture_format: "MJPG"
  day_time_exposure_ms: 5
  night_time_exposure_ms: 5000
  photos_format: "jpg"

遇到的问题细节

1. FFmpeg生成延时摄影失败

执行以下命令尝试将所有JPG转为MP4延时视频:

ffmpeg -framerate 20 -pattern_type glob -i "*.jpg" -c:v libx264 output.mp4

返回错误信息:

[image2 @ 0x555609c45240] Could not open file : 08:59:20.jpg
[image2 @ 0x555609c45240] Could not find codec parameters for stream 0 (Video: mjpeg, none(bt470bg/unknown/unknown)): unspecified size
Consider increasing the value for the 'analyzeduration' (0) and 'probesize' (5000000) options
Input #0, image2, from '*.jpg':
  Duration: 00:00:00.05, start: 0.000000, bitrate: N/A
  Stream #0:0: Video: mjpeg, none(bt470bg/unknown/unknown), 20 fps, 20 tbr, 20 tbn
Output #0, mp4, to 'output.mp4':
Output file #0 does not contain any stream

2. Linux到Windows文件传输异常

将保存的JPG文件复制到Windows时,系统弹出复制失败提示,但点击"跳过"后,文件仍能正常打开,推测是文件格式存在非标准标记或写入不完整。

3. 尝试过的临时修复

  • 为获取实时帧,每次抓帧前循环read5次清理缓冲区,但格式问题依然存在
  • 尝试用FFmpeg直接录制视频,却被驱动强制降级为800x600分辨率:
    ffmpeg -f v4l2 -framerate 30 -video_size 1920x1080 -i /dev/video0 -c:v libx264 -preset fast -crf 23 -t 00:01:00 output.mp4
    
    输出提示:
    [video4linux2,v4l2 @ 0x555659ed92b0] The V4L2 driver changed the video from 1920x1080 to 800x600
    [video4linux2,v4l2 @ 0x555659ed92b0] The driver changed the time per frame from 1/30 to 1/15
    
    手动用v4l2-ctl重置格式后恢复FHD:
    v4l2-ctl --device=/dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=MJPG
    v4l2-ctl --get-fmt-video
    

问题根源分析

  1. OpenCV二次编码破坏格式:OpenCV读取MJPG帧时会先解码为BGR矩阵,再通过cv2.imwrite重新编码为JPEG。这个过程可能破坏原始MJPG的标准头信息,导致FFmpeg无法识别图片尺寸,同时可能引发文件写入不完整。
  2. 缓冲区清理不彻底:仅循环read5次不足以清空摄像头内部缓冲区(高帧率摄像头缓冲区通常存储更多旧帧),导致抓帧并非实时画面。
  3. FFmpeg命令缺少格式指定:当JPEG头异常时,FFmpeg无法自动推导尺寸,需手动指定参数。
  4. FFmpeg录制未指定输入格式:未声明输入格式为MJPG,导致驱动自动 fallback 到兼容的低分辨率格式。

解决方案建议

方案1:直接保存原始MJPG帧(推荐)

跳过OpenCV的解码-重编码步骤,直接抓取摄像头原始MJPG字节流,避免格式损坏:

def shot(self, picture_name, is_night):
    if not self.started:
        return InitializationError()

    self.configure_mode(is_night)
    sleep(0.2)  # 等待参数生效

    # 用v4l2-ctl直接抓取1帧原始MJPG
    result = subprocess.run([
        "v4l2-ctl",
        f"-d /dev/video{self.cam_index}",
        "--set-fmt-video=width=1920,height=1080,pixelformat=MJPG",
        "--stream-mmap",  # 内存映射提升抓帧效率
        "--stream-count=1",  # 仅抓取1帧
        f"--stream-to={picture_name}"
    ], capture_output=True, text=True)

    if result.returncode == 0:
        print(f"Successfully saved {picture_name}")
        return True
    else:
        print(f"Capture failed: {result.stderr}")
        return False

方案2:修复FFmpeg转码命令

若坚持使用OpenCV保存的JPG,手动指定图片尺寸和格式参数:

ffmpeg -framerate 20 -pattern_type glob -i "*.jpg" -video_size 1920x1080 -pixel_format yuvj420p -c:v libx264 output.mp4

或增加分析时长,让FFmpeg有足够时间解析文件:

ffmpeg -analyzeduration 10M -probesize 10M -framerate 20 -pattern_type glob -i "*.jpg" -c:v libx264 output.mp4

方案3:优化缓冲区清理

循环grab()(仅抓帧不解码,效率更高)直到清空缓冲区:

def shot(self, picture_name, is_night):
    if not self.started:
        return InitializationError()

    self.configure_mode(is_night)
    
    # 彻底清空缓冲区
    while self.cap.grab():
        pass
    
    ret, frame = self.cap.read()
    if ret:
        cv2.imwrite(picture_name, frame, [int(cv2.IMWRITE_JPEG_QUALITY), 95])  # 指定编码质量
        return True
    return False

方案4:正确用FFmpeg直接录制

指定输入格式为MJPG,避免驱动降级分辨率:

ffmpeg -f v4l2 -input_format mjpeg -framerate 30 -video_size 1920x1080 -i /dev/video0 -c:v copy -t 00:01:00 output.mp4

-c:v copy参数直接复制原始MJPG流,不转码,保证格式和分辨率正确。


验证步骤

  1. 用方案1抓1帧,检查文件大小是否符合1920x1080 MJPG预期(约2-4MB)
  2. 将文件传输到Windows,确认无复制错误
  3. 用修改后的FFmpeg命令生成延时摄影,验证是否成功
  4. 测试抓帧实时性,确保无延迟

备注:内容来源于stack exchange,提问作者Marcel Kopera

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 09:28:01