Press "Enter" to skip to content

YUV入门及go YUV转JPG和H264

最近在做IoT相关的工作,有一部分数据是来自摄像头,通过摄像头获取的数据格式是YUV格式,所以研究了一下YUV是个什么东西,测试了几个示例,记录一下,希望能对新手有所帮助。

YUV理解

YUV是一种颜色编码方法。Y(Luma, Luminance)代表明亮度,UV代表色度(Chroma、Chrominance),其实就是Y是黑白的,然后UV代表颜色,据说是为了电视机而设计的,Y给黑白电视机用,加上UV后就成了彩色电视机了。YUV对应的是YCbCr颜色空间,Y代表灰度成分,U代表Cb(相对于绿色,蓝色blue的成分),V代表Cr(相对于绿色,红色red的成分)。

Y为0.5,UV色度的表示如图所示:

YUV有两种格式,一种是紧缩格式(packed formats),就是将YUV存储到一个像素点数组里;另一种是平面格式(planar formats),将YUV分别提取出来分别存储为Y、U、V三个分量。YUV的提取叫做抽样(subsample),主要的抽样格式有YCbCr 4:2:0、YCbCr 4:2:2、YCbCr 4:1:1和YCbCr 4:4:4。YUV的表示法称为A:B:C表示法:

4:4:4表示完全取样,存储结构对应YUV444;
4:2:2表示2:1的水平取样,垂直完全采样,存储结构对应YUV422;
4:2:0表示2:1的水平取样,垂直2:1采样,存储结构对应YUV420;
4:1:1表示4:1的水平取样,垂直完全采样,存储结构对应YUV411;

采样该如何理解呢,请参考这篇文章

go Image处理YUV为JPG

首先我们找一个yuv文件 ds_480x272.yuv,这个文件的YUV采样格式为YUV420。我们直接上代码:

package main

import (
"fmt"
"image"
"image/jpeg"
"os"
)

func main() {
width := 480
height := 272
size := width * height
f, _ := os.Open("./ds_480x272.yuv")
defer f.Close()

Y := make([]byte, size)
U := make([]byte, size/4)
V := make([]byte, size/4)

n1, _ := f.Read(Y)
n2, _ := f.Read(U)
n3, _ := f.Read(V)

fmt.Println(n1, n2, n3)

img := image.NewYCbCr(image.Rect(0, 0, width, height), image.YCbCrSubsampleRatio420)
img.Y = Y
img.Cb = U
img.Cr = V

jpg, _ := os.Create("YUV.jpg")
jpeg.Encode(jpg, img, nil)
}

简单起见,已去掉了一些err处理哈,执行上述代码,可以从ds_480x272.yuv中读取一张YUV的图像,并保存为YUV.jpg,效果如下图所示:

如果只提取出Y分量,我们只需要修改代码如下:

img := image.NewGray(image.Rect(0, 0, width, height))
img.Pix = Y

此处创建一张黑白图片即可,效果如下:

go处理YUV为H.264

接下来我们将上述ds_480x272.yuv转换为h.264格式,用于播放等,我们用x264-go 这个包,可以简单快速完成这个转换,上代码:

package main

import (
"bytes"
"fmt"
"image"
"io/ioutil"
"os"
"os/signal"
"syscall"

"github.com/gen2brain/x264-go"
)

func main() {
width := 480
height := 272
size := width * height
f, err := os.Open("./ds_480x272.yuv")
if err != nil {

}
defer f.Close()

buf := bytes.NewBuffer(make([]byte, 0))
opts := &x264.Options{
Width: width,
Height: height,
FrameRate: 25,
Tune: "zerolatency",
Preset: "veryfast",
Profile: "baseline",
LogLevel: x264.LogDebug,
}

enc, err := x264.NewEncoder(buf, opts)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
defer enc.Close()

s := make(chan os.Signal, 1)
signal.Notify(s, os.Interrupt, syscall.SIGTERM)

Y := make([]byte, size)
U := make([]byte, size/4)
V := make([]byte, size/4)

for {
select {
case <-s:
enc.Flush()

err = ioutil.WriteFile(“ds.264", buf.Bytes(), 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}

os.Exit(0)
default:
n1, _ := f.Read(Y)
if n1 == 0 {
s <- os.Interrupt
}
n2, _ := f.Read(U)
n3, _ := f.Read(V)

fmt.Println(n1, n2, n3)

img := image.NewYCbCr(image.Rect(0, 0, width, height), image.YCbCrSubsampleRatio420)
img.Y = Y
img.Cb = U
img.Cr = V

err = enc.Encode(img)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
}
}
}

}

运行上述代码,我们可以得到ds.264文件,我们可以用vlc播放器播放此文件,效果如下:

 

其他参考资料

https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-H.264-201610-S!!PDF-E&type=items

http://www.enkichen.com/2017/11/26/image-h264-encode/

Be First to Comment

发表评论

电子邮件地址不会被公开。 必填项已用*标注