如何将原始的YUV图像转换为JPG

4

我有一张原始图像,是在相机设置完成后使用v4l2-ctl拍摄的:

# media-ctl -d /dev/media0 -l "'rzg2l_csi2 10830400.csi2':1 -> 'CRU output':0 [1]"
# media-ctl -d /dev/media0 -V "'rzg2l_csi2 10830400.csi2':1 [fmt:UYVY8_2X8/1280x960 field:none]"
# media-ctl -d /dev/media0 -V "'ov5645 0-003c':0 [fmt:UYVY8_2X8/1280x960 field:none]"

然后使用以下方式拍摄图片:

# v4l2-ctl --device /dev/video0 --stream-mmap --stream-to=frame.raw --stream-count=1

现在我已经尝试了多种方法将其转换为JPEG格式,但没有一个方法能产生预期的输出。
可以在此处下载原始文件:https://drive.google.com/file/d/1VqXnrJDYbzdtSsWfTlm2mX9rl1-Rl_7F/view?usp=sharing 我尝试了以下命令:
convert -verbose -size 1280x960 UYVY:frame.raw frame.bmp

我在Converting from YUV(UYVY) to RGB using imagemagick上找到了相关的信息,但是它并不能解决问题。


1
也许您可以通过Google Drive、Dropbox或类似的方式分享原始文件。 - Mark Setchell
@MarkSetchell,我在上面的帖子中添加了一个链接。 - stdcerr
1
现在这里已经很晚了,所以如果到明天还没有其他人提供帮助的话,我会再看一下。 - Mark Setchell
谢谢@MarkSetchell,我也会自己多做些研究!期待明天收到您的回复,晚上好! - stdcerr
1个回答

5

你的框架大小为2457600字节,像素尺寸为1280x960,因此你有:

bits per pixel = 2457600 * 8 / (1280 * 960) = 16

你可以使用以下命令获取ffmpeg支持的像素格式列表:
ffmpeg -pix_fmts 2> /dev/null

样例输出

FLAGS NAME            NB_COMPONENTS BITS_PER_PIXEL
-----
IO... yuv420p                3            12
IO... yuyv422                3            16
IO... rgb24                  3            24
IO... bgr24                  3            24
IO... yuv422p                3            16
IO... yuv444p                3            24
IO... yuv410p                3             9
...
...

这意味着您可以按照以下方式获取包含每像素16位的YUV的像素格式列表:
ffmpeg -pix_fmts 2> /dev/null | awk '/y/ && /u/ && /16$/ {print}'  

IO... yuyv422                3            16
IO... yuv422p                3            16
IO... yuvj422p               3            16
IO... uyvy422                3            16
IO... yuv440p                3            16
IO... yuvj440p               3            16
IO... yvyu422                3            16

现在,您可以运行一个循环,迭代所有16位每像素YUV格式,并查看ffmpeg对您的图像的处理结果-将每个结果命名为格式,以便您可以识别它们。
ffmpeg -pix_fmts 2> /dev/null | 
   awk '/y/ && /u/ && /16$/ {print $2}' | 
      while read f; do 
         ffmpeg -y -s:v 1280x960 -pix_fmt $f -i frame.raw $f.jpg
      done

这将给你这些文件:

-rw-r--r--  1 mark  staff  304916  3 Feb 09:38 yuv440p.jpg
-rw-r--r--  1 mark  staff  227123  3 Feb 09:38 yuvj422p.jpg
-rw-r--r--  1 mark  staff   39543  3 Feb 09:38 yuyv422.jpg
-rw-r--r--  1 mark  staff   39545  3 Feb 09:38 yvyu422.jpg

我猜测yuyv422.jpg是你的图片,所以你可以使用以下方式提取它:

ffmpeg -y -s:v 1280x960 -pix_fmt yuyv422 -i frame.raw result.jpg

enter image description here


如果您想使用ImageMagick来实现这一点,可以尝试以下方法:
#!/bin/bash

python3 <<EOF
import numpy as np
h, w = 960, 1280

# Load raw file into Numpy array
raw = np.fromfile('frame.raw', np.uint8)
raw[0::2].tofile('Y')     # Starting at the 1st byte, write every 2nd byte to file "Y"
raw[1::4].tofile('U')     # Starting at the 2nd byte, write every 4th byte to file "U"
raw[3::4].tofile('V')     # Starting at the 3rd byte, write every 4th byte to file "V"
EOF

# Load the Y channel, then the U and V channels forcibly resizing them, then combine and go to sRGB
magick -depth 8 -size 1280x960 gray:Y \
  \( -size 640x960 gray:U gray:V -resize 1280x960\! \) \
  -set colorspace YUV -combine -colorspace sRGB result.jpg

如果您不喜欢或不会Python,那么可以用以下基本的C语言代替该部分:
#include <stdint.h>
#include <stdio.h>

// Split YUYV file called "frame.raw" into separate channels with filenames "Y", "U" and "V"
// Compile with: clang -O3 splitter.c -o splitter

int main(){

   FILE    *in, *Y, *U, *V;
   uint8_t buffer[4];
   size_t  bytesRead;

   // Open input file and 1 output file per channel
   in = fopen("frame.raw", "rb");   
   Y  = fopen("Y", "wb");   
   U  = fopen("U", "wb");   
   V  = fopen("V", "wb");   

   // read up to sizeof(buffer) bytes
   while ((bytesRead = fread(buffer, 1, sizeof(buffer), in)) > 0)
   {
      fputc(buffer[0], Y);
      fputc(buffer[1], U);
      fputc(buffer[2], Y);
      fputc(buffer[3], V);
   }
}

之前我已经用过 ffmpegPythonC 版本,感觉很有趣,现在我尝试在 shell 中完成同样的操作——将字节转换为行,这样我就可以选择交替行而不是交替字节。这与上述版本的效果相同:

#!/bin/bash

# Build JPEG image from YUYV image with packed bytes in order YUYVYUYV...
# Use "xxd" to convert bytes into lines, then extract alternate lines - which is easier than extracting bytes

H=960
W=1280
INPUT="frame.raw"

# Take top byte of every uint16 and put into "Y.pgm"
xxd -c1 -p "$INPUT" | sed -n 'p;n' | xxd -r -p | magick -size ${W}x${H} -depth 8 gray:- Y.pgm

# Take bottom byte of every 2nd uint16, starting at the 1st, resize up to full width and put into "U.pgm"
xxd -c1 -p "$INPUT" | sed -n 'n;p' | sed -n 'p;n' | xxd -r -p | magick -size $((W/2))x${H} -depth 8 gray:- -resize ${W}x${H}\! U.pgm

# Take bottom byte of every 2nd uint16, starting at the 2nd, resize up to full width and put into "V.pgm"
xxd -c1 -p "$INPUT" | sed -n 'n;p' | sed -n 'n;p' | xxd -r -p | magick -size $((W/2))x${H} -depth 8 gray:- -resize ${W}x${H}\! V.pgm

# Load the 3 channels, combine and convert to JPEG
magick {Y,U,V}.pgm -set colorspace YUV -combine -colorspace sRGB result.jpg

# Remove litter
rm {Y,U,V}.pgm

关于去除色偏,如我在评论中所说,据我所知,"正常"的方法是获取图像的平均颜色,反转其色相,然后将"否定的色偏"与原始图像混合以抵消原始色偏。这里是一个粗略的尝试 - 如果有人知道更好的方法,请告诉我!
步骤1:获取平均色偏
magick result.jpg -resize 1x1\! cast.png

enter image description here

步骤2:反转投射。
magick cast.png -modulate 100,100,0 correction.png

enter image description here

步骤三:将原始内容与更正内容混合,并可能进行亮度调整。
magick result.jpg correction.png -define compose:args=50,50 -compose blend -composite -auto-level result.jpg

以下是原始版本和更正后的版本:

enter image description here

显然,您可以更改不同程度的“更正”的百分比。


使用ImageMagick的版本似乎按预期正常工作! - stdcerr
1
ffmpeg 应该可以正常工作,尽管会出现错误信息。 - Mark Setchell
没错,它确实能够工作。抱歉,我将循环添加到了脚本中,这不起作用,它会给我返回错误信息:“没有这样的像素格式:uvj440p. frame.raw: 无效参数”,但是单独选择该命令:ffmpeg -y -s:v 1280x960 -pix_fmt yuyv422 -i frame.raw result.jpg 是可以正常工作的!非常感谢你的帮助! - stdcerr
1
很酷 - 我很乐意。祝你的项目好运! - Mark Setchell
你知道在从YUYV422转换后,图片为什么会出现黄色调吗?你知道有没有一种方法可以获得真实的颜色而不会出现黄色调吗? - stdcerr
1
“正常”的方法是获取图像的平均颜色,反转色调并将其与原始颜色混合以抵消色彩偏差。我建议您提出一个新问题,看看这里是否有任何相当知识渊博的人有更好的想法。一定要将其框架化为编程问题,而不是Photoshop问题,否则它将被关闭。您的图像实际上也相当曝光不足,这可能也没有帮助。将新问题标记为“图像处理”。 - Mark Setchell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接