当前位置:网站首页>YUV444、YUV422、YUV420、YUV420P、YUV420SP、YV12、YU12、NV12、NV21

YUV444、YUV422、YUV420、YUV420P、YUV420SP、YV12、YU12、NV12、NV21

2022-06-25 23:38:00 android_ cai_ niao

Preface

Various YUV There are so many formats , It's really hard to learn at first , The article about online search is not very clear .

All kinds of different YUV The format is just different from the sampling method and storage method , Just these two points , Different sampling methods are used to save memory , I don't know the usefulness of different storage methods .

RGB turn YUV444

Let's assume that there is a sheet with a width of 4 Pixels , High for 2 Pixel image , Then this picture has 8 Pixel , I use a grid to represent a pixel ,8 One pixel is 8 Lattice , Here's the picture :
 Insert picture description here
Pictured above , share 8 A grid represents 8 Pixel ,0 ~ 7 Represents the position of the pixel . In fact, a pixel is very small , A pixel on a computer is almost invisible to the naked eye , So I use a large grid to represent a pixel , Easy to understand . The pixels in the computer are made of RGB Composed of three primary colors , Here I put RGB Label in pixels , as follows :
 Insert picture description here
Every R use 1 Bytes to store ,G use 1 Byte store ,B Also used 1 Byte store , therefore 1 Of pixels RGB need 3 Bytes to store , And in the above figure 8 Pixel , There is a 8 Yes RGB value , need 24 Bytes to store .

RGB It can be converted into by mathematical formula YUV, As for the principle of transformation, we don't need to understand , As long as you know that you can convert each other through formulas , Put up the picture above. RGB Convert to YUV Format , as follows :
 Insert picture description here
Y need 1 Bytes to store ,U need 1 Bytes to store ,V Also needed 1 Bytes to store , therefore 8 A pixel YUV Pictures are also needed 24 Bytes to store , and RGB equally . It feels like YUV No advantage , Don't worry. , Then you will know the benefits .

YV12 Collection method of (YUV444 * YV12)

front RGB Converted YUV The data is called YUV444, In this format YUV The sampling is complete , No loss of precision . And what we got from the camera YUV Image data can never be YUV444 Format , But something else YUV Format , Are those formats that have lost precision , such as YV12, stay Android In the system , You can set the data collected by the camera to come out YV12 Format ( perhaps NV21 Format ),YV12 Is a loss of precision YUV Format , When it is sampling ,Y There will be no loss , While collecting U when , Pick one every other , And pick one line after another , collection V It's also a matter of picking one at a time , Pick one line after another , Suppose a picture is... High 6 Pixels (6 That's ok ), be YV12 The format collection method is :

first line :Y All collect , collection U, Collect one every other , Don't collect V
The second line :Y All collect , collection V, Collect one every other , Don't collect U
The third line :Y All collect , collection U, Collect one every other , Don't collect V
In the fourth row :Y All collect , collection V, Collect one every other , Don't collect U
The fifth row :Y All collect , collection U, Collect one every other , Don't collect V
Sixth elements :Y All collect , collection V, Collect one every other , Don't collect U

It should be easy to understand ,U and V Lost , Therefore, the storage space required will be reduced ,YV12 comparison YUV444 Save half the storage space , That's why the data from the camera is YUV Format not RGB Format reason , Because the storage space required is small , And although some are missing U and V, But the quality of the image is almost no decline with the naked eye .

according to YV12 Format sampling method , We put the previous YUV444 The image sample of is YV12 Format , as follows :

first line :Y All collect , collection U, Collect one every other , Don't collect V, as follows :
 Insert picture description here
The second line :Y All collect , collection V, Collect one every other , Don't collect U, as follows :
 Insert picture description here
The two lines come together , as follows :
 Insert picture description here
As can be seen from the figure above ,YUV444 Every line of the has 12 individual YUV related data , need 12 Bytes to store , Acquisition component YV12 after , Each line is just 6 individual YUV The relevant data , It only needs 6 Bytes can be stored , It saves half of the storage space , This is why the data collected by the camera is generally YUV Format reasons .

YV12 Storage mode

YV12 The storage mode of is : hold Y、U、V Keep separately , Pre deposit Y, Save again V, Save again U, as follows :
 Insert picture description here
YV12 In this way Y、U、V Are stored separately , The technical term is divided into 3 Planes , It seems that you need to use 3 An array to store , In fact, the pictures collected by the camera are transmitted to us YUV Data is a one-dimensional array , It's not two-dimensional , as follows :
 Insert picture description here
therefore , Let's not think YV12 The three planes are divided into 3 An array to store , The camera came out YV12 It's a one-dimensional array , When saving the file, you can directly save the one-dimensional array . Yes, of course , In the code , To facilitate the operation of this YV12 data , You can convert a one-dimensional array to save it separately Y、U、V Three arrays of .

Here we are. , Let's take a look at another YUV Format :YU12( Also called I420), It and YV12 It's like , Just when it's stored YU12 First deposit U Save again V, as follows :
 Insert picture description here

YV12 Reduction (YV12 * YUV444)

adopt YV12 Some of the sampled data is missing U and V Of , How to restore it ? as follows :
 Insert picture description here
As can be seen above ,YV12 The restore method of is : In every two lines , Every time 4 Next to each other Y Share a set UV( That is... In the red box 4 individual Y Share a set UV, In the blue box 4 individual Y Share a set UV), When again YUV Convert back to RGB when , And the original RGB It must be different , But in practical effect , We can hardly see the difference with our naked eyes ( Only one color can tell the difference , But if you look at a whole picture, you can't see the difference ).

According to this YV12 Restore back YUV444 Principle , You can know , The width and height of the image should be even , So when we do the exercises , When setting the width and height of the image, do not make a singular width and height , So as to avoid any abnormality ! We are seeing that the resolution settings of some mobile phones or cameras are even , There is no singular .

Through code simulation YUV444 * YV12 * YUV444

1、 The illustration

 Insert picture description here

As shown in the figure above , Next, we will simulate through code YUV444 To YV12 Data collection and storage process , Then restore back YUV444. For the convenience of later operation YUV Convenience of data ,YV12 We use three arrays to store the data of , Instead of using an array to hold .

2、 Analog out YUV444 data

 Insert picture description here
Simulate the above figure through code YUV444 data , as follows :

fun main() {
    
    val yuv444Bytes = arrayOf(
        arrayOf("Y0", "U0", "V0", "Y1", "U1", "V1", "Y2", "U2", "V2", "Y3", "U3", "V3"),
        arrayOf("Y4", "U4", "V4", "Y5", "U5", "V5", "Y6", "U6", "V6", "Y7", "U7", "V7")
    )
    printYUV444(yuv444Bytes)
}

fun printYUV444(yuv444Bytes: Array<Array<String>>) {
    
    println(" Next output YUV444 data ")
    for (oneLine in yuv444Bytes) {
    
        for (columnIndex in oneLine.indices step 3) {
    
            val y = oneLine[columnIndex + 0]
            val u = oneLine[columnIndex + 1]
            val v = oneLine[columnIndex + 2]
            print("$y $u $v | ")
        }
        println()
    }
}

The operation results are as follows :

 Next output YUV444 data 
Y0 U0 V0 | Y1 U1 V1 | Y2 U2 V2 | Y3 U3 V3 | 
Y4 U4 V4 | Y5 U5 V5 | Y6 U6 V6 | Y7 U7 V7 | 

Here we use a two-dimensional string array to represent a sheet with a width of 4, High for 2 The picture of YUV444 data , Use strings to simulate YUV The data is for the convenience of viewing the results . The next step is to implement the from YUV444 Collected from YV12 data .

3、 from YUV444 Collected from YV12 data

 Insert picture description here
Pictured above , We need to YUV444 From the data collected YV12 The data of , The code is as follows :

fun main() {
    
    val yuv444Bytes = arrayOf(
        arrayOf("Y0", "U0", "V0", "Y1", "U1", "V1", "Y2", "U2", "V2", "Y3", "U3", "V3"),
        arrayOf("Y4", "U4", "V4", "Y5", "U5", "V5", "Y6", "U6", "V6", "Y7", "U7", "V7")
    )
    printYUV444(yuv444Bytes)

    val (yBytes, uBytes, vBytes) = yuv444ToYv12(yuv444Bytes)
    printYV12(yBytes, uBytes, vBytes)
}

fun printYUV444(yuv444Bytes: Array<Array<String>>) {
    
    println(" Next output YUV444 data ")
    for (oneLine in yuv444Bytes) {
    
        for (columnIndex in oneLine.indices step 3) {
    
            val y = oneLine[columnIndex + 0]
            val u = oneLine[columnIndex + 1]
            val v = oneLine[columnIndex + 2]
            print("$y $u $v | ")
        }
        println()
    }
}

fun printYV12(yBytes: Array<String>, uBytes: Array<String>, vBytes: Array<String>) {
    
    println(" Next output YV12 data ")
    yBytes.forEach {
     print("$it ") }
    println()
    vBytes.forEach {
     print("$it ") }
    println()
    uBytes.forEach {
     print("$it ") }
    println()
}

private fun yuv444ToYv12(yuv444Bytes: Array<Array<String>>): Triple<Array<String>, Array<String>, Array<String>> {
    
    val width = yuv444Bytes[0].size / 3 //  notes : because yuv444 One pixel of the format is 3 Bytes , So divide by 3
    val height = yuv444Bytes.size
    val ySize = width * height
    val vSize = ySize / 4
    val yBytes = Array(ySize) {
     "" }
    val uBytes = Array(vSize) {
     "" }
    val vBytes = Array(vSize) {
     "" }
    var yIndex = 0
    var uIndex = 0
    var vIndex = 0
    var saveU = true
    var saveV = true
    for (rowIndex in 0 until height) {
    
        val oneLine = yuv444Bytes[rowIndex]
        for (columnIndex in oneLine.indices step 3) {
    
            val y = oneLine[columnIndex + 0]
            val u = oneLine[columnIndex + 1]
            val v = oneLine[columnIndex + 2]
            yBytes[yIndex++] = y
            if (rowIndex % 2 == 0) {
    
                //  Even lines take U, Take one from another 
                if (saveU) {
    
                    uBytes[uIndex++] = u
                }
                saveU = !saveU
            } else {
    
                //  The singular row is taken as V, Take one from another 
                if (saveV) {
    
                    vBytes[vIndex++] = v
                }
                saveV = !saveV
            }
        }
    }
    return Triple(yBytes, uBytes, vBytes)
}

The operation effect is as follows :

 Next output YUV444 data 
Y0 U0 V0 | Y1 U1 V1 | Y2 U2 V2 | Y3 U3 V3 | 
Y4 U4 V4 | Y5 U5 V5 | Y6 U6 V6 | Y7 U7 V7 | 
 Next output YV12 data 
Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7                 
V4 V6     
U0 U2 

4、 hold YV12 Revert to YUV444

 Insert picture description here
The implementation code is as follows :

fun main() {
    
    val yuv444Bytes = arrayOf(
        arrayOf("Y0", "U0", "V0", "Y1", "U1", "V1", "Y2", "U2", "V2", "Y3", "U3", "V3"),
        arrayOf("Y4", "U4", "V4", "Y5", "U5", "V5", "Y6", "U6", "V6", "Y7", "U7", "V7")
    )
    printYUV444(yuv444Bytes)

    val (yBytes, uBytes, vBytes) = yuv444ToYv12(yuv444Bytes)
    printYV12(yBytes, uBytes, vBytes)

    val width = yuv444Bytes[0].size
    val height = yuv444Bytes.size
    val yuv444 = yv12ToYuv444(width, height, yBytes, uBytes, vBytes)
    printYUV444(yuv444)
}

fun yv12ToYuv444(width: Int, height: Int, yBytes: Array<String>, uBytes: Array<String>, vBytes: Array<String>): Array<Array<String>> {
    
    var yIndex = 0
    val yuv444Bytes = Array(height) {
     Array(width) {
     " " } }
    val oneLineUvSize = width / 2 //  stay YV12 in , In a row U or V The number of 
    var twoLineIndex = -1  //  Count every two lines 
    for (rowIndex in 0 until height) {
    
        val oneLineBytes = yuv444Bytes[rowIndex]
        var u = ""
        var v = ""

        //  Because it reads every two lines UV The starting position is the same , So we'll just put twoLineIndex Just add 
        if (rowIndex % 2 == 0) {
    
            twoLineIndex++
        }

        //  Calculate each row in the fetch UV when uvIndex Starting position 
        var uvIndex = twoLineIndex * oneLineUvSize

        for (columnIndex in oneLineBytes.indices step 3) {
    
            if (yIndex % 2 == 0) {
    
                //  In a row , Every two Y Just once UV
                u = uBytes[uvIndex]
                v = vBytes[uvIndex]
                uvIndex++
            }

            val y = yBytes[yIndex++]
            oneLineBytes[columnIndex + 0] = y
            oneLineBytes[columnIndex + 1] = u
            oneLineBytes[columnIndex + 2] = v
        }
    }

    return yuv444Bytes
}

The operation results are as follows :

 Next output YUV444 data 
Y0 U0 V0 | Y1 U1 V1 | Y2 U2 V2 | Y3 U3 V3 | 
Y4 U4 V4 | Y5 U5 V5 | Y6 U6 V6 | Y7 U7 V7 | 
 Next output YV12 data 
Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7                 
V4 V6     
U0 U2     
 Next output YUV444 data 
Y0 U0 V4 | Y1 U0 V4 | Y2 U2 V6 | Y3 U2 V6 | 
Y4 U0 V4 | Y5 U0 V4 | Y6 U2 V6 | Y7 U2 V6 | 

You can compare the results with the previous screenshot :
 Insert picture description here
You can see ,4 individual Y Share a set UV, In this case , Per pixel YUV The value is not original YUV The value of , So the color effect must be biased , But looking at the whole picture , The naked eye can hardly see the difference , As mentioned earlier , We need to be clear about this .

YUV444 To YV12 It's relatively simple , But put YV12 Restore back YUV444 The logic of is quite complicated , So here are some details :

As shown in the figure above , In a row , Every two Y Share a set UV, therefore , Every two Y Read only once UV that will do , Let's look at preservation Y The index of each element of the array , as follows :
 Insert picture description here
That is, every two Y Read it once UV, Find out from the above picture , Actually in y When the index of is an even number , That is to say 0、2、4、6 Read when , So the implementation code is as follows :

if (yIndex % 2 == 0) {
    
    u = uBytes[uvIndex]
    v = vBytes[uvIndex]
    uvIndex++
}

Every two Y Read once UV It is also relatively easy to implement , The harder part is the second line 、 The third line 、 On the fourth line UV How to get it ? This is really difficult , It is a logical problem , To find its regularity :

  1. The first line takes UV And the second line UV It's exactly the same , from 0 Start reading , So just let their uvIndex Just keep reading the first and second lines the same .
  2. The third line takes UV And the fourth line UV It's the same thing , And first 、 The difference between the two lines is ,uvIndex The starting position of is not from 0 Here we go .

therefore , The difficulty is how to find uvIndex The starting position of , To find out the rules , We need more data , Suppose the width is 8 Pixels , High for 6 Pixels , It has a total of 6 x 8 = 48 Pixels , There will be 48 individual Y, We know that every 4 individual Y Corresponding to one U and V, be 48 / 4 = 12, There will be 12 individual U and 12 individual V, The drawing analysis is as follows :
 Insert picture description here
Pictured above , It analyzes and calculates each row read UV The formula of the starting position of time , The corresponding implementation code is as follows :

...
fun yv12ToYuv444(...): Array<Array<String>> {
    
    ...
    val oneLineUvSize = width / 2 //  stay YV12 in , In a row U or V The number of 
    var twoLineIndex = -1  //  Count every two lines 
    for (rowIndex in 0 until height) {
    
        ...
        //  Because it reads every two lines UV The starting position is the same , So we'll just put twoLineIndex Just add 
        if (rowIndex % 2 == 0) {
    
            twoLineIndex++
        }

        //  Calculate each row before reading UV when uvIndex Starting position 
        var uvIndex = twoLineIndex * oneLineUvSize
        ...
    }

    return yuv444Bytes
}

5、 Complete code

fun main() {
    
    val yuv444Bytes = arrayOf(
        arrayOf("Y0", "U0", "V0", "Y1", "U1", "V1", "Y2", "U2", "V2", "Y3", "U3", "V3"),
        arrayOf("Y4", "U4", "V4", "Y5", "U5", "V5", "Y6", "U6", "V6", "Y7", "U7", "V7")
    )
    printYUV444(yuv444Bytes)

    val (yBytes, uBytes, vBytes) = yuv444ToYv12(yuv444Bytes)
    printYV12(yBytes, uBytes, vBytes)

    val width = yuv444Bytes[0].size
    val height = yuv444Bytes.size
    val yuv444 = yv12ToYuv444(width, height, yBytes, uBytes, vBytes)
    printYUV444(yuv444)
}

fun yv12ToYuv444(width: Int, height: Int, yBytes: Array<String>, uBytes: Array<String>, vBytes: Array<String>): Array<Array<String>> {
    
    var yIndex = 0
    val yuv444Bytes = Array(height) {
     Array(width) {
     " " } }
    val oneLineUvSize = width / 2 //  stay YV12 in , In a row U or V The number of 
    var twoLineIndex = -1  //  Count every two lines 
    for (rowIndex in 0 until height) {
    
        val oneLineBytes = yuv444Bytes[rowIndex]
        var u = ""
        var v = ""

        //  Because it reads every two lines UV The starting position is the same , So we'll just put twoLineIndex Just add 
        if (rowIndex % 2 == 0) {
    
            twoLineIndex++
        }

        //  Calculate each row in the fetch UV when uvIndex Starting position 
        var uvIndex = twoLineIndex * oneLineUvSize

        for (columnIndex in oneLineBytes.indices step 3) {
    
            if (yIndex % 2 == 0) {
    
                //  In a row , Every two Y Just once UV
                u = uBytes[uvIndex]
                v = vBytes[uvIndex]
                uvIndex++
            }

            val y = yBytes[yIndex++]
            oneLineBytes[columnIndex + 0] = y
            oneLineBytes[columnIndex + 1] = u
            oneLineBytes[columnIndex + 2] = v
        }
    }

    return yuv444Bytes
}

fun printYUV444(yuv444Bytes: Array<Array<String>>) {
    
    println(" Next output YUV444 data ")
    for (oneLine in yuv444Bytes) {
    
        for (columnIndex in oneLine.indices step 3) {
    
            val y = oneLine[columnIndex + 0]
            val u = oneLine[columnIndex + 1]
            val v = oneLine[columnIndex + 2]
            print("$y $u $v | ")
        }
        println()
    }
}

fun printYV12(yBytes: Array<String>, uBytes: Array<String>, vBytes: Array<String>) {
    
    println(" Next output YV12 data ")
    yBytes.forEach {
     print("$it ") }
    println()
    vBytes.forEach {
     print("$it ") }
    println()
    uBytes.forEach {
     print("$it ") }
    println()
}

fun yuv444ToYv12(yuv444Bytes: Array<Array<String>>): Triple<Array<String>, Array<String>, Array<String>> {
    
    val width = yuv444Bytes[0].size / 3
    val height = yuv444Bytes.size
    val ySize = width * height
    val vSize = ySize / 4
    val yBytes = Array(ySize) {
     "" }
    val uBytes = Array(vSize) {
     "" }
    val vBytes = Array(vSize) {
     "" }
    var yIndex = 0
    var uIndex = 0
    var vIndex = 0
    var saveU = true
    var saveV = true
    for (rowIndex in 0 until height) {
    
        val oneLine = yuv444Bytes[rowIndex]
        for (columnIndex in oneLine.indices step 3) {
    
            val y = oneLine[columnIndex + 0]
            val u = oneLine[columnIndex + 1]
            val v = oneLine[columnIndex + 2]
            yBytes[yIndex++] = y
            if (rowIndex % 2 == 0) {
    
                //  Even lines take U, Take one from another 
                if (saveU) {
    
                    uBytes[uIndex++] = u
                }
                saveU = !saveU
            } else {
    
                //  The singular row is taken as V, Take one from another 
                if (saveV) {
    
                    vBytes[vIndex++] = v
                }
                saveV = !saveV
            }
        }
    }
    return Triple(yBytes, uBytes, vBytes)
}

6、 Complete code ( hold String Replace with byte)

below , We put yuv444 turn YV12 and YV12 turn YUV444 The code of is modified to use byte, And wrapped in YuvUtil in , To facilitate reuse , Here's the thing to watch out for , Here is a row of data byte The array size is multiplied by the width 3 Of , because 1 Pixels need to be 3 Bytes to store , In turn, , When we pass a line byte When the array calculates the width , You need to divide the size of the array by 3.

object YuvUtil {
    

    fun yv12ToYuv444(width: Int, height: Int, yBytes: Array<Byte>, uBytes: Array<Byte>, vBytes: Array<Byte>): Array<ByteArray> {
    
        var yIndex = 0
        val yuv444Bytes = Array(height) {
     ByteArray(width * 3) } //  Because each pixel needs 3 Bytes , So this is multiplied by 3
        val oneLineUvSize = width / 2 //  stay YV12 in , In a row U or V The number of 
        var twoLineIndex = -1  //  Count every two lines 
        for (rowIndex in 0 until height) {
    
            val oneLineBytes = yuv444Bytes[rowIndex]
            var u: Byte = 0
            var v: Byte = 0

            //  Because it reads every two lines UV The starting position is the same , So we'll just put twoLineIndex Just add 
            if (rowIndex % 2 == 0) {
    
                twoLineIndex++
            }

            //  Calculate each row in the fetch UV when uvIndex Starting position 
            var uvIndex = twoLineIndex * oneLineUvSize

            for (columnIndex in oneLineBytes.indices step 3) {
    
                if (yIndex % 2 == 0) {
    
                    //  In a row , Every two Y Just once UV
                    u = uBytes[uvIndex]
                    v = vBytes[uvIndex]
                    uvIndex++
                }

                val y = yBytes[yIndex++]
                oneLineBytes[columnIndex + 0] = y
                oneLineBytes[columnIndex + 1] = u
                oneLineBytes[columnIndex + 2] = v
            }
        }

        return yuv444Bytes
    }

    private fun yuv444ToYv12(yuv444Bytes: Array<ByteArray>): Triple<ByteArray, ByteArray, ByteArray> {
    
        val width = yuv444Bytes[0].size / 3 //  Because each pixel accounts for 3 Bytes , So divide by 3
        val height = yuv444Bytes.size
        val ySize = width * height
        val vSize = ySize / 4
        val yBytes = ByteArray(ySize)
        val uBytes = ByteArray(vSize)
        val vBytes = ByteArray(vSize)
        var yIndex = 0
        var uIndex = 0
        var vIndex = 0
        var saveU = true
        var saveV = true
        for (rowIndex in 0 until height) {
    
            val oneLine = yuv444Bytes[rowIndex]
            for (columnIndex in oneLine.indices step 3) {
    
                val y = oneLine[columnIndex + 0]
                val u = oneLine[columnIndex + 1]
                val v = oneLine[columnIndex + 2]
                yBytes[yIndex++] = y
                if (rowIndex % 2 == 0) {
    
                    //  Even lines take U, Take one from another 
                    if (saveU) {
    
                        uBytes[uIndex++] = u
                    }
                    saveU = !saveU
                } else {
    
                    //  The singular row is taken as V, Take one from another 
                    if (saveV) {
    
                        vBytes[vIndex++] = v
                    }
                    saveV = !saveV
                }
            }
        }
        return Triple(yBytes, uBytes, vBytes)
    }

    fun printYUV444(yuv444Bytes: Array<ByteArray>) {
    
        println(" Next output YUV444 data ")
        for (oneLine in yuv444Bytes) {
    
            for (columnIndex in oneLine.indices step 3) {
    
                val y = oneLine[columnIndex + 0]
                val u = oneLine[columnIndex + 1]
                val v = oneLine[columnIndex + 2]
                print("$y $u $v | ")
            }
            println()
        }
    }

    fun printYV12(yBytes: ByteArray, uBytes: ByteArray, vBytes: ByteArray) {
    
        //  With 16 Print in hexadecimal 
        println(" Next output YV12 data ")
        println(" Next output Y data ")
        yBytes.forEach {
     print("${
      toHexString(byteToInt(it))} ") }
        println("\n Next output V data ")
        vBytes.forEach {
     print("${
      toHexString(byteToInt(it))} ") }
        println("\n Next output U data ")
        uBytes.forEach {
     print("${
      toHexString(byteToInt(it))} ") }
        println()
    }

    fun byteToInt(byte: Byte): Int = byte.toInt() shl 24 ushr 24
    fun toHexString(int: Int): String = Integer.toHexString(int)

}

YV12 Picture vs bmp Pictures interact with each other

bmp Refer to this document for relevant knowledge of pictures :https://blog.csdn.net/android_cai_niao/article/details/120528734

YV12 And bmp Transformation , To put it bluntly yuv and rgb Transformation , Find the conversion formula . There are many conversion formulas on the Internet , Which of these conversion formulas is reliable , I'm not sure , Because there are too many knowledge points in it , Different color spaces have different conversion formulas , I took a picture from my mobile camera YUV Convert the picture to bmp View pictures under the computer , The color is almost the same, so I think it is the correct formula , I'm too lazy to think about what color space it is .

RGB turn YUV The formula

  • Y = 0.299 * R + 0.587 * G + 0.114 * B
  • U = -0.169 * R - 0.331 * G + 0.499 * B + 128
  • V = 0.499 * R - 0.418 * G - 0.0813 * B + 128

It should be noted that ,R、G、B The range is 0 ~ 255, Just one byte Can be said , We read from memory RGB when , It's also byte Data of type , However, you should pay attention to when participating in the conversion formula ,java Medium byte It's symbolic , One byte Yes 8 A bit , If it's all 1, stay byte The middle is -1, If in int The middle is 255, So we need to take byte Converted to a positive number int value , Otherwise, the calculation formula will not work . Also note that :byte.toInt() This function , One -1 Of byte Convert to int After all -1, So we need to pay attention , We're going to take int The lowest 8 position , Then turn the high positions into 0, Then it becomes a positive number . There is also the conversion formula Y、U、V The range of values is also 0 ~ 255 Of , It needs to be handled beyond the scope .

Corresponding Kotlin The implementation code is as follows :

object YuvUtil {
       

    fun byteToInt(byte: Byte): Int = byte.toInt() shl 24 ushr 24
    fun verify(int: Int) = if (int < 0) 0 else if (int > 255) 255 else int
    fun toHexString(int: Int): String = Integer.toHexString(int)

    fun rgbToYuv(R: Byte, G: Byte, B: Byte): Triple<Byte, Byte, Byte> {
    
        //  notes :R、G、B The range of values is 0 ~ 255, There is no negative number , That needs to be converted to a positive number int.
        //  A negative number is used byte.toInt() After conversion, it is still a negative number , So we use bitwise operators to convert ,Byte Of -1 Convert to Int The value should be 255
        return rgbToYuv(byteToInt(R), byteToInt(G), byteToInt(B))
    }

    fun rgbToYuv(R: Int, G: Int, B: Int): Triple<Byte, Byte, Byte> {
    
        var Y = (0.299 * R + 0.587 * G + 0.114 * B).toInt()
        var U = (-0.169 * R - 0.331 * G + 0.499 * B + 128).toInt()
        var V = (0.499 * R - 0.418 * G - 0.0813 * B + 128).toInt()
        Y = verify(Y)
        U = verify(U)
        V = verify(V)
        println("rgb: ${
      toHexString(R)} ${
      toHexString(G)} ${
      toHexString(B)} -> yuv: ${
      toHexString(Y)} ${
      toHexString(U)} ${
      toHexString(V)}")
        return Triple(Y.toByte(), U.toByte(), V.toByte())
    }
}

YUV turn RGB The formula

  • R = Y + 1.4075 * (V - 128)
  • G = Y - 0.3455 * (U - 128) - 0.7169 * (V - 128)
  • B = Y + 1.7790 * (U - 128)

Here also need to pay attention to byte Data processing before participating in formula calculation , There is also out of range processing of calculation results .

Corresponding Kotlin The implementation code is as follows :

object YuvUtil {
        

    fun byteToInt(byte: Byte): Int = byte.toInt() shl 24 ushr 24
    fun verify(int: Int) = if (int < 0) 0 else if (int > 255) 255 else int
    fun toHexString(int: Int): String = Integer.toHexString(int)    

    fun yuvToRgb(Y: Byte, U: Byte, V: Byte): Triple<Byte, Byte, Byte> {
    
        //  notes :Y、U、V The range of values is 0 ~ 255, There is no negative number , That needs to be converted to a positive number int.
        //  A negative number is used byte.toInt() After conversion, it is still a negative number , So we use bitwise operators to convert ,Byte Of -1 Convert to Int The value should be 255
        return yuvToRgb(byteToInt(Y), byteToInt(U), byteToInt(V))
    }

    fun yuvToRgb(Y: Int, U: Int, V: Int): Triple<Byte, Byte, Byte> {
    
        var R = (Y + 1.4075 * (V - 128)).toInt()
        var G = (Y - 0.3455 * (U - 128) - 0.7169 * (V - 128)).toInt()
        var B = (Y + 1.779 * (U - 128)).toInt()
        R = verify(R)
        G = verify(G)
        B = verify(B)
        println("yuv: ${
      toHexString(Y)} ${
      toHexString(U)} ${
      toHexString(V)} -> rgb: ${
      toHexString(R)} ${
      toHexString(G)} ${
      toHexString(B)}")
        return Triple(R.toByte(), G.toByte(), B.toByte())
    }

}

RGB and YUV Rotation deviation

RGB and YUV It's impossible to perfect each other , in other words RGB Convert to YUV after , Then switch back to GRB Time is the same as the original RGB There may be deviations , Examples are as follows :

fun main() {
    
    val (Y, U, V) = YuvUtil.rgbToYuv(0xff, 0, 0)
    val (R, G, B) = YuvUtil.yuvToRgb(Y, U, V)
}

The operation results are as follows :

rgb: ff 0 0 -> yuv: 4c 54 ff
yuv: 4c 54 ff -> rgb: fe 0 0

As you can see from the results , The original RGB by :0xff0000, Convert to YUV by :0x4c54ff, Then switch back to RGB by :0xfe0000, And the original RGB The values are different , But it's close to , That is to say, they are all red , There is almost no difference between the original red and the converted red with the naked eye .

bmp Picture turn YUV picture

import java.io.*

object YuvUtil {
    

    fun bmpFileToYV12FileDemo() {
    
        val grbBytes = BmpUtil.createRgbBytes(4, 2)
        println(" Next output RGB pixel data :")
        BmpUtil.printColorBytes(grbBytes)
        val yuv444Bytes = rgbBytesToYuv444Bytes(grbBytes)
        println(" Next output YUV pixel data :")
        BmpUtil.printColorBytes(yuv444Bytes)
        val (yBytes, uBytes, vBytes) = yuv444BytesToYv12Bytes(yuv444Bytes)
        printYV12(yBytes, uBytes, vBytes)
        val yv12File = File("C:\\Users\\Even\\Pictures\\demo.yuv")
        writeYv12BytesToFile(yv12File, yBytes, vBytes, uBytes)
    }

    fun bmpFileToYV12FileDemo2() {
    
        val bmpFile = File("C:\\Users\\Even\\Pictures\\ Haiqin smoke .bmp")
        val yv12File = File("C:\\Users\\Even\\Pictures\\ Haiqin smoke .yuv")
        val rgbBytes = BmpUtil.readBmpFilePixelBytes(bmpFile)
        val yuv444Bytes = rgbBytesToYuv444Bytes(rgbBytes)
        val (yBytes, uBytes, vBytes) = yuv444BytesToYv12Bytes(yuv444Bytes)
        writeYv12BytesToFile(yv12File, yBytes, vBytes, uBytes)
    }

    private fun writeYv12BytesToFile(yv12File: File, yBytes: ByteArray, vBytes: ByteArray, uBytes: ByteArray) {
    
        FileOutputStream(yv12File).use {
     fos ->
            BufferedOutputStream(fos).use {
     bos ->
                bos.write(yBytes)
                bos.write(vBytes)
                bos.write(uBytes)
            }
        }
    }

    fun rgbBytesToYuv444Bytes(rgbBytes: Array<ByteArray>): Array<ByteArray> {
    
        val yuv444Bytes = Array(rgbBytes.size) {
     ByteArray(rgbBytes[0].size) }
        for (rowIndex in rgbBytes.indices) {
    
            val oneLineBytes = rgbBytes[rowIndex]
            val oneLineYuv444Bytes = yuv444Bytes[rowIndex]
            for (columnIndex in oneLineBytes.indices step 3) {
    
                val red   = oneLineBytes[columnIndex + 0]
                val green = oneLineBytes[columnIndex + 1]
                val blue  = oneLineBytes[columnIndex + 2]
                val (Y, U, V) = rgbToYuv(red, green, blue)
                oneLineYuv444Bytes[columnIndex + 0] = Y
                oneLineYuv444Bytes[columnIndex + 1] = U
                oneLineYuv444Bytes[columnIndex + 2] = V
            }
        }
        return yuv444Bytes
    }

    fun byteToInt(byte: Byte): Int = byte.toInt() shl 24 ushr 24
    fun verify(int: Int) = if (int < 0) 0 else if (int > 255) 255 else int
    fun toHexString(int: Int): String = Integer.toHexString(int)

    fun rgbToYuv(R: Byte, G: Byte, B: Byte): Triple<Byte, Byte, Byte> {
    
        //  notes :R、G、B The range of values is 0 ~ 255, There is no negative number , That needs to be converted to a positive number int.
        //  A negative number is used byte.toInt() After conversion, it is still a negative number , So we use bitwise operators to convert ,Byte Of -1 Convert to Int The value should be 255
        return rgbToYuv(byteToInt(R), byteToInt(G), byteToInt(B))
    }

    fun rgbToYuv(R: Int, G: Int, B: Int): Triple<Byte, Byte, Byte> {
    
        var Y = (0.299 * R + 0.587 * G + 0.114 * B).toInt()
        var U = (-0.169 * R - 0.331 * G + 0.499 * B + 128).toInt()
        var V = (0.499 * R - 0.418 * G - 0.0813 * B + 128).toInt()
        Y = verify(Y)
        U = verify(U)
        V = verify(V)
        //println("rgb: ${toHexString(R)} ${toHexString(G)} ${toHexString(B)} -> yuv: ${toHexString(Y)} ${toHexString(U)} ${toHexString(V)}")
        return Triple(Y.toByte(), U.toByte(), V.toByte())
    }

    fun yuvToRgb(Y: Byte, U: Byte, V: Byte): Triple<Byte, Byte, Byte> {
    
        //  notes :Y、U、V The range of values is 0 ~ 255, There is no negative number , That needs to be converted to a positive number int.
        //  A negative number is used byte.toInt() After conversion, it is still a negative number , So we use bitwise operators to convert ,Byte Of -1 Convert to Int The value should be 255
        return yuvToRgb(byteToInt(Y), byteToInt(U), byteToInt(V))
    }

    fun yuvToRgb(Y: Int, U: Int, V: Int): Triple<Byte, Byte, Byte> {
    
        var R = (Y + 1.4075 * (V - 128)).toInt()
        var G = (Y - 0.3455 * (U - 128) - 0.7169 * (V - 128)).toInt()
        var B = (Y + 1.779 * (U - 128)).toInt()
        R = verify(R)
        G = verify(G)
        B = verify(B)
        //println("yuv: ${toHexString(Y)} ${toHexString(U)} ${toHexString(V)} -> rgb: ${toHexString(R)} ${toHexString(G)} ${toHexString(B)}")
        return Triple(R.toByte(), G.toByte(), B.toByte())
    }



    fun yv12BytesToYuv444Bytes(width: Int, height: Int, yBytes: ByteArray, uBytes: ByteArray, vBytes: ByteArray): Array<ByteArray> {
    
        var yIndex = 0
        val yuv444Bytes = Array(height) {
     ByteArray(width * 3) }
        val oneLineUvSize = width / 2 //  stay YV12 in , In a row U or V The number of 
        var twoLineIndex = -1  //  Count every two lines 
        for (rowIndex in 0 until height) {
    
            val oneLineBytes = yuv444Bytes[rowIndex]
            var u: Byte = 0
            var v: Byte = 0

            //  Because it reads every two lines UV The starting position is the same , So we'll just put twoLineIndex Just add 
            if (rowIndex % 2 == 0) {
    
                twoLineIndex++
            }

            //  Calculate each row in the fetch UV when uvIndex Starting position 
            var uvIndex = twoLineIndex * oneLineUvSize

            for (columnIndex in oneLineBytes.indices step 3) {
    
                if (yIndex % 2 == 0) {
    
                    //  In a row , Every two Y Just once UV
                    u = uBytes[uvIndex]
                    v = vBytes[uvIndex]
                    uvIndex++
                }

                val y = yBytes[yIndex++]
                oneLineBytes[columnIndex + 0] = y
                oneLineBytes[columnIndex + 1] = u
                oneLineBytes[columnIndex + 2] = v
            }
        }

        return yuv444Bytes
    }

    private fun yuv444BytesToYv12Bytes(yuv444Bytes: Array<ByteArray>): Triple<ByteArray, ByteArray, ByteArray> {
    
        val width = yuv444Bytes[0].size / 3  //  Each pixel accounts for 3 Bytes , So divide by 3
        val height = yuv444Bytes.size
        val ySize = width * height
        val vSize = ySize / 4
        val yBytes = ByteArray(ySize)
        val uBytes = ByteArray(vSize)
        val vBytes = ByteArray(vSize)
        var yIndex = 0
        var uIndex = 0
        var vIndex = 0
        var saveU = true
        var saveV = true
        for (rowIndex in 0 until height) {
    
            val oneLine = yuv444Bytes[rowIndex]
            for (columnIndex in oneLine.indices step 3) {
    
                val y = oneLine[columnIndex + 0]
                val u = oneLine[columnIndex + 1]
                val v = oneLine[columnIndex + 2]
                yBytes[yIndex++] = y
                if (rowIndex % 2 == 0) {
    
                    //  Even lines take U, Take one from another 
                    if (saveU) {
    
                        uBytes[uIndex++] = u
                    }
                    saveU = !saveU
                } else {
    
                    //  The singular row is taken as V, Take one from another 
                    if (saveV) {
    
                        vBytes[vIndex++] = v
                    }
                    saveV = !saveV
                }
            }
        }
        return Triple(yBytes, uBytes, vBytes)
    }

    fun printYUV444(yuv444Bytes: Array<ByteArray>) {
    
        println(" Next output YUV444 data ")
        for (oneLine in yuv444Bytes) {
    
            for (columnIndex in oneLine.indices step 3) {
    
                val y = oneLine[columnIndex + 0]
                val u = oneLine[columnIndex + 1]
                val v = oneLine[columnIndex + 2]
                print("$y $u $v | ")
            }
            println()
        }
    }

    fun printYV12(yBytes: ByteArray, uBytes: ByteArray, vBytes: ByteArray) {
    
        //  With 16 Print in hexadecimal 
        println(" Next output YV12 data ")
        println(" Next output Y data ")
        yBytes.forEach {
     print("${
      toHexString(byteToInt(it))} ") }
        println("\n Next output V data ")
        vBytes.forEach {
     print("${
      toHexString(byteToInt(it))} ") }
        println("\n Next output U data ")
        uBytes.forEach {
     print("${
      toHexString(byteToInt(it))} ") }
        println()
    }

}
import java.io.*

object BmpUtil {
    

    /**  establish Bitmap An example of : By reading a bmp Pixels of the file , Then write these pixels into a new bmp file  */
    fun createBitmapDemo2() {
    
        val bmpFilePixelBytes = readBmpFilePixelBytes(File("C:\\Users\\Even\\Pictures\\ Haiqin smoke .bmp"))
        //printPixelBytes(bmpFilePixelBytes)
        createBmpFile(bmpFilePixelBytes, File("C:\\Users\\Even\\Pictures\\demo.bmp"))
    }

    /**  establish Bitmap An example of : Create one with the top half red , The lower half is green bmp file  */
    fun createBitmapDemo() {
    
        val width = 300 //  Be careful : The width and height should be set to 4 Multiple , So as to avoid the operation of filling positions 
        val height = 200
        val pixelBytes = createRgbBytes(width, height)
        //printPixelBytes(pixelBytes)
        val bmpFile = File("C:\\Users\\Even\\Pictures\\demo.bmp")
        createBmpFile(pixelBytes, bmpFile)
    }

    fun readBmpFilePixelBytes(bmpFile: File): Array<ByteArray> {
    
        //  obtain bmp All bytes of the file 
        val bmpFileBytes = bmpFile.readBytes()

        //  from bmp Get the byte array of the width and height of the image in the file 
        val widthLittleEndianBytes = ByteArray(4)
        val heightLittleEndianBytes = ByteArray(4)
        System.arraycopy(bmpFileBytes, 0x12, widthLittleEndianBytes, 0, 4)
        System.arraycopy(bmpFileBytes, 0x16, heightLittleEndianBytes, 0, 4)

        //  Convert the byte array at the small end to Int
        val width = littleEndianBytesToInt(widthLittleEndianBytes)
        val height = littleEndianBytesToInt(heightLittleEndianBytes)
        println(" Read bmp Images width = $width, height = $height")
        val pixelBytes = Array(height) {
     ByteArray(width * 3) }
        var rowIndex = height - 1 //  because bmp The picture is saved from the last line , When we read it, we put it in the correct position 
        var columnIndex = 0
        var oneLineBytes = pixelBytes[rowIndex]
        val oneLineBytesSize = oneLineBytes.size
        //  Pixel values are all from 0x36 Start saving at , And every pixel 3 Bytes 
        for (i in 0x36 until bmpFileBytes.size step 3) {
    
            if (columnIndex == oneLineBytesSize) {
    
                //  There is a full line , Need to wrap to save . here --rowIndex Because the original image is saved from the last line to the front line 
                oneLineBytes = pixelBytes[--rowIndex]
                columnIndex = 0
            }

            //  Be careful :bmp The color of the file is blue 、 green 、 Saved in red order 
            val blue  = bmpFileBytes[i + 0]
            val green = bmpFileBytes[i + 1]
            val red   = bmpFileBytes[i + 2]

            oneLineBytes[columnIndex++] = red
            oneLineBytes[columnIndex++] = green
            oneLineBytes[columnIndex++] = blue
        }

        return pixelBytes
    }

    /**  Convert the byte array at the small end to int */
    private fun littleEndianBytesToInt(littleEndianBytes: ByteArray): Int {
    
        val bigEndianBytes = littleEndianBytes.reversedArray()
        val bais = ByteArrayInputStream(bigEndianBytes)
        val dis = DataInputStream(bais)
        return dis.readInt()
    }

    /**  Create a pixel matrix , Be careful : The width should be set to 4 Multiple  */
    fun createRgbBytes(width: Int, height: Int) : Array<ByteArray> {
    
        val redColor   = 0xFF0000
        val greenColor = 0x00FF00
        val redBytes   = getColorBytes(redColor)
        val greenBytes = getColorBytes(greenColor)
        val rgbBytes = Array(height) {
     ByteArray(width * 3) }
        for (rowIndex in 0 until height) {
    
            val colorBytes = if (rowIndex < height / 2) redBytes else greenBytes
            val oneLineBytes = rgbBytes[rowIndex]
            for (columnIndex in oneLineBytes.indices step 3) {
    
                val red   = colorBytes[0x00]
                val green = colorBytes[0x01]
                val blue  = colorBytes[0x02]
                oneLineBytes[columnIndex + 0] = red
                oneLineBytes[columnIndex + 1] = green
                oneLineBytes[columnIndex + 2] = blue
            }
        }
        return rgbBytes
    }

    fun getColorBytes(color: Int): ByteArray {
    
        val red   = (color and 0xFF0000 ushr 16).toByte()
        val green = (color and 0x00FF00 ushr 8).toByte()
        val blue  = (color and 0x0000FF).toByte()
        val colorBytes = byteArrayOf(red, green, blue)
        return colorBytes
    }

    /**  Print color values , Printable rgb Color value , You can also print yuv444 Color value  */
    fun printColorBytes(pixelBytes: Array<ByteArray>) {
    
        for (rowIndex in pixelBytes.indices) {
    
            val oneLine = pixelBytes[rowIndex]
            for (columnIndex in oneLine.indices step 3) {
    
                //  obtain 1 A pixel 3 A color channel :R、G、B  or  Y、U、V
                val colorChannel1 =   oneLine[columnIndex + 0]
                val colorChannel2 =  oneLine[columnIndex + 1]
                val colorChannel3 = oneLine[columnIndex + 2]

                //  hold byte To int, And then to 16 Binary output 
                val colorChannelInt1 = toHexString(byteToInt(colorChannel1))
                val colorChannelInt2 = toHexString(byteToInt(colorChannel2))
                val colorChannelInt3 = toHexString(byteToInt(colorChannel3))

                //  With 16 Print in hexadecimal 
                print("$colorChannelInt1 $colorChannelInt2 $colorChannelInt3| ")
            }
            println()
        }
    }

    fun byteToInt(byte: Byte): Int = byte.toInt() shl 24 ushr 24
    fun toHexString(int: Int): String = Integer.toHexString(int)

    /**  According to the given pixel two-dimensional data , according to bmp The file specification is saved to the specified bmp In file  */
    fun createBmpFile(pixelBytes: Array<ByteArray>, saveFile: File) {
    
        //  Because each pixel in a row occupies 3 Bytes of , Divide 3 You get the width of the image 
        val pixelWidth = pixelBytes[0].size / 3
        val pixelHeight = pixelBytes.size
        //  Each pixel accounts for 3 individual byte, So multiply by 3
        val pixelBytesCount = pixelWidth * pixelHeight * 3
        //  The total size of the file is : Pixel data size  +  Header file size 
        val fileBytesCount = pixelBytesCount + 54
        //  Create a byte Array , Used to hold bmp All of the documents byte data 
        val bmpFileBytes = ByteArray(fileBytesCount)
        //  Go to bmpFileBytes Add bmp The file header 
        addBmpFileHeader(pixelWidth, pixelHeight, bmpFileBytes)
        //  Go to bmpFileBytes Add pixel data to 
        addPixelBytes(pixelBytes, bmpFileBytes)
        //  Write all the bytes to the file 
        saveFile.writeBytes(bmpFileBytes)
    }

    fun addBmpFileHeader(width: Int, height: Int, bmpFileBytes: ByteArray) {
    
        val pixelBytesCount = width * height * 3
        val fileBytesCount = pixelBytesCount + 54

        // 424d
        bmpFileBytes[0x00] = 0x42
        bmpFileBytes[0x01] = 0x4d

        //  file size 
        var bytes = getLittleEndianBytes(fileBytesCount)
        bmpFileBytes[0x02] = bytes[0]
        bmpFileBytes[0x03] = bytes[1]
        bmpFileBytes[0x04] = bytes[2]
        bmpFileBytes[0x05] = bytes[3]

        //  Keep the data 
        bmpFileBytes[0x06] = 0x00
        bmpFileBytes[0x07] = 0x00
        bmpFileBytes[0x08] = 0x00
        bmpFileBytes[0x09] = 0x00

        //  Pixel storage location 
        bmpFileBytes[0x0a] = 0x36
        bmpFileBytes[0x0b] = 0x00
        bmpFileBytes[0x0c] = 0x00
        bmpFileBytes[0x0d] = 0x00

        // bmp Header file size 
        bmpFileBytes[0x0e] = 0x28
        bmpFileBytes[0x0f] = 0x00
        bmpFileBytes[0x10] = 0x00
        bmpFileBytes[0x11] = 0x00

        //  The width of the image 
        bytes = getLittleEndianBytes(width)
        bmpFileBytes[0x12] = bytes[0]
        bmpFileBytes[0x13] = bytes[1]
        bmpFileBytes[0x14] = bytes[2]
        bmpFileBytes[0x15] = bytes[3]

        //  Height of the image 
        bytes = getLittleEndianBytes(height)
        bmpFileBytes[0x16] = bytes[0]
        bmpFileBytes[0x17] = bytes[1]
        bmpFileBytes[0x18] = bytes[2]
        bmpFileBytes[0x19] = bytes[3]

        //  Number of color planes 
        bmpFileBytes[0x1a] = 0x01
        bmpFileBytes[0x1b] = 0x00

        //  Pixel digits 
        bmpFileBytes[0x1c] = 0x18
        bmpFileBytes[0x1d] = 0x00

        //  Compression way 
        bmpFileBytes[0x1e] = 0x00
        bmpFileBytes[0x1f] = 0x00
        bmpFileBytes[0x20] = 0x00
        bmpFileBytes[0x21] = 0x00

        //  Pixel data size 
        bytes = getLittleEndianBytes(pixelBytesCount)
        bmpFileBytes[0x22] = bytes[0]
        bmpFileBytes[0x23] = bytes[1]
        bmpFileBytes[0x24] = bytes[2]
        bmpFileBytes[0x25] = bytes[3]

        //  Lateral resolution 
        bmpFileBytes[0x26] = 0x00
        bmpFileBytes[0x27] = 0x00
        bmpFileBytes[0x28] = 0x00
        bmpFileBytes[0x29] = 0x00

        //  Vertical resolution 
        bmpFileBytes[0x2a] = 0x00
        bmpFileBytes[0x2b] = 0x00
        bmpFileBytes[0x2c] = 0x00
        bmpFileBytes[0x2d] = 0x00

        //  Number of palette colors 
        bmpFileBytes[0x2e] = 0x00
        bmpFileBytes[0x2f] = 0x00
        bmpFileBytes[0x30] = 0x00
        bmpFileBytes[0x31] = 0x00

        //  Number of important colors 
        bmpFileBytes[0x32] = 0x00
        bmpFileBytes[0x33] = 0x00
        bmpFileBytes[0x34] = 0x00
        bmpFileBytes[0x35] = 0x00
    }

    /**  Add the specified pixel data to bmp File array  */
    fun addPixelBytes(pixelBytes: Array<ByteArray>, bmpFileBytes: ByteArray) {
    
        val height = pixelBytes.size
        var index = 0x36

        //  Set pixel data , Be careful : To store from the last row of pixels 
        for (rowIndex in height - 1 downTo 0) {
    
            val oneLineBytes = pixelBytes[rowIndex]
            for (columnIndex in oneLineBytes.indices step 3) {
    
                val red   = oneLineBytes[columnIndex + 0]
                val green = oneLineBytes[columnIndex + 1]
                val blue  = oneLineBytes[columnIndex + 2]

                //  The three primary colors of each pixel are stored in reverse order 
                bmpFileBytes[index++] = blue
                bmpFileBytes[index++] = green
                bmpFileBytes[index++] = red
            }
        }
    }

    /**  hold int Convert to byte Array , The default is the array of big end mode , Returns an array converted to the small end method  */
    fun getLittleEndianBytes(number: Int): ByteArray {
    
        val baos = ByteArrayOutputStream()
        val dos = DataOutputStream(baos)
        dos.writeInt(number)
        val bigEndianBytes = baos.toByteArray()
        val littleEndianBytes = bigEndianBytes.reversedArray()
        return littleEndianBytes
    }

}
fun main() {
    
    YuvUtil.bmpFileToYV12FileDemo()
    //YuvUtil.bmpFileToYV12FileDemo2()
}

Here we write two Demo:bmpFileToYV12FileDemo()、bmpFileToYV12FileDemo2(), first Demo It is created by code rgbBytes data , Only red and green , And it's 4 x 2 Size , This makes it easy for us to check whether the data is correct , It will be very convenient to troubleshoot problems if you can't get the correct results , The operation results are as follows :

 Next output RGB pixel data :
ff 0 0| ff 0 0| ff 0 0| ff 0 0| 
0 ff 0| 0 ff 0| 0 ff 0| 0 ff 0| 
 Next output YUV pixel data :
4c 54 ff| 4c 54 ff| 4c 54 ff| 4c 54 ff| 
95 2b 15| 95 2b 15| 95 2b 15| 95 2b 15| 
 Next output YV12 data 
 Next output Y data 
4c 4c 4c 4c 95 95 95 95 
 Next output V data 
15 15 
 Next output U data 
54 54

Because of the small amount of data , You can see that the data is correct , It can even be used 16 Open the generated demo.yuv View the data , as follows :
 Insert picture description here
Because of the small amount of data , So it's easy to check whether the data is wrong . Now our data is correct , And then we can run bmpFileToYV12FileDemo2() This function , I read this one bmp picture , Width height is 640 x 480, Here is bmp picture , And generated yuv Picture effect comparison :
 Insert picture description here
On the left is Windows 11 The self-contained drawing viewing software opens bmp picture , On the right is to use YUV Player The open yuv picture , You can see bmp Convert to yuv The back color is biased , And it can be seen , I don't know if it's the wrong formula I chose that caused the problem .

YUV Player Download address :https://github.com/latelee/YUVPlayer/tree/master/bin, This has not been updated for a long time , But it's easy to use . For more updates, there is another , But this setting feels complicated , I don't know how to adjust parameters :https://github.com/IENT/YUView, Download address :https://github.com/IENT/YUView/releases

YUV Picture turn bmp picture

import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream

object YuvUtil {
    

    fun yv12FileToBmpFile() {
    
        val yv12File = File("C:\\Users\\Even\\Pictures\\ Haiqin smoke .yuv")
        val bmpFile = File("C:\\Users\\Even\\Pictures\\ Haiqin smoke (yuv turn bmp).bmp")
        val (yBytes, uBytes, vBytes) = readYuvFilePlanarBytes(yv12File, 640, 480)
        val yuv444Bytes = yv12BytesToYuv444Bytes(640, 480, yBytes, uBytes, vBytes)
        val rgbBytes = yuv444BytesToRgbBytes(yuv444Bytes)
        BmpUtil.createBmpFile(rgbBytes, bmpFile)
    }

    fun bmpFileToYV12FileDemo() {
    
        val grbBytes = BmpUtil.createRgbBytes(4, 2)
        println(" Next output RGB pixel data :")
        BmpUtil.printColorBytes(grbBytes)
        val yuv444Bytes = rgbBytesToYuv444Bytes(grbBytes)
        println(" Next output YUV pixel data :")
        BmpUtil.printColorBytes(yuv444Bytes)
        val (yBytes, uBytes, vBytes) = yuv444BytesToYv12Bytes(yuv444Bytes)
        printYV12(yBytes, uBytes, vBytes)
        val yv12File = File("C:\\Users\\Even\\Pictures\\demo.yuv")
        writeYv12BytesToFile(yv12File, yBytes, vBytes, uBytes)
    }

    fun bmpFileToYV12FileDemo2() {
    
        val bmpFile = File("C:\\Users\\Even\\Pictures\\ Haiqin smoke .bmp")
        val yv12File = File("C:\\Users\\Even\\Pictures\\ Haiqin smoke .yuv")
        val rgbBytes = BmpUtil.readBmpFilePixelBytes(bmpFile)
        val yuv444Bytes = rgbBytesToYuv444Bytes(rgbBytes)
        val (yBytes, uBytes, vBytes) = yuv444BytesToYv12Bytes(yuv444Bytes)
        writeYv12BytesToFile(yv12File, yBytes, vBytes, uBytes)
    }

    /**  Read YUV The three planes of the file are saved in three arrays , Keep separately Y、U、V Three planes  */
    fun readYuvFilePlanarBytes(yuvFile: File, width: Int, height: Int): Triple<ByteArray, ByteArray, ByteArray> {
    
        return readYuvFilePlanarBytes(yuvFile.readBytes(), width, height)
    }

    fun readYuvFilePlanarBytes(yuvBytes: ByteArray, width: Int, height: Int): Triple<ByteArray, ByteArray, ByteArray> {
    
        val ySize = width * height
        val vSize = ySize / 4
        val yBytes = ByteArray(ySize)
        val uBytes = ByteArray(vSize)
        val vBytes = ByteArray(vSize)
        var i = 0
        yuvBytes.forEachIndexed {
     index, byte ->
            val bytes = when {
    
                index < ySize -> yBytes
                index < ySize + vSize -> vBytes
                else -> uBytes
            }
            if (index == ySize || index == ySize + vSize) {
    
                i = 0
            }
            bytes[i++] = byte
        }
        return Triple(yBytes, uBytes, vBytes)
    }

    private fun writeYv12BytesToFile(yv12File: File, yBytes: ByteArray, vBytes: ByteArray, uBytes: ByteArray) {
    
        FileOutputStream(yv12File).use {
     fos ->
            BufferedOutputStream(fos).use {
     bos ->
                bos.write(yBytes)
                bos.write(vBytes)
                bos.write(uBytes)
            }
        }
    }

    fun rgbBytesToYuv444Bytes(rgbBytes: Array<ByteArray>): Array<ByteArray> {
    
        val yuv444Bytes = Array(rgbBytes.size) {
     ByteArray(rgbBytes[0].size) }
        for (rowIndex in rgbBytes.indices) {
    
            val oneLineBytes = rgbBytes[rowIndex]
            val oneLineYuv444Bytes = yuv444Bytes[rowIndex]
            for (columnIndex in oneLineBytes.indices step 3) {
    
                val red   = oneLineBytes[columnIndex + 0]
                val green = oneLineBytes[columnIndex + 1]
                val blue  = oneLineBytes[columnIndex + 2]
                val (Y, U, V) = rgbToYuv(red, green, blue)
                oneLineYuv444Bytes[columnIndex + 0] = Y
                oneLineYuv444Bytes[columnIndex + 1] = U
                oneLineYuv444Bytes[columnIndex + 2] = V
            }
        }
        return yuv444Bytes
    }

    fun byteToInt(byte: Byte): Int = byte.toInt() shl 24 ushr 24
    fun verify(int: Int) = if (int < 0) 0 else if (int > 255) 255 else int
    fun toHexString(int: Int): String = Integer.toHexString(int)

    fun rgbToYuv(R: Byte, G: Byte, B: Byte): Triple<Byte, Byte, Byte> {
    
        //  notes :R、G、B The range of values is 0 ~ 255, There is no negative number , That needs to be converted to a positive number int.
        //  A negative number is used byte.toInt() After conversion, it is still a negative number , So we use bitwise operators to convert ,Byte Of -1 Convert to Int The value should be 255
        return rgbToYuv(byteToInt(R), byteToInt(G), byteToInt(B))
    }

    fun rgbToYuv(R: Int, G: Int, B: Int): Triple<Byte, Byte, Byte> {
    
        var Y = (0.299 * R + 0.587 * G + 0.114 * B).toInt()
        var U = (-0.169 * R - 0.331 * G + 0.499 * B + 128).toInt()
        var V = (0.499 * R - 0.418 * G - 0.0813 * B + 128).toInt()
        Y = verify(Y)
        U = verify(U)
        V = verify(V)
        //println("rgb: ${toHexString(R)} ${toHexString(G)} ${toHexString(B)} -> yuv: ${toHexString(Y)} ${toHexString(U)} ${toHexString(V)}")
        return Triple(Y.toByte(), U.toByte(), V.toByte())
    }

    fun yuvToRgb(Y: Byte, U: Byte, V: Byte): Triple<Byte, Byte, Byte> {
    
        //  notes :Y、U、V The range of values is 0 ~ 255, There is no negative number , That needs to be converted to a positive number int.
        //  A negative number is used byte.toInt() After conversion, it is still a negative number , So we use bitwise operators to convert ,Byte Of -1 Convert to Int The value should be 255
        return yuvToRgb(byteToInt(Y), byteToInt(U), byteToInt(V))
    }

    fun yuvToRgb(Y: Int, U: Int, V: Int): Triple<Byte, Byte, Byte> {
    
        var R = (Y + 1.4075 * (V - 128)).toInt()
        var G = (Y - 0.3455 * (U - 128) - 0.7169 * (V - 128)).toInt()
        var B = (Y + 1.779 * (U - 128)).toInt()
        R = verify(R)
        G = verify(G)
        B = verify(B)
        //println("yuv: ${toHexString(Y)} ${toHexString(U)} ${toHexString(V)} -> rgb: ${toHexString(R)} ${toHexString(G)} ${toHexString(B)}")
        return Triple(R.toByte(), G.toByte(), B.toByte())
    }



    fun yv12BytesToYuv444Bytes(width: Int, height: Int, yBytes: ByteArray, uBytes: ByteArray, vBytes: ByteArray): Array<ByteArray> {
    
        var yIndex = 0
        val yuv444Bytes = Array(height) {
     ByteArray(width * 3) }
        val oneLineUvSize = width / 2 //  stay YV12 in , In a row U or V The number of 
        var twoLineIndex = -1  //  Count every two lines 
        for (rowIndex in 0 until height) {
    
            val oneLineBytes = yuv444Bytes[rowIndex]
            var u: Byte = 0
            var v: Byte = 0

            //  Because it reads every two lines UV The starting position is the same , So we'll just put twoLineIndex Just add 
            if (rowIndex % 2 == 0) {
    
                twoLineIndex++
            }

            //  Calculate each row in the fetch UV when uvIndex Starting position 
            var uvIndex = twoLineIndex * oneLineUvSize

            for (columnIndex in oneLineBytes.indices step 3) {
    
                if (yIndex % 2 == 0) {
    
                    //  In a row , Every two Y Just once UV
                    u = uBytes[uvIndex]
                    v = vBytes[uvIndex]
                    uvIndex++
                }

                val y = yBytes[yIndex++]
                oneLineBytes[columnIndex + 0] = y
                oneLineBytes[columnIndex + 1] = u
                oneLineBytes[columnIndex + 2] = v
            }
        }

        return yuv444Bytes
    }

    private fun yuv444BytesToYv12Bytes(yuv444Bytes: Array<ByteArray>): Triple<ByteArray, ByteArray, ByteArray> {
    
        val width = yuv444Bytes[0].size / 3  //  Each pixel accounts for 3 Bytes , So divide by 3
        val height = yuv444Bytes.size
        val ySize = width * height
        val vSize = ySize / 4
        val yBytes = ByteArray(ySize)
        val uBytes = ByteArray(vSize)
        val vBytes = ByteArray(vSize)
        var yIndex = 0
        var uIndex = 0
        var vIndex = 0
        var saveU = true
        var saveV = true
        for (rowIndex in 0 until height) {
    
            val oneLine = yuv444Bytes[rowIndex]
            for (columnIndex in oneLine.indices step 3) {
    
                val y = oneLine[columnIndex + 0]
                val u = oneLine[columnIndex + 1]
                val v = oneLine[columnIndex + 2]
                yBytes[yIndex++] = y
                if (rowIndex % 2 == 0) {
    
                    //  Even lines take U, Take one from another 
                    if (saveU) {
    
                        uBytes[uIndex++] = u
                    }
                    saveU = !saveU
                } else {
    
                    //  The singular row is taken as V, Take one from another 
                    if (saveV) {
    
                        vBytes[vIndex++] = v
                    }
                    saveV = !saveV
                }
            }
        }
        return Triple(yBytes, uBytes, vBytes)
    }

    fun yuv444BytesToRgbBytes(yuv444Bytes: Array<ByteArray>): Array<ByteArray> {
    
        val rgbBytes = Array(yuv444Bytes.size) {
     ByteArray(yuv444Bytes[0].size) }
        for (rowIndex in yuv444Bytes.indices) {
    
            val oneLineYuv444Bytes = yuv444Bytes[rowIndex]
            val oneLineRgbBytes = rgbBytes[rowIndex]
            for (columnIndex in oneLineYuv444Bytes.indices step 3) {
    
                val Y   = oneLineYuv444Bytes[columnIndex + 0]
                val U = oneLineYuv444Bytes[columnIndex + 1]
                val V  = oneLineYuv444Bytes[columnIndex + 2]
                val (R, G, B) = yuvToRgb(Y, U, V)
                oneLineRgbBytes[columnIndex + 0] = R
                oneLineRgbBytes[columnIndex + 1] = G
                oneLineRgbBytes[columnIndex + 2] = B
            }
        }
        return rgbBytes
    }

    fun printYUV444(yuv444Bytes: Array<ByteArray>) {
    
        println(" Next output YUV444 data ")
        for (oneLine in yuv444Bytes) {
    
            for (columnIndex in oneLine.indices step 3) {
    
                val y = oneLine[columnIndex + 0]
                val u = oneLine[columnIndex + 1]
                val v = oneLine[columnIndex + 2]
                print("$y $u $v | ")
            }
            println()
        }
    }

    fun printYV12(yBytes: ByteArray, uBytes: ByteArray, vBytes: ByteArray) {
    
        //  With 16 Print in hexadecimal 
        println(" Next output YV12 data ")
        println(" Next output Y data ")
        yBytes.forEach {
     print("${
      toHexString(byteToInt(it))} ") }
        println("\n Next output V data ")
        vBytes.forEach {
     print("${
      toHexString(byteToInt(it))} ") }
        println("\n Next output U data ")
        uBytes.forEach {
     print("${
      toHexString(byteToInt(it))} ") }
        println()
    }

}
import java.io.*

object BmpUtil {
    

    /**  establish Bitmap An example of : By reading a bmp Pixels of the file , Then write these pixels into a new bmp file  */
    fun createBitmapDemo2() {
    
        val bmpFilePixelBytes = readBmpFilePixelBytes(File("C:\\Users\\Even\\Pictures\\ Haiqin smoke .bmp"))
        //printPixelBytes(bmpFilePixelBytes)
        createBmpFile(bmpFilePixelBytes, File("C:\\Users\\Even\\Pictures\\demo.bmp"))
    }

    /**  establish Bitmap An example of : Create one with the top half red , The lower half is green bmp file  */
    fun createBitmapDemo() {
    
        val width = 300 //  Be careful : The width and height should be set to 4 Multiple , So as to avoid the operation of filling positions 
        val height = 200
        val pixelBytes = createRgbBytes(width, height)
        //printPixelBytes(pixelBytes)
        val bmpFile = File("C:\\Users\\Even\\Pictures\\demo.bmp")
        createBmpFile(pixelBytes, bmpFile)
    }

    fun readBmpFilePixelBytes(bmpFile: File): Array<ByteArray> {
    
        //  obtain bmp All bytes of the file 
        val bmpFileBytes = bmpFile.readBytes()

        //  from bmp Get the byte array of the width and height of the image in the file 
        val widthLittleEndianBytes = ByteArray(4)
        val heightLittleEndianBytes = ByteArray(4)
        System.arraycopy(bmpFileBytes, 0x12, widthLittleEndianBytes, 0, 4)
        System.arraycopy(bmpFileBytes, 0x16, heightLittleEndianBytes, 0, 4)

        //  Convert the large byte array to Int
        val width = littleEndianBytesToInt(widthLittleEndianBytes)
        val height = littleEndianBytesToInt(heightLittleEndianBytes)
        println(" Read bmp Images width = $width, height = $height")
        val pixelBytes = Array(height) {
     ByteArray(width * 3) }
        var rowIndex = height - 1 //  because bmp The picture is saved from the last line , When we read it, we put it in the correct position 
        var columnIndex = 0
        var oneLineBytes = pixelBytes[rowIndex]
        val oneLineBytesSize = oneLineBytes.size
        //  Pixel values are all from 0x36 Start saving at , And every pixel 3 Bytes 
        for (i in 0x36 until bmpFileBytes.size step 3) {
    
            if (columnIndex == oneLineBytesSize) {
    
                //  There is a full line , Need to wrap to save . here --rowIndex Because the original image is saved from the last line to the front line 
                oneLineBytes = pixelBytes[--rowIndex]
                columnIndex = 0
            }

            //  Be careful :bmp The color of the file is blue 、 green 、 Saved in red order 
            val blue  = bmpFileBytes[i + 0]
            val green = bmpFileBytes[i + 1]
            val red   = bmpFileBytes[i + 2]

            oneLineBytes[columnIndex++] = red
            oneLineBytes[columnIndex++] = green
            oneLineBytes[columnIndex++] = blue
        }

        return pixelBytes
    }

    /**  Convert the byte array at the small end to int */
    private fun littleEndianBytesToInt(littleEndianBytes: ByteArray): Int {
    
        val bigEndianBytes = littleEndianBytes.reversedArray()
        val bais = ByteArrayInputStream(bigEndianBytes)
        val dis = DataInputStream(bais)
        return dis.readInt()
    }

    /**  Create a pixel matrix , Be careful : The width should be set to 4 Multiple  */
    fun createRgbBytes(width: Int, height: Int) : Array<ByteArray> {
    
        val redColor   = 0xFF0000
        val greenColor = 0x00FF00
        val redBytes   = getColorBytes(redColor)
        val greenBytes = getColorBytes(greenColor)
        val rgbBytes = Array(height) {
     ByteArray(width * 3) }
        for (rowIndex in 0 until height) {
    
            val colorBytes = if (rowIndex < height / 2) redBytes else greenBytes
            val oneLineBytes = rgbBytes[rowIndex]
            for (columnIndex in oneLineBytes.indices step 3) {
    
                val red   = colorBytes[0x00]
                val green = colorBytes[0x01]
                val blue  = colorBytes[0x02]
                oneLineBytes[columnIndex + 0] = red
                oneLineBytes[columnIndex + 1] = green
                oneLineBytes[columnIndex + 2] = blue
            }
        }
        return rgbBytes
    }

    fun getColorBytes(color: Int): ByteArray {
    
        val red   = (color and 0xFF0000 ushr 16).toByte()
        val green = (color and 0x00FF00 ushr 8).toByte()
        val blue  = (color and 0x0000FF).toByte()
        val colorBytes = byteArrayOf(red, green, blue)
        return colorBytes
    }

    /**  Print color values , Printable rgb Color value , You can also print yuv444 Color value  */
    fun printColorBytes(pixelBytes: Array<ByteArray>) {
    
        for (rowIndex in pixelBytes.indices) {
    
            val oneLine = pixelBytes[rowIndex]
            for (columnIndex in oneLine.indices step 3) {
    
                //  obtain 1 A pixel 3 A color channel :R、G、B  or  Y、U、V
                val colorChannel1 =   oneLine[columnIndex + 0]
                val colorChannel2 =  oneLine[columnIndex + 1]
                val colorChannel3 = oneLine[columnIndex + 2]

                //  hold byte To int, And then to 16 Binary output 
                val colorChannelInt1 = toHexString(byteToInt(colorChannel1))
                val colorChannelInt2 = toHexString(byteToInt(colorChannel2))
                val colorChannelInt3 = toHexString(byteToInt(colorChannel3))

                //  With 16 Print in hexadecimal 
                print("$colorChannelInt1 $colorChannelInt2 $colorChannelInt3| ")
            }
            println()
        }
    }

    fun byteToInt(byte: Byte): Int = byte.toInt() shl 24 ushr 24
    fun toHexString(int: Int): String = Integer.toHexString(int)

    /**  According to the given pixel two-dimensional data , according to bmp The file specification is saved to the specified bmp In file  */
    fun createBmpFile(rgbBytes: Array<ByteArray>, saveFile: File) {
    
        //  Because each pixel in a row occupies 3 Bytes of , Divide 3 You get the width of the image 
        val pixelWidth = rgbBytes[0].size / 3
        val pixelHeight = rgbBytes.size
        //  Each pixel accounts for 3 individual byte, So multiply by 3
        val pixelBytesCount = pixelWidth * pixelHeight * 3
        //  The total size of the file is : Pixel data size  +  Header file size 
        val fileBytesCount = pixelBytesCount + 54
        //  Create a byte Array , Used to hold bmp All of the documents byte data 
        val bmpFileBytes = ByteArray(fileBytesCount)
        //  Go to bmpFileBytes Add bmp The file header 
        addBmpFileHeader(pixelWidth, pixelHeight, bmpFileBytes)
        //  Go to bmpFileBytes Add pixel data to 
        addPixelBytes(rgbBytes, bmpFileBytes)
        //  Write all the bytes to the file 
        saveFile.writeBytes(bmpFileBytes)
    }

    fun addBmpFileHeader(width: Int, height: Int, bmpFileBytes: ByteArray) {
    
        val pixelBytesCount = width * height * 3
        val fileBytesCount = pixelBytesCount + 54

        // 424d
        bmpFileBytes[0x00] = 0x42
        bmpFileBytes[0x01] = 0x4d

        //  file size 
        var bytes = getLittleEndianBytes(fileBytesCount)
        bmpFileBytes[0x02] = bytes[0]
        bmpFileBytes[0x03] = bytes[1]
        bmpFileBytes[0x04] = bytes[2]
        bmpFileBytes[0x05] = bytes[3]

        //  Keep the data 
        bmpFileBytes[0x06] = 0x00
        bmpFileBytes[0x07] = 0x00
        bmpFileBytes[0x08] = 0x00
        bmpFileBytes[0x09] = 0x00

        //  Pixel storage location 
        bmpFileBytes[0x0a] = 0x36
        bmpFileBytes[0x0b] = 0x00
        bmpFileBytes[0x0c] = 0x00
        bmpFileBytes[0x0d] = 0x00

        // bmp Header file size 
        bmpFileBytes[0x0e] = 0x28
        bmpFileBytes[0x0f] = 0x00
        bmpFileBytes[0x10] = 0x00
        bmpFileBytes[0x11] = 0x00

        //  The width of the image 
        bytes = getLittleEndianBytes(width)
        bmpFileBytes[0x12] = bytes[0]
        bmpFileBytes[0x13] = bytes[1]
        bmpFileBytes[0x14] = bytes[2]
        bmpFileBytes[0x15] = bytes[3]

        //  Height of the image 
        bytes = getLittleEndianBytes(height)
        bmpFileBytes[0x16] = bytes[0]
        bmpFileBytes[0x17] = bytes[1]
        bmpFileBytes[0x18] = bytes[2]
        bmpFileBytes[0x19] = bytes[3]

        //  Number of color planes 
        bmpFileBytes[0x1a] = 0x01
        bmpFileBytes[0x1b] = 0x00

        //  Pixel digits 
        bmpFileBytes[0x1c] = 0x18
        bmpFileBytes[0x1d] = 0x00

        //  Compression way 
        bmpFileBytes[0x1e] = 0x00
        bmpFileBytes[0x1f] = 0x00
        bmpFileBytes[0x20] = 0x00
        bmpFileBytes[0x21] = 0x00

        //  Pixel data size 
        bytes = getLittleEndianBytes(pixelBytesCount)
        bmpFileBytes[0x22] = bytes[0]
        bmpFileBytes[0x23] = bytes[1]
        bmpFileBytes[0x24] = bytes[2]
        bmpFileBytes[0x25] = bytes[3]

        //  Lateral resolution 
        bmpFileBytes[0x26] = 0x00
        bmpFileBytes[0x27] = 0x00
        bmpFileBytes[0x28] = 0x00
        bmpFileBytes[0x29] = 0x00

        //  Vertical resolution 
        bmpFileBytes[0x2a] = 0x00
        bmpFileBytes[0x2b] = 0x00
        bmpFileBytes[0x2c] = 0x00
        bmpFileBytes[0x2d] = 0x00

        //  Number of palette colors 
        bmpFileBytes[0x2e] = 0x00
        bmpFileBytes[0x2f] = 0x00
        bmpFileBytes[0x30] = 0x00
        bmpFileBytes[0x31] = 0x00

        //  Number of important colors 
        bmpFileBytes[0x32] = 0x00
        bmpFileBytes[0x33] = 0x00
        bmpFileBytes[0x34] = 0x00
        bmpFileBytes[0x35] = 0x00
    }

    /**  Add the specified pixel data to bmp File array  */
    fun addPixelBytes(pixelBytes: Array<ByteArray>, bmpFileBytes: ByteArray) {
    
        val height = pixelBytes.size
        var index = 0x36

        //  Set pixel data , Be careful : To store from the last row of pixels 
        for (rowIndex in height - 1 downTo 0) {
    
            val oneLineBytes = pixelBytes[rowIndex]
            for (columnIndex in oneLineBytes.indices step 3) {
    
                val red   = oneLineBytes[columnIndex + 0]
                val green = oneLineBytes[columnIndex + 1]
                val blue  = oneLineBytes[columnIndex + 2]

                //  The three primary colors of each pixel are stored in reverse order 
                bmpFileBytes[index++] = blue
                bmpFileBytes[index++] = green
                bmpFileBytes[index++] = red
            }
        }
    }

    /**  hold int Convert to byte Array , The default is the array of small end mode , Returns an array converted to the big end method  */
    fun getLittleEndianBytes(number: Int): ByteArray {
    
        val baos = ByteArrayOutputStream()
        val dos = DataOutputStream(baos)
        dos.writeInt(number)
        val bigEndianBytes = baos.toByteArray()
        val littleEndianBytes = bigEndianBytes.reversedArray()
        return littleEndianBytes
    }

}
fun main() {
    
// YuvUtil.bmpFileToYV12FileDemo()
// YuvUtil.bmpFileToYV12FileDemo2()
    YuvUtil.yv12FileToBmpFile()
}

The operation effect is as follows :
 Insert picture description here
The leftmost part is the screenshot saved after the screenshot of the software bmp The original document , In the middle bmp The conversion yuv picture , On the right is yuv Converted back bmp picture , I don't know the difference , The difference is small , If there is no comparison with the original picture, there is generally no difference !

YUV444、YUV422、YUV420、YUV420P、YUV420SP、YV12、YU12、NV12、NV21

As the title ,YUV There are so many formats , It is really difficult to understand , in front , We mainly explained YV12 Format , Whatever you want to know YUV Format , After understanding the most complicated YV12 After the format , It's easy to understand other formats , That's why I put these differences between multiple formats to the end .

Why use YUV without RGB

R(red)、G(green)、B(blue) It is called three primary colors , The three primary colors can be combined into any color , We modify RGB Value , The color will change , And the brightness of the color will also change , so to speak RGB The brightness information and color information are mixed together .
Y、U、V Simple understanding ,Y Indicates brightness ,U and V Color . And RGB comparison ,YUV Separate brightness and color information , This coding method is very suitable for human eyes , According to the research , The human eye is more sensitive to brightness information than to color information , for instance , You make a red color less red , It may seem that you can't find that the red color has been reduced , And you darken the red , It is easier to find , According to this feature , When we collect image information , You can collect all the brightness , But you can throw away some when you collect color information , Because after losing some, people's eyes can't find , such , We go through YUV Get a good quality image , And the required storage space is larger than RGB The way should be small .

YUV Separate brightness and color information , It is also more convenient for us to deal with brightness and color respectively , For example, make the image brighter , hold Y You can increase the value of , And for RGB, To brighten a color, you need to change it 3 It's worth , For example, you put 3 Both values are increased , Maybe the color has also changed , It's not just a change in brightness , It is difficult to adjust .

Another advantage is that it can be compatible with black-and-white TV sets and color TV sets , For black and white TV sets , Just parse Y Information is enough , For color TV set, I will explain YUV Information .

YUV Classify according to the sampling method

YUV There are three main sampling methods :

  • YUV444, Every time 4 individual Y, There are corresponding 4 individual U and 4 individual V, So called YUV444
  • YUV422, Every time 4 individual Y, There are corresponding 2 individual U and 2 individual V, So called YUV422
  • YUV420, Every time 4 individual Y, There are corresponding 2 individual U and 0 individual V, Or every time 4 individual Y There are corresponding 2 individual V and 0 individual U, So called YUV420

1、YUV444 Sampling method of

 Insert picture description here

Pictured above , For each pixel , It collects Y、U、V value , So every 4 Every pixel will have 4 individual Y、4 individual U、4 individual V, This is a YUV444 The origin of the name .

2、YUV422 Sampling method of

 Insert picture description here

Pictured above , For each pixel ,Y It's all collected , and U or V Collect only one of them , As shown in the above figure, the rule is , First pixel acquisition U, Second pixel acquisition V, Third pixel acquisition U, The fourth pixel acquisition V... So repeat the cycle , Then every 4 Pixels must have 4 individual Y、2 individual U、2 individual V, This is it. YUV422 The origin of the name .

When restoring , Every two Y Share a pair UV, After the restoration, it must not be the same as before , But as we said before , The perception of color information by human eyes is relatively low , As long as the brightness doesn't change , We changed it U or Y Value , Generally, I can't feel any change , And most of the two adjacent pixels have the same color , That is, most of the two adjacent pixels U and V Not much difference in value , That's why the number one 1 Pixels are represented by the 2 A pixel V, The first 2 Pixels are represented by the 1 A pixel U, And we can't feel the reason why the image color is wrong !

3、YUV420 Sampling method of

 Insert picture description here
Pictured above , For each pixel ,Y It's all collected . about U and V They are all taken from one line to another , as follows :

The first 1 That's ok : collection 1 individual U, Collect another one every other U... By analogy
The first 2 That's ok : collection 1 individual V, Collect another one every other V... By analogy

If there are more lines , This is also the law collection , as follows :

The first 3 That's ok : collection 1 individual U, Collect another one every other U... By analogy
The first 4 That's ok : collection 1 individual V, Collect another one every other V... By analogy

first line , Every time 4 individual Y There will be 2 individual U and 0 individual V.
The second line , Every time 4 individual Y There will be 2 individual V and 0 individual U.

This may be YUV420 The origin of , I can't find the official website description , Anyway, this is a reasonable explanation !

When restoring , The two lines come together , Two of the first line Y And the two in the second line Y share 1 individual U and 1 individual V, in other words 4 individual Y Corresponding 1 individual U and 1 individual V, comparison YUV422 The accuracy of the restore is even lower , But people still can't see the difference , This is it. YUV The power of this place , So it can be seen that ,YUV420 Should be the most commonly used , Because it saves the most space , And the image quality is also very good !

YUV Classify according to storage mode

YUV The format is first classified according to the collection method , Then it can be classified for the second time according to the storage mode , such as YUV420 Format , This is classified by acquisition format ,YUV420 Storage can be divided into multiple formats in different ways , such as :YV12、YU12、NV21、NV12, this 4 Both formats are based on YUV420 The method of sampling , It's just that the storage order is a little different when storing .

YV12、YU12

 Insert picture description here
Pictured above , hold Y、U、V They are stored separately Planar( Plane ) Format , combination YUV420 Call YUV420P. Save before storing V Save again U Call YV12, Pre deposit U Save again V Call YU12( Also called I420).YV12 Format and YU12 The format belongs to YUV420P Sub format under format .

NV21、NV12

 Insert picture description here
Pictured above ,Y and UV Are stored separately , Belong to Planar( Plane ) Format , however U and V It's every two packs (Packed) Together , So called Semi-Planar( Half plane ) Format , and YUV420 Put it all together YUV420SP. stay U and V In the order of , Pre deposit V Post storage U Yes. NV21 Format , Pre deposit U Post storage V Yes. NV12 Format ,NV21 and NV12 All are YUV420SP The subformat of .

about Planar、Semi-Planar The format of , There's another one called Packed( pack ) Format , For example YUV444 In the format , Store each pixel's Y、U、V All packaged together for storage , This is called Packed Format , Examples are as follows :
 Insert picture description here
because YUV420 Is the most commonly used , So I put YUV420 The three formats are classified and counted :
 Insert picture description here

Conversion between formats

After understanding the principles of various formats , In fact, there is no need to search the conversion formula on the Internet , Write your own code , Of course , If you want to pursue efficiency , Looking for open source libraries , such as libyuv library .

Here is a brief introduction to the conversion of common formats , as follows :

YU12 turn YV12:
 Insert picture description here

YU12 turn NV12:
 Insert picture description here

NV21 turn NV12:
 Insert picture description here

原网站

版权声明
本文为[android_ cai_ niao]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/176/202206252033299927.html