搜尋此網誌

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:])


沒有留言:

張貼留言