搜尋此網誌

2020年4月4日 星期六

[影像處理] 利用 Python 搭配 OpenCV 將 NV21 (YUV420) 的影像轉成 RGB 格式


最近在研究影像格式,拿到許多 NV21 格式的檔案,原本是利用網路上的工具將影像轉成 RGB 存下來。突然間土炮的念頭又冒出來了,不如自已來實作一下轉換的工具好了。


先研究一下 NV21 的格式:

NV21 Android 上的標準影像格式,其格式就是 8bit YUV420
Wiki上還貼心的附了 YUV RGB 的公式

公式有了,接下來就是弄清楚資料格式


原來 YUV 有多種取樣方式,主要可以分成 YUV444, YUV422, 以及 YUV420 三大類




看了一下 wikipedia YUV 的歷史https://zh.wikipedia.org/wiki/YUV
原來 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

在了解資料格式後,就可以來實做轉換工具了。
實做會分成幾個部份:
  1. Y, U, V 從原本的資料拆出來成為獨立的區塊
  2. Y, U, V 利用 YUV RGB 的公式,換算成 RGB 的資料
  3. 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:])


沒有留言:

張貼留言