最近博主在做HDR2SDR 的项目,在用python做demo的过程中遇到一个问题,输入是HDR的视频(H.265, YUV420P10LE, BT2020, 25fps的MKV文件),由于MKV文件是10bit的,博主直接使用 cv2.VideoCapture() 函数,得到的frame数据只有8bit, 在网上找了好久,都不知道如何使用cv2.VeideoCapture()函数读10bit 的数据,只好使用最笨的办法,此博文用以记录,如果有相同需求的朋友看到这篇文章也可有个借鉴,如果有知道如何直接读取10bit MKV文件的朋友,也麻烦留言告诉一下博主,博主感激不尽。
MKV是一种视频格式,包含许多封装信息,python要读取YUV420P10LE 格式,那么首先要将MKV文件解码出YUV文件,此处博主使用ffmpeg解码,且只选取其中10s的所有帧。语法如下:
ffmpeg -i inputfile.MKV -ss 00:00:00 -t 10 -s 3840x2160 -pix_fmt yuv420p10le outfile.yuv
解码得到YUV文件后,可以使用ffplay 播放一下视频:
ffplay -video_size 3840x2160 -pixel_format yuv420p10le -i outfile.yuv
解码得到单帧YUV语法如下:
ffmpeg -i input.MKV -ss 00:00:00 -f segment -segment_time 0.04 -frames 1 -s 3840x2160 -pix_fmt yuv420p10le %3d.yuv
此处博主有个疑惑,用ffplay播放的视频和直接打开MKV播放的视频并不一致,亮度和颜色明显被抑制,博主猜测可能原因是MKV直接使用播放器播放的视频是BT2020 10 bit的,但是ffplay 在播放时,只能播放8bit的视频(内部做了10-8处理),未做验证。
得到YUV10bit数据,排列方式是按照 YYY...YYY UUU...UUU VVV...VVV排放,且每个分量占2个byte. 因此python 代码如下:
input_filename = "outfile.yuv"
pic_width = 3840
pic_height = 2160
frame_number = 250
frame_size = 24883200fp = open(input_filename , 'rb')
ps = fp.tell()
for i in range(frame_num):Yt = np.zeros(shape=(pic_height , pic_width ), dtype=np.uint16, order='C')Ut = np.zeros(shape=(pic_height , pic_width ), dtype=np.uint16, order='C')Vt = np.zeros(shape=(pic_height , pic_width ), dtype=np.uint16, order='C')for m in range(pic_height):for n in range(pic_widht):Yt[m,n] = ord(fp.read(1))+(ord(fp.read(1))<<8)for m in range(0, pic_height, 2):for n in range(0, pic_widht-1, 2):Ut[m,n] = ord(fp.read(1))+(ord(fp.read(1))<<8)Ut[m+1, n] = Ut[m, n+1] = Ut[m+1, n+1] = Ut[m, n]for m in range(0, pic_height-1, 2):for n in range(0, pic_widht-1, 2):Vt[m//2, n//2] = ord(fp.read(1)) + (ord(fp.read(1)) << 8)Vt[m + 1, n] = Vt[m, n + 1] = Vt[m + 1, n + 1] = Vt[m, n]
注意,此处我是因为要将YUV再转成RGB格式后再进行处理,因此我在读UV数据时,将图像变成了YUV444模式, 如果仅仅只需要YUV420模式,请自行修改代码。