从YUV文件中提取出特定的某一帧有哪几种办法?

这种场合一般发生在需要帧同步调试/特别比照某两帧/检查内容/批处理的场合,不过其实专门评论意义有限。由于往往都有图形化界面自在检查。

因而这其实是一个相对无聊的问题。当然,换一种办法也能够:关于像YUV这样一个规整的格局,有多少种工具能够拆分文件?

p.s. 以下都以YUV420格局为例子。其每帧巨细为WH32Wtimes H times frac{3}{2}.

FFmpeg

  • 假如手头有FFmpeg工具,这是坠吼滴。
ffmpeg -s [width]x[height] -i input.yuv -vframes 1 -f rawvideo -pix_fmt yuv420p output.yuv

-vframes 1 表明只处理一个视频帧。

  • 视场景设置,改换为帧率指定也是能够的。

例如,关于30fps的视频,提取第10秒的一帧。即第300帧:

ffmpeg -s [width]x[height] -r 30 -i input.yuv -ss 00:00:10 -vframes 1 -f rawvideo -pix_fmt yuv420p output.yuv

Python

高档言语就恣意折腾就好。一般都是系统之一的模块。

width = 1920 # 示例宽度
height = 1080 # 示例高度
frame_number = 1 # 提取第1帧# YUV420格局一帧数据巨细
frame_size = width * height   (width // 2) * (height // 2) * 2with open('input.yuv', 'rb') as file:
  file.seek(frame_size * (frame_number - 1))
  frame_data = file.read(frame_size)
  with open(f'output_{width}x{height}.yuv', 'wb') as output_file:
    output_file.write(frame_data)

C

同上,高档言语恣意折腾。惯例 File IO.

#include <stdio.h>
#include <stdlib.h>
​
int main()
{
  FILE *file = fopen("input.yuv", "rb");
  if (file == NULL)
   {
    perror("Error opening file");
    return 1;
   }
​
  int width = 1920;   // 宽度
  int height = 1080;  // 高度
  int frame_number = 1; // 提取的帧号
​
  // YUV420格局一帧数据巨细
  int frame_size = width * height   (width / 2) * (height / 2) * 2;
  unsigned char *frame_data = (unsigned char *)malloc(frame_size);
  if (frame_data == NULL)
   {
    perror("Memory allocation failed");
    fclose(file);
    return 1;
   }
​
  fseek(file, (long)frame_size * (frame_number - 1), SEEK_SET);
  fread(frame_data, 1, frame_size, file);
  FILE *output_file = fopen("output.yuv", "wb");
  if (output_file != NULL)
   {
    fwrite(frame_data, 1, frame_size, output_file);
    fclose(output_file);
   }
  else
   {
    perror("Error opening output file");
   }
  free(frame_data);
  fclose(file);
  return 0;
}
​

Command line

其实主要是想折腾这种办法,即在没有FFmpeg和高档言语支持下怎么(随手)完成规整文件读写这样的小事。

dd

能够考虑dd,直接指定文件读写的位置和巨细。

例如,1920×1080 YUV 420 提取第ii帧:

dd if=input.yuv of=output_frame.yuv bs=1 count=$((1920*1080*3/2)) skip=$(( [i-1] *1920*1080*3/2))

其中, -if=input.yuv 指定输入文件; -of=output_frame.yuv 指定输出文件; -bs=1 设置块巨细为1字节; -count=$((1920*1080*3/2)) 指定复制的字节数,即一帧的巨细; -skip=$((4*1920*1080*3/2)) 越过前i−1i-1帧的数据量,以提取第ii帧.

split & cat

或者直接运用split分帧split分帧是确实便利,便是文件命名比较折腾)

# 分帧,以frame_xx命名,xx遵从字母计数法。
split -b $((1920*1080*3/2)) input.yuv frame_
# 例如,将某一帧(_aa,26x25 帧规模内转化出来的榜首帧)转化为输出文件
cat frame_aa > output.yuv

当然,由于split 命令生成的文件后缀是字母计数法。是按照字母顺序递增的,从 aa 开始,然后是 ab,以此类推。所以会留传一堆不需要的分帧。

解决这个问题的话能够通过一个粗糙的shell脚本,其编写了convert_number_to_suffix函数,完成了最简略粗暴的字母计数法转化(ASCII码直转),能够处理帧数为26~676的视频。

#!/bin/bash# 字母计数法和阿拉伯数字转化脚本,规模为 26~676(即26x26)
convert_number_to_suffix() {
   local num=$1
   local -i charcode_a=97
   local -i first_digit=$(( (num - 1) / 26   charcode_a ))
   local -i second_digit=$(( (num - 1) % 26   charcode_a ))
   local first_char=$(printf $(printf 'o' $first_digit) )
   local second_char=$(printf $(printf 'o' $second_digit) )
  echo "$first_char$second_char"
}
if [ "$#" -ne 4 ]; then
  echo "Usage: $0 <input.yuv> <width> <height> <frame_number>"
  exit 1
fi
# 输入的YUV文件途径
INPUT_FILE=$1
# 视频的宽度
WIDTH=$2
# 视频的高度
HEIGHT=$3
# 提取的帧序号
FRAME_NUMBER=$4
​
FRAME_SIZE=$((WIDTH * HEIGHT * 3 / 2))
FILE_SUFFIX=$(convert_number_to_suffix $FRAME_NUMBER)
split -b $FRAME_SIZE $INPUT_FILE frame_
cat "frame_$FILE_SUFFIX" > "output_frame_$FRAME_NUMBER.yuv"# 清理其他分割文件
rm frame_*
​

与其说是在折腾分帧,不如说是在折腾命名办法

总结

现已折腾得简略问题杂乱化了。

TODO:

  • 比照高档言语脚本完成与dd/cat办法的性能分析
  • 字母序列计数法的完成评论,我们能完成一个更完善的版别(当然,那会更杂乱,并且属于另一个问题了)