Python OpenCV将平面YUV 4:2:0图像转换为RGB--YUV数组格式
我正试图通过python使用OpenCV 4.1.0版将平面YUV 4:2:0图像转换为RGB,并且正在努力理解如何格式化数组以传递给Python OpenCV将平面YUV 4:2:0图像转换为RGB--YUV数组格式,python,numpy,opencv,image-processing,Python,Numpy,Opencv,Image Processing,我正试图通过python使用OpenCV 4.1.0版将平面YUV 4:2:0图像转换为RGB,并且正在努力理解如何格式化数组以传递给cvtColor函数。我将所有3个通道作为单独的阵列,并尝试将它们合并以与cv2.cvtColor一起使用。我使用的是cv2.cvtColor(yuv\u数组,cv2.COLOR\u YUV420p2RGB)。我知道yuv_数组应该是原始图像的1.5倍高(这是cvtColor中使用cv2.COLOR\u RGB2YUV_YV12的yuv数组的高度),我应该将UV组
cvtColor
函数。我将所有3个通道作为单独的阵列,并尝试将它们合并以与cv2.cvtColor
一起使用。我使用的是cv2.cvtColor(yuv\u数组,cv2.COLOR\u YUV420p2RGB)
。我知道yuv_数组
应该是原始图像的1.5倍高(这是cvtColor
中使用cv2.COLOR\u RGB2YUV_YV12
的yuv数组的高度),我应该将UV组件放入yuv_数组的下半部分
并将Y通道放入数组的顶部
我似乎不知道U和V通道应该如何在这个数组的底部格式化。我试着把它们交错放在一起,然后把它们两个连在一起。在这两种方法中,我都试着先放U,然后放V,反之亦然。所有方法都会导致结果图像中出现瑕疵。以下是我的代码和示例图像:
import os
import errno
import numpy as np
import cv2
fifo_names = ["/tmp/fifos/y_fifo", "/tmp/fifos/u_fifo", "/tmp/fifos/v_fifo"]
#teardown; delete fifos
import signal, sys
def cleanup_exit(signal, frame):
print ("cleaning up!")
for fifo in fifo_names:
os.remove(fifo)
sys.exit(0)
signal.signal(signal.SIGINT, cleanup_exit)
signal.signal(signal.SIGTERM, cleanup_exit)
#make fifos
for fifo in fifo_names:
try:
os.mkfifo(fifo);
except OSError as oe:
if oe.errno == errno.EEXIST:
os.remove(fifo)
os.mkfifo(fifo)
else:
raise()
#make individual np arrays to store Y,U, and V channels
#we know the image size beforehand -- 640x360 pixels
yuv_data = []
frame_size = []
fullsize = (360, 640)
halfsize = (180, 320)
for i in range(len(fifo_names)):
if (i == 0):
size = fullsize
else:
size = halfsize
yuv_data.append(np.empty(size, dtype=np.uint8));
frame_size.append(size)
#make array that holds all yuv data for display with cv2
all_yuv_data = np.empty((fullsize[0] + halfsize[0], fullsize[1]), dtype=np.uint8)
#continuously read yuv images from fifos
print("waiting for fifo to be written to...")
while True:
for i in range(len(fifo_names)):
fifo = fifo_names[i]
with open(fifo, 'rb') as f:
print("FIFO %s opened" % (fifo))
all_data = b''
while True:
data = f.read()
print("read from %s, len: %d" % (fifo,len(data)))
if len(data) == 0: #then the fifo has been closed
break
else:
all_data += data
yuv_data[i] = np.frombuffer(all_data, dtype=np.uint8).reshape(frame_size[i])
#stick all yuv data in one buffer, interleaving columns
all_yuv_data[0:fullsize[0],0:fullsize[1]] = yuv_data[0]
all_yuv_data[fullsize[0]:,0:fullsize[1]:2] = yuv_data[1]
all_yuv_data[fullsize[0]:,1:fullsize[1]:2] = yuv_data[2]
#show each yuv channel individually
cv2.imshow('y', yuv_data[0])
cv2.imshow('u', yuv_data[1])
cv2.imshow('v', yuv_data[2])
#convert yuv to rgb and display it
rgb = cv2.cvtColor(all_yuv_data, cv2.COLOR_YUV420p2RGB);
cv2.imshow('rgb', rgb)
cv2.waitKey(1)
上面的代码正在尝试按列交错U和V信息
我还尝试使用以下方法将U和V通道信息放入all_yuv\U data
数组中:
#try back-to-back
all_yuv_data[0:fullsize[0],0:fullsize[1]] = yuv_data[0]
all_yuv_data[fullsize[0]:,0:halfsize[1]] = yuv_data[1]
all_yuv_data[fullsize[0]:,halfsize[1]:] = yuv_data[2]
该图像是通过libav从另一个程序获得的视频帧。框架的格式为AV_PIX_FMT_YUV420P,“平面YUV4:2:0,12bpp,(每2x2 Y样本1个Cr和Cb样本)”
以下是以灰度显示的示例图像的yuv通道:
Y频道:
U频道:
V频道:
以及相应的RGB转换(这是使用上述交错方法得出的,使用“背对背”方法时可以看到类似的伪影):
带有瑕疵的RGB图像:
我应该如何将u和v通道信息放置在所有数据中
由Mark Setchell在此点之后编辑
我相信预期的结果是:
存储在此函数调用中的
yuv\u数组底部的u和v通道信息:
cv2.cvtColor(yuv_数组,cv2.COLOR_YUV420p2RGB)
预期的格式如下所示:
添加到yuv_数组底部的额外行的上半部分用u信息填充。行是交错的;第一行u位于左侧插槽中y通道信息的正下方,第二行u位于同一行yuv_data
的右侧插槽中,依此类推
v通道数据是相同的,但对于添加到yuv_阵列的额外行的下半部分
以下是将MarkSetchnell放置在原始程序中时生成预期图像的连接代码:
#place y channel into buffer
all_yuv_data[0:fullsize[0],0:fullsize[1]] = yuv_data[0]
#formatted as interleaved u rows on top, (half on left, half on right)
#and interleaved v rows on bottom
all_yuv_data[fullsize[0]:fullsize[0]+halfsize[0]//2, :] = yuv_data[1].reshape(-1, fullsize[1])
all_yuv_data[fullsize[0]+halfsize[0]//2:,:] = yuv_data[2].reshape(-1, fullsize[1])
#convert to rgb
rgb = cv2.cvtColor(all_yuv_data, cv2.COLOR_YUV420p2RGB);
以下是所有_yuv_数据的灰度图像,以确保清晰:
调用cv2.cvtColor(所有数据、cv2.COLOR\u YUV420p2RGB)后的结果
:
如果YUV标准与OpenCVCOLOR\u YUV2BGR\u I420
转换公式相匹配,您可以将帧作为一个块来读取,并将其重塑为高度*1.5行应用转换
以下代码示例:
- 以YUV420格式构建输入,并将其写入内存流(而不是fifo)李>
- 从流中读取帧并使用
COLOR\u YUV2BGR\u I420将其转换为BGR
颜色不正确
- 通过读取Y、U和V,调整U和V的大小,并使用
COLOR\U YCrCb2BGR
转换,重复此过程。
注意:OpenCV使用BGR颜色格式(非RGB)李>
代码如下:
import cv2
import numpy as np
import io
# Building the input:
###############################################################################
img = cv2.imread('GrandKingdom.jpg')
#yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
#y, u, v = cv2.split(yuv)
# Convert BGR to YCrCb (YCrCb apply YCrCb JPEG (or YCC), "full range",
# where Y range is [0, 255], and U, V range is [0, 255] (this is the default JPEG format color space format).
yvu = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
y, v, u = cv2.split(yvu)
# Downsample U and V (apply 420 format).
u = cv2.resize(u, (u.shape[1]//2, u.shape[0]//2))
v = cv2.resize(v, (v.shape[1]//2, v.shape[0]//2))
# Open In-memory bytes streams (instead of using fifo)
f = io.BytesIO()
# Write Y, U and V to the "streams".
f.write(y.tobytes())
f.write(u.tobytes())
f.write(v.tobytes())
f.seek(0)
###############################################################################
# Read YUV420 (I420 planar format) and convert to BGR
###############################################################################
data = f.read(y.size*3//2) # Read one frame (number of bytes is width*height*1.5).
# Reshape data to numpy array with height*1.5 rows
yuv_data = np.frombuffer(data, np.uint8).reshape(y.shape[0]*3//2, y.shape[1])
# Convert YUV to BGR
bgr = cv2.cvtColor(yuv_data, cv2.COLOR_YUV2BGR_I420);
# How to How should I be placing the u and v channel information in all_yuv_data?
# -------------------------------------------------------------------------------
# Example: place the channels one after the other (for a single frame)
f.seek(0)
y0 = f.read(y.size)
u0 = f.read(y.size//4)
v0 = f.read(y.size//4)
yuv_data = y0 + u0 + v0
yuv_data = np.frombuffer(yuv_data, np.uint8).reshape(y.shape[0]*3//2, y.shape[1])
bgr = cv2.cvtColor(yuv_data, cv2.COLOR_YUV2BGR_I420);
###############################################################################
# Display result:
cv2.imshow("bgr incorrect colors", bgr)
###############################################################################
f.seek(0)
y = np.frombuffer(f.read(y.size), dtype=np.uint8).reshape((y.shape[0], y.shape[1])) # Read Y color channel and reshape to height x width numpy array
u = np.frombuffer(f.read(y.size//4), dtype=np.uint8).reshape((y.shape[0]//2, y.shape[1]//2)) # Read U color channel and reshape to height x width numpy array
v = np.frombuffer(f.read(y.size//4), dtype=np.uint8).reshape((y.shape[0]//2, y.shape[1]//2)) # Read V color channel and reshape to height x width numpy array
# Resize u and v color channels to be the same size as y
u = cv2.resize(u, (y.shape[1], y.shape[0]))
v = cv2.resize(v, (y.shape[1], y.shape[0]))
yvu = cv2.merge((y, v, u)) # Stack planes to 3D matrix (use Y,V,U ordering)
bgr = cv2.cvtColor(yvu, cv2.COLOR_YCrCb2BGR)
###############################################################################
# Display result:
cv2.imshow("bgr", bgr)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果:
如果您提供了YUV频道的组合屏幕抓图,那么如果您提供了三个实际独立的PNG图像,而不是一个屏幕抓图,这将更加有用。@MarkSetchell感谢您的反馈,我已经用实际图像替换了截图,我已经添加了我认为是预期结果的内容-如果我错了,请告诉我,我会将我的编辑内容反转回来。谢谢。@MarkSetchell是的,这是我期待的结果,谢谢你添加它。你是如何获得它的?我比OpenCV更快,使用ImageMagick:-)而且我喜欢做点什么。谢谢你这么说。我的直觉告诉我OP在这里做了太多的工作。我更新了我的帖子,color\u YCrCb2BGR
的颜色格式是你可以通过做all\u yuv\u data[fullsize[0]:fullsize[0]+half size[0],:]=yuv\u data[1]来让它更容易阅读。对最后一个频道进行整形(-1,fullsize[1])
和类似操作。您真正关心的是底层线性缓冲区的顺序。正如您可能看到的,颜色不准确。颜色格式太多了…@Rotem哦,嘿,你说得对。我甚至没有注意到我的颜色不好。谢谢你指出这一点。@Mad物理学家好的,谢谢你。我想你的意思是all_yuv_data[fullsize[0]:fullsize[0]+half size[0]//2,:]=yuv_data[1]。重塑(-1,fullsize[1])
是的,你是对的。我脑后的想法是实际使用缓冲区中的视图,如small=all_yuv_data[fullsize[0]:,:]。重塑(2,-1,halfsize[1]);小[0]=yuv_数据[1];小[1]=yuv_数据[2]
。这就是numpy视图的神奇之处,当您不处理连续性时。如果您担心在创建副本的过程中会出现重塑,可以通过在同一缓冲区上创建具有适当偏移量和步长的新阵列来显式创建视图