Search code examples
pythonyuv

python RGB Image to YUYV


I want to convert Bitmap to YUV422 (YUYV) format. I've google about the YUYV format and tried to write this code.

path = "C:/Users/hogan/Desktop/red.bmp"
image = Image.open(path).convert("YCbCr")  # YUV
image = np.asarray(image)
width, height, YUYV = image.shape[1], image.shape[0], 4
array = np.empty((height * width * 3), dtype="uint8")
Y, U, V = 0, 1, 2
count = 0
for i in range(len(image)):
    for j in range(len(image[i])):
        for k in range(len(image[i][j])):
            if (count % 4 == 0):
                array[count] = image[i][j][Y]
            if (count % 4 == 1):
                array[count] = image[i][j][U]
            if (count % 4 == 2):
                array[count] = image[i][j][Y]
            if (count % 4 == 3):
                array[count] = image[i][j][V]
            count = count + 1
array.astype('uint8').tofile("C:/Users/hogan/Desktop/tmpdir/1.raw")

I read this image and know my code is wrong but no idea how to make it right. enter image description here For Example : Red color (255,0,0) in YUV is (76,84,255), if I've a lot of pixels, I don't know which 'U' an 'V' should be dropped.

If use my code to convert a 480*640 (W*H), it will be 960*480.


Solution

  • You can use numpy advanced indexing and broadcasting here.

    Suppose, you have an image:

    ycbcr = np.array([
        [['Y00', 'U00', 'V00'], ['Y01', 'U01', 'V01'], ['Y02', 'U02', 'V02']],
        [['Y10', 'U10', 'V10'], ['Y11', 'U11', 'V11'], ['Y12', 'U12', 'V12']],
        [['Y20', 'U20', 'V20'], ['Y21', 'U21', 'V21'], ['Y22', 'U22', 'V22']],
    ], dtype=str)
    

    Working with 3D array is going to be unwieldy, so let's convert it to 1D:

    ycbcr = ycbcr.reshape(27)
    

    Then, I'll allocate an array for YUYV stream:

    yuyv = np.array(['   ' for _ in range(18)])  # '   ' is basically because
    # I use strings in this example. You'd probably want to use arrays of uint8
    

    First step is the easiest - we take every third value of ycbcr (Y components) and place them on even positions of yuyv:

    yuyv[::2] = ycbcr[::3]
    

    Then we proceed with other bytes. U00 should go from position 1 to position 1, V00 from 2 to 3, U01 and V01 are omitted, U02 goes from 7 to 5, V02 goes from 8 to 7, U03 is omitted and so on:

    yuyv[1::4] = ycbcr[1::6]  # Moving U, every sixth byte starting from position 1
    yuyv[3::4] = ycbcr[2::6][:-1]  # Moving V, dropping last element of V
    

    So you get the following yuyv, just like an image suggested:

    array(['Y00', 'U00', 'Y01', 'V00', 'Y02', 'U02', 'Y10', 'V02', 'Y11',
           'U11', 'Y12', 'V11', 'Y20', 'U20', 'Y21', 'V20', 'Y22', 'U22'],
    dtype='<U3')
    

    If you want to follow @alkanen advice, the approach is feasible as well, you'll just need to sample U and V bytes in two arrays and take an average. Perhaps it is going to look like this: (untested)

    u_even = ycbcr[1::6]
    u_odd = ycbcr[4::6]
    u = (u_even + u_odd) / 2
    yuyv[1::4] = u
    

    More complex border cases will encounter as well.