NV12转YUV444加速
NV12 to YUV444 speed up
我有一个代码可以将图像从 nv12 转换为 yuv444
for h in range(self.img_shape[0]):
# centralize yuv 444 data for inference framework
for w in range(self.img_shape[1]):
yuv444_res[h][w][0] = (nv12_y_data[h * self.img_shape[1] +w]).astype(np.int8)
yuv444_res[h][w][1] = (nv12_u_data[int(h / 2) * int(self.img_shape[1] / 2) +int(w / 2)]).astype(np.int8)
yuv444_res[h][w][2] = (nv12_v_data[int(h / 2) * int(self.img_shape[1] / 2) +int(w / 2)]).astype(np.int8)
因为for循环在python中非常慢,比numpy慢很多。我想知道这个转换是否可以在 NumPy 计算中完成。
2021 年 6 月 15 日更新:
我能够从这个页面得到这段带有精美索引的代码 External Link:
yuv444 = np.empty([self.height, self.width, 3], dtype=np.uint8)
yuv444[:, :, 0] = nv12_data[:self.width * self.height].reshape(
self.height, self.width)
u = nv12_data[self.width * self.height::2].reshape(
self.height // 2, self.width // 2)
yuv444[:, :, 1] = Image.fromarray(u).resize((self.width, self.height))
v = nv12_data[self.width * self.height + 1::2].reshape(
self.height // 2, self.width // 2)
yuv444[:, :, 2] = Image.fromarray(v).resize((self.width, self.height))
data[0] = yuv444.astype(np.int8)
如果使用PIL替换已弃用的imresize,则代码匹配旧代码100%
2021 年 6 月 19 日更新:
仔细看了Rotem给出的答案,发现他的方法更快
#nv12_data is reshaped to one dimension
y = nv12_data[:self.width * self.height].reshape(
self.height, self.width)
shrunk_u = nv12_data[self.width * self.height::2].reshape(
self.height // 2, self.width // 2)
shrunk_v = nv12_data[self.width * self.height + 1::2].reshape(
self.height // 2, self.width // 2)
u = cv2.resize(shrunk_u, (self.width, self.height),
interpolation=cv2.INTER_NEAREST)
v = cv2.resize(shrunk_v, (self.width, self.height),
interpolation=cv2.INTER_NEAREST)
yuv444 = np.dstack((y, u, v))
另外,我做了处理1000张图片的时间对比。事实证明,cv 重塑更快并保证相同的结果。
cv time: 4.417593002319336, pil time: 5.395732164382935
2021 年 6 月 25 日更新:
Pillow resize 在不同版本中具有不同的默认重采样参数值。
5.1.0:
def resize(self, size, resample=NEAREST, box=None):
8.1.0:
def resize(self, size, resample=BICUBIC, box=None, reducing_gap=None):
指定使用的重采样策略是个好主意。
似乎是花式索引(高级索引)的主要案例。
像这样的东西应该可以解决问题,尽管我没有在实际图像上验证它。我在开始时添加了一个部分来重建图像,因为将阵列作为一个整体来处理比分解成多个部分更容易。很可能,您可以重构它并避免拆分它。
# reconstruct image array
y = nv12_y_data.reshape(self.image_shape[0], self.image_shape[1])
u = nv12_u_data.reshape(self.image_shape[0], self.image_shape[1])
v = nv12_v_data.reshape(self.image_shape[0], self.image_shape[1])
img = np.stack((y,u,v), axis=-1)
# take every index twice until half the range
idx_h = np.repeat(np.arange(img.shape[0] // 2), 2)[:, None]
idx_w = np.repeat(np.arange(img.shape[1] // 2), 2)[None, :]
# convert
yuv444 = np.empty_like(img, dtype=np.uint8)
yuv444[..., 0] = img[..., 0]
yuv444[..., 1] = img[idx_h, idx_w, 1]
yuv444[..., 2] = img[idx_h, idx_w, 2]
如果这是你的关键路径,并且你想梳理出更多的性能,你可以考虑先处理图像通道,这在现代 CPU 上会更快(但不是 GPU)。
您可以按相反的顺序使用我下面 中描述的过程(没有 RGB 部分)。
插图:
首先使用 FFmpeg(命令行工具)创建 NV12 格式的合成示例图像。
示例图片用于测试。
使用子进程模块从 Python 执行:
import subprocess as sp
import shlex
sp.run(shlex.split('ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=1 -vcodec rawvideo -pix_fmt nv12 nv12.yuv'))
sp.run(shlex.split('ffmpeg -y -f rawvideo -video_size 192x162 -pixel_format gray -i nv12.yuv -pix_fmt gray nv12_gray.png'))
读取示例图像,并执行您的 post 中的代码(用作参考):
import numpy as np
import cv2
nv12 = cv2.imread('nv12_gray.png', cv2.IMREAD_GRAYSCALE)
cols, rows = nv12.shape[1], nv12.shape[0]*2//3
# Reference implementation - using for-loops (the solution is in the part below):
################################################################################
nv12_y_data = nv12[0:rows, :].flatten()
nv12_u_data = nv12[rows:, 0::2].flatten()
nv12_v_data = nv12[rows:, 1::2].flatten()
yuv444_res = np.zeros((rows, cols, 3), np.uint8)
for h in range(rows):
# centralize yuv 444 data for inference framework
for w in range(cols):
yuv444_res[h][w][0] = (nv12_y_data[h * cols + w]).astype(np.int8)
yuv444_res[h][w][1] = (nv12_u_data[int(h / 2) * int(cols / 2) + int(w / 2)]).astype(np.int8)
yuv444_res[h][w][2] = (nv12_v_data[int(h / 2) * int(cols / 2) + int(w / 2)]).astype(np.int8)
################################################################################
我的建议的解决方案适用于以下阶段:
- 将 U 和 V 分成两个“一半大小”的矩阵
shrunk_u
和 shrunk_v
。
- 使用
cv2.resize
.
将 shrunk_u
和 shrunk_v
调整为完整图像大小矩阵
在我的代码示例中,我使用最近邻插值来获得与您的结果相同的结果。
建议换成线性插值,画质更好
- 使用
np.dstack
将 Y、U 和 V 合并为 YUV(3 个颜色通道)图像。
完整的代码示例如下:
import numpy as np
import subprocess as sp
import shlex
import cv2
sp.run(shlex.split('ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=1 -vcodec rawvideo -pix_fmt nv12 nv12.yuv'))
sp.run(shlex.split('ffmpeg -y -f rawvideo -video_size 192x162 -pixel_format gray -i nv12.yuv -pix_fmt gray nv12_gray.png'))
#sp.run(shlex.split('ffmpeg -y -f rawvideo -video_size 192x108 -pixel_format nv12 -i nv12.yuv -vcodec rawvideo -pix_fmt yuv444p yuv444.yuv'))
#sp.run(shlex.split('ffmpeg -y -f rawvideo -video_size 192x324 -pixel_format gray -i yuv444.yuv -pix_fmt gray yuv444_gray.png'))
#sp.run(shlex.split('ffmpeg -y -f rawvideo -video_size 192x108 -pixel_format yuv444p -i yuv444.yuv -pix_fmt rgb24 rgb.png'))
#sp.run(shlex.split('ffmpeg -y -f rawvideo -video_size 192x108 -pixel_format gbrp -i yuv444.yuv -filter_complex "extractplanes=g+b+r[g][b][r],[r][g][b]mergeplanes=0x001020:gbrp[v]" -map "[v]" -vcodec rawvideo -pix_fmt rgb24 yuvyuv.yuv'))
#sp.run(shlex.split('ffmpeg -y -f rawvideo -video#_size 576x108 -pixel_format gray -i yuvyuv.yuv -pix_fmt gray yuvyuv_gray.png'))
nv12 = cv2.imread('nv12_gray.png', cv2.IMREAD_GRAYSCALE)
cols, rows = nv12.shape[1], nv12.shape[0]*2//3
nv12_y_data = nv12[0:rows, :].flatten()
nv12_u_data = nv12[rows:, 0::2].flatten()
nv12_v_data = nv12[rows:, 1::2].flatten()
yuv444_res = np.zeros((rows, cols, 3), np.uint8)
for h in range(rows):
# centralize yuv 444 data for inference framework
for w in range(cols):
yuv444_res[h][w][0] = (nv12_y_data[h * cols + w]).astype(np.int8)
yuv444_res[h][w][1] = (nv12_u_data[int(h / 2) * int(cols / 2) + int(w / 2)]).astype(np.int8)
yuv444_res[h][w][2] = (nv12_v_data[int(h / 2) * int(cols / 2) + int(w / 2)]).astype(np.int8)
y = nv12[0:rows, :]
shrunk_u = nv12[rows:, 0::2].copy()
shrunk_v = nv12[rows:, 1::2].copy()
u = cv2.resize(shrunk_u, (cols, rows), interpolation=cv2.INTER_NEAREST) # Resize U channel (use NEAREST interpolation - fastest, but lowest quality).
v = cv2.resize(shrunk_v, (cols, rows), interpolation=cv2.INTER_NEAREST) # Resize V channel
yuv444 = np.dstack((y, u, v))
is_eqaul = np.all(yuv444 == yuv444_res)
print('is_eqaul = ' + str(is_eqaul)) # is_eqaul = True
# Convert to RGB for display
yvu = np.dstack((y, v, u)) # Use COLOR_YCrCb2BGR, because it's uses the corrected conversion coefficients.
rgb = cv2.cvtColor(yvu, cv2.COLOR_YCrCb2BGR)
# Show results:
cv2.imshow('nv12', nv12)
cv2.imshow('yuv444_res', yuv444_res)
cv2.imshow('yuv444', yuv444)
cv2.imshow('rgb', rgb)
cv2.waitKey()
cv2.destroyAllWindows()
输入(NV12显示为灰度):
输出(转换为 RGB 后):
我有一个代码可以将图像从 nv12 转换为 yuv444
for h in range(self.img_shape[0]):
# centralize yuv 444 data for inference framework
for w in range(self.img_shape[1]):
yuv444_res[h][w][0] = (nv12_y_data[h * self.img_shape[1] +w]).astype(np.int8)
yuv444_res[h][w][1] = (nv12_u_data[int(h / 2) * int(self.img_shape[1] / 2) +int(w / 2)]).astype(np.int8)
yuv444_res[h][w][2] = (nv12_v_data[int(h / 2) * int(self.img_shape[1] / 2) +int(w / 2)]).astype(np.int8)
因为for循环在python中非常慢,比numpy慢很多。我想知道这个转换是否可以在 NumPy 计算中完成。
2021 年 6 月 15 日更新:
我能够从这个页面得到这段带有精美索引的代码 External Link:
yuv444 = np.empty([self.height, self.width, 3], dtype=np.uint8)
yuv444[:, :, 0] = nv12_data[:self.width * self.height].reshape(
self.height, self.width)
u = nv12_data[self.width * self.height::2].reshape(
self.height // 2, self.width // 2)
yuv444[:, :, 1] = Image.fromarray(u).resize((self.width, self.height))
v = nv12_data[self.width * self.height + 1::2].reshape(
self.height // 2, self.width // 2)
yuv444[:, :, 2] = Image.fromarray(v).resize((self.width, self.height))
data[0] = yuv444.astype(np.int8)
如果使用PIL替换已弃用的imresize,则代码匹配旧代码100%
2021 年 6 月 19 日更新:
仔细看了Rotem给出的答案,发现他的方法更快
#nv12_data is reshaped to one dimension
y = nv12_data[:self.width * self.height].reshape(
self.height, self.width)
shrunk_u = nv12_data[self.width * self.height::2].reshape(
self.height // 2, self.width // 2)
shrunk_v = nv12_data[self.width * self.height + 1::2].reshape(
self.height // 2, self.width // 2)
u = cv2.resize(shrunk_u, (self.width, self.height),
interpolation=cv2.INTER_NEAREST)
v = cv2.resize(shrunk_v, (self.width, self.height),
interpolation=cv2.INTER_NEAREST)
yuv444 = np.dstack((y, u, v))
另外,我做了处理1000张图片的时间对比。事实证明,cv 重塑更快并保证相同的结果。
cv time: 4.417593002319336, pil time: 5.395732164382935
2021 年 6 月 25 日更新:
Pillow resize 在不同版本中具有不同的默认重采样参数值。
5.1.0:
def resize(self, size, resample=NEAREST, box=None):
8.1.0:
def resize(self, size, resample=BICUBIC, box=None, reducing_gap=None):
指定使用的重采样策略是个好主意。
似乎是花式索引(高级索引)的主要案例。
像这样的东西应该可以解决问题,尽管我没有在实际图像上验证它。我在开始时添加了一个部分来重建图像,因为将阵列作为一个整体来处理比分解成多个部分更容易。很可能,您可以重构它并避免拆分它。
# reconstruct image array
y = nv12_y_data.reshape(self.image_shape[0], self.image_shape[1])
u = nv12_u_data.reshape(self.image_shape[0], self.image_shape[1])
v = nv12_v_data.reshape(self.image_shape[0], self.image_shape[1])
img = np.stack((y,u,v), axis=-1)
# take every index twice until half the range
idx_h = np.repeat(np.arange(img.shape[0] // 2), 2)[:, None]
idx_w = np.repeat(np.arange(img.shape[1] // 2), 2)[None, :]
# convert
yuv444 = np.empty_like(img, dtype=np.uint8)
yuv444[..., 0] = img[..., 0]
yuv444[..., 1] = img[idx_h, idx_w, 1]
yuv444[..., 2] = img[idx_h, idx_w, 2]
如果这是你的关键路径,并且你想梳理出更多的性能,你可以考虑先处理图像通道,这在现代 CPU 上会更快(但不是 GPU)。
您可以按相反的顺序使用我下面
插图:
首先使用 FFmpeg(命令行工具)创建 NV12 格式的合成示例图像。
示例图片用于测试。
使用子进程模块从 Python 执行:
import subprocess as sp
import shlex
sp.run(shlex.split('ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=1 -vcodec rawvideo -pix_fmt nv12 nv12.yuv'))
sp.run(shlex.split('ffmpeg -y -f rawvideo -video_size 192x162 -pixel_format gray -i nv12.yuv -pix_fmt gray nv12_gray.png'))
读取示例图像,并执行您的 post 中的代码(用作参考):
import numpy as np
import cv2
nv12 = cv2.imread('nv12_gray.png', cv2.IMREAD_GRAYSCALE)
cols, rows = nv12.shape[1], nv12.shape[0]*2//3
# Reference implementation - using for-loops (the solution is in the part below):
################################################################################
nv12_y_data = nv12[0:rows, :].flatten()
nv12_u_data = nv12[rows:, 0::2].flatten()
nv12_v_data = nv12[rows:, 1::2].flatten()
yuv444_res = np.zeros((rows, cols, 3), np.uint8)
for h in range(rows):
# centralize yuv 444 data for inference framework
for w in range(cols):
yuv444_res[h][w][0] = (nv12_y_data[h * cols + w]).astype(np.int8)
yuv444_res[h][w][1] = (nv12_u_data[int(h / 2) * int(cols / 2) + int(w / 2)]).astype(np.int8)
yuv444_res[h][w][2] = (nv12_v_data[int(h / 2) * int(cols / 2) + int(w / 2)]).astype(np.int8)
################################################################################
我的建议的解决方案适用于以下阶段:
- 将 U 和 V 分成两个“一半大小”的矩阵
shrunk_u
和shrunk_v
。 - 使用
cv2.resize
.
将shrunk_u
和shrunk_v
调整为完整图像大小矩阵 在我的代码示例中,我使用最近邻插值来获得与您的结果相同的结果。
建议换成线性插值,画质更好 - 使用
np.dstack
将 Y、U 和 V 合并为 YUV(3 个颜色通道)图像。
完整的代码示例如下:
import numpy as np
import subprocess as sp
import shlex
import cv2
sp.run(shlex.split('ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=1 -vcodec rawvideo -pix_fmt nv12 nv12.yuv'))
sp.run(shlex.split('ffmpeg -y -f rawvideo -video_size 192x162 -pixel_format gray -i nv12.yuv -pix_fmt gray nv12_gray.png'))
#sp.run(shlex.split('ffmpeg -y -f rawvideo -video_size 192x108 -pixel_format nv12 -i nv12.yuv -vcodec rawvideo -pix_fmt yuv444p yuv444.yuv'))
#sp.run(shlex.split('ffmpeg -y -f rawvideo -video_size 192x324 -pixel_format gray -i yuv444.yuv -pix_fmt gray yuv444_gray.png'))
#sp.run(shlex.split('ffmpeg -y -f rawvideo -video_size 192x108 -pixel_format yuv444p -i yuv444.yuv -pix_fmt rgb24 rgb.png'))
#sp.run(shlex.split('ffmpeg -y -f rawvideo -video_size 192x108 -pixel_format gbrp -i yuv444.yuv -filter_complex "extractplanes=g+b+r[g][b][r],[r][g][b]mergeplanes=0x001020:gbrp[v]" -map "[v]" -vcodec rawvideo -pix_fmt rgb24 yuvyuv.yuv'))
#sp.run(shlex.split('ffmpeg -y -f rawvideo -video#_size 576x108 -pixel_format gray -i yuvyuv.yuv -pix_fmt gray yuvyuv_gray.png'))
nv12 = cv2.imread('nv12_gray.png', cv2.IMREAD_GRAYSCALE)
cols, rows = nv12.shape[1], nv12.shape[0]*2//3
nv12_y_data = nv12[0:rows, :].flatten()
nv12_u_data = nv12[rows:, 0::2].flatten()
nv12_v_data = nv12[rows:, 1::2].flatten()
yuv444_res = np.zeros((rows, cols, 3), np.uint8)
for h in range(rows):
# centralize yuv 444 data for inference framework
for w in range(cols):
yuv444_res[h][w][0] = (nv12_y_data[h * cols + w]).astype(np.int8)
yuv444_res[h][w][1] = (nv12_u_data[int(h / 2) * int(cols / 2) + int(w / 2)]).astype(np.int8)
yuv444_res[h][w][2] = (nv12_v_data[int(h / 2) * int(cols / 2) + int(w / 2)]).astype(np.int8)
y = nv12[0:rows, :]
shrunk_u = nv12[rows:, 0::2].copy()
shrunk_v = nv12[rows:, 1::2].copy()
u = cv2.resize(shrunk_u, (cols, rows), interpolation=cv2.INTER_NEAREST) # Resize U channel (use NEAREST interpolation - fastest, but lowest quality).
v = cv2.resize(shrunk_v, (cols, rows), interpolation=cv2.INTER_NEAREST) # Resize V channel
yuv444 = np.dstack((y, u, v))
is_eqaul = np.all(yuv444 == yuv444_res)
print('is_eqaul = ' + str(is_eqaul)) # is_eqaul = True
# Convert to RGB for display
yvu = np.dstack((y, v, u)) # Use COLOR_YCrCb2BGR, because it's uses the corrected conversion coefficients.
rgb = cv2.cvtColor(yvu, cv2.COLOR_YCrCb2BGR)
# Show results:
cv2.imshow('nv12', nv12)
cv2.imshow('yuv444_res', yuv444_res)
cv2.imshow('yuv444', yuv444)
cv2.imshow('rgb', rgb)
cv2.waitKey()
cv2.destroyAllWindows()
输入(NV12显示为灰度):
输出(转换为 RGB 后):