最近在研究影像格式,拿到許多 NV21 格式的檔案,原本是利用網路上的工具將影像轉成 RGB 存下來。突然間土炮的念頭又冒出來了,不如自已來實作一下轉換的工具好了。
先研究一下 NV21 的格式:
Wikipedia 上有提到:https://en.wikipedia.org/wiki/YUV#Y%E2%80%B2UV420sp_(NV21)_to_RGB_conversion_(Android)
NV21 是
Android 上的標準影像格式,其格式就是 8bit 的 YUV420
Wiki上還貼心的附了 YUV 轉 RGB 的公式
公式有了,接下來就是弄清楚資料格式
參考 Microsoft 的資料:https://docs.microsoft.com/en-us/previous-versions/aa904813(v=vs.80)?redirectedfrom=MSDN
原來 YUV 有多種取樣方式,主要可以分成 YUV444, YUV422, 以及
YUV420 三大類
原來 YUV 是和彩色電視和黑白電視有關。黑白電視只需要 Y 的影像,到了彩色電視時,為了解決相容問題,所以再加上了彩度的 UV,再加上頻寬問題以及人眼的敏感度,所以出現不同的取樣頻率。
底下是實際的例子來解釋 影像,RGB, YUV444, YUV422,
YUV420 彼此間的關係
以一張影像來說,會先切成以像素為單位 (Image -> Pixel)
數字為寬,英文字為高。這張影像為 16x16 的像素點
若每個像素點是一個 RGB 的資料格式
若 RGB 是以 byte 為單位,則這張影像為
16x16x3 的大小 (寬 x 高 x 3 (R,G,B 各為 1
byte))
若影像以 YUV444 來表示
此時影像會以 Y 和 UV 的大小存在,Y =
16x16x1 , UV = 16x16x2,所以會和 RGB 一樣大
若影像以 YUV422 來表示
由於 UV 的資料是每二個像素取樣一點,所以 UV 的大小會成為 16x8x2
若影像以 YUV420 來表示
此時 UV 會是四個像素點取樣一次,因此 UV 的大小成為 8x8x2。所以 YUV420 的影像大小變成 RGB 的 1/2
在了解資料格式後,就可以來實做轉換工具了。
實做會分成幾個部份:
- 將 Y, U, V 從原本的資料拆出來成為獨立的區塊
- 將 Y, U, V 利用 YUV 轉 RGB 的公式,換算成 RGB 的資料
- 將 R, G, B 組成一顆像素點
在實做過程中每個像素點都要進行 YUV 轉 RGB 的計算,再加上用 python
硬刻,所以速度上實在是慢…….XD
所以,後來還是用 OpenCV 裡的 YUV 轉 RGB的功能,雖然可以直接轉,但需要先將 YUV420 擴展成 YUV444,並且排列成每一像素點為 YUV 的格式。雖然有點偷吃步,不過,還是把 NV21 這格式以及資料排列的方式給弄清楚了。
底下是實作的 Python 程式
import sys
import cv2
import numpy as np
def YUVtoRGB(byteArray, width, height):
size_y = width * height
image_y = byteArray[0:size_y]
image_y = np.reshape(image_y, (height, width))
image_y = image_y.astype(np.uint8)
start_uv = size_y
image_v = byteArray[start_uv::2]
image_v = np.repeat(image_v, 2, 0)
image_v = np.reshape(image_v, (int(height/2), width))
image_v = np.repeat(image_v, 2, 0)
image_v = image_v.astype(np.uint8)
image_u = byteArray[start_uv+1::2]
image_u = np.repeat(image_u, 2, 0)
image_u = np.reshape(image_u, (int(height/2), width))
image_u = np.repeat(image_u, 2, 0)
image_u = image_u.astype(np.uint8)
RGBMatrix = (np.dstack([image_y, image_u, image_v])).astype(np.uint8)
RGBMatrix = cv2.cvtColor(RGBMatrix, cv2.COLOR_YUV2RGB, 3)
return RGBMatrix
def main():
file_in, image_size_w, image_size_h, crop_x, crop_y, crop_w, crop_h = argv
str_file_in = str(file_in)
str_data = str_file_in[:-4]
file_out = str_data + "bmp"
print("Input file [%s] \n ==> out [%s]"% (str_file_in, file_out))
str_data = str_data.split('_')
int_image_size_w = int(image_size_w)
int_image_size_h = int(image_size_h)
int_crop_x = int(crop_x)
int_crop_y = int(crop_y)
int_crop_w = int(crop_w)
int_crop_h = int(crop_h)
with open(str_file_in, "rb") as f:
input_data = f.read()
f.close()
file_size = len(input_data)
print("Read [%s]-[%d]"% (file_in, file_size))
data_array = np.frombuffer(input_data, dtype=np.uint8)
image_rgb = YUVtoRGB(data_array, int_image_size_w, int_image_size_h)
cv2.imwrite(file_out, image_resize)
if __name__ == "__main__":
main(sys.argv[1:])
沒有留言:
張貼留言