YUV2RGB Opencv

51 篇文章 2 订阅
44 篇文章 2 订阅

YUV2RGB OpenCV(C/C++/Python)

参考:

百度百科:YUV
维基百科:YUV
YUV Colorspacehttp://softpixel.com/~cwright/programming/colorspace/yuv/
YUV420P格式分析https://my.oschina.net/u/589963/blog/167766


YUV是一种颜色编码方法

Y 分量表示颜色的亮度(luminance),单取出 Y 分量就是图像的灰度图;UV 分量表示颜色色度或者浓度(Chrominance

YUV 图像有两种编码格式:

  • 紧缩格式(packed formats):YUV 三通道像素值依次排列,即 Y0 U0 V0 Y1 U1 V1 ...
  • 平面格式(planar formats):先排列 Y 分量的所有像素值,再排列 U 分量的,最后排列 V 分量的

Note:平面模式适合采样(subsample

由于人眼对于亮度的敏感度远大于色度,所以减少 UV 分量并不会过多损害图像的质量,从而减少了数据量,达到压缩的目的

  • YUV444 表示图像完全采样,没有压缩,紧缩格式
  • YUV420p 表示图像 2:1 的水平取样,垂直 2:1 采样,即每 4Y 分量对应一个 UV 分量,平面模式

Note:YUV420p(有时简称为 YUV420) 是常见的图像格式,接下来进行的 YUVRGB 的转换操作都是针对 420p 格式的

YUVRGB 之间的转换公式如下:

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

Y = R *  .299000 + G *  .587000 + B *  .114000
U = R * -.168736 + G * -.331264 + B *  .500000 + 128
V = R *  .500000 + G * -.418688 + B * -.081312 + 128

Note:假定各分量之间的取值范围均为 [0, 255]


主要内容

  1. C++YUV420pRGB
  2. C++:关键函数介绍
  3. PythonYUV420pRGB 方法一
  4. Python:关键函数介绍
  5. PythonYUV420pRGB 方法二
  6. Python:关键函数介绍
  7. CYUV420pRGB
  8. C:关键函数介绍
  9. YUV 图片资源

C++YUV420pRGB

  • 首先读取 YUV 格式图片,是二进制文件;打开图像文件,按字节读取,并赋值到新建的图像中,其中,Y 分量图像大小和结果图像大小一致,UV 分量图像大小比结果图像小一倍;按顺序读取 Y 分量,再读取 UV 分量

    vector<Mat> readYUV(char* image_path, int width, int height) {
        ifstream fin;
        fin.open(image_path, ios::binary);
        if (!fin.is_open()) {
            cout << "open failed" << endl;
            exit(0);
        }
    
        Mat img_Y = Mat::zeros(height, width, CV_8UC1);
    
        char ch;
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                fin.read(&ch, sizeof(ch));
    
                img_Y.at<uchar>(i, j) = (uchar)ch;
            }
        }
    
        Mat img_U = Mat::zeros(height / 2, width / 2, CV_8UC1);
        for (int i = 0; i < height / 2; i++) {
            for (int j = 0; j < width / 2; j++) {
                fin.read(&ch, sizeof(ch));
    
                img_U.at<uchar>(i, j) = (uchar)ch;
            }
        }
    
        Mat img_V = Mat::zeros(height / 2, width / 2, CV_8UC1);
        for (int i = 0; i < height / 2; i++) {
            for (int j = 0; j < width / 2; j++) {
                fin.read(&ch, sizeof(ch));
    
                img_V.at<uchar>(i, j) = (uchar)ch;
            }
        }
        fin.close();
    
        vector<Mat> yuv;
        yuv.push_back(img_Y);
        yuv.push_back(img_U);
        yuv.push_back(img_V);
    
        return yuv;
    }
    
  • 得到从文件解析出来的各分量后,放大 UV 分量和 Y 分量同样大小:

    Mat enlarge(Mat src) {
        Mat enlarge = Mat::zeros(src.rows*2, src.cols*2, CV_8UC1);
        resize(src, enlarge, Size(), 2, 2, INTER_CUBIC);
    
        return enlarge;
    }
    
  • 合并 3 个通道分量:

    Mat merge(vector<Mat> channels) {
        int width = channels.at(0).cols;
        int height = channels.at(0).rows;
    
        Mat yuv = Mat::zeros(height, width, CV_8UC3);
        merge(channels, yuv);
    
        return yuv;
    }
    
  • YUV 图像转换为 RGB 图像(OpenCV 中格式为 BGR):

    Mat cvtYUV2BGR(Mat yuv) {
        int width = yuv.cols;
        int height = yuv.rows;
    
        Mat bgr = Mat::zeros(height, width, CV_8UC3);
        cvtColor(yuv, bgr, COLOR_YUV2BGR);
    
        return bgr;
    }
    

完整代码如下所示:

#include <iostream>
#include <fstream>
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;

vector<Mat> readYUV(char* image_path, int width, int height) {
    ifstream fin;
    fin.open(image_path, ios::binary);
    if (!fin.is_open()) {
        cout << "open failed" << endl;
        exit(0);
    }

    Mat img_Y = Mat::zeros(height, width, CV_8UC1);

    char ch;
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            fin.read(&ch, sizeof(ch));

            img_Y.at<uchar>(i, j) = (uchar)ch;
        }
    }

    Mat img_U = Mat::zeros(height / 2, width / 2, CV_8UC1);
    for (int i = 0; i < height / 2; i++) {
        for (int j = 0; j < width / 2; j++) {
            fin.read(&ch, sizeof(ch));

            img_U.at<uchar>(i, j) = (uchar)ch;
        }
    }

    Mat img_V = Mat::zeros(height / 2, width / 2, CV_8UC1);
    for (int i = 0; i < height / 2; i++) {
        for (int j = 0; j < width / 2; j++) {
            fin.read(&ch, sizeof(ch));

            img_V.at<uchar>(i, j) = (uchar)ch;
        }
    }
    fin.close();

    vector<Mat> yuv;
    yuv.push_back(img_Y);
    yuv.push_back(img_U);
    yuv.push_back(img_V);

    return yuv;
}

Mat enlarge(Mat src) {
    Mat enlarge = Mat::zeros(src.rows*2, src.cols*2, CV_8UC1);
    resize(src, enlarge, Size(), 2, 2, INTER_CUBIC);

    return enlarge;
}

Mat merge(vector<Mat> channels) {
    int width = channels.at(0).cols;
    int height = channels.at(0).rows;

    Mat yuv = Mat::zeros(height, width, CV_8UC3);
    merge(channels, yuv);

    return yuv;
}

Mat cvtYUV2BGR(Mat yuv) {
    int width = yuv.cols;
    int height = yuv.rows;

    Mat bgr = Mat::zeros(height, width, CV_8UC3);
    cvtColor(yuv, bgr, COLOR_YUV2BGR);

    return bgr;
}

int main(int argc, char* argv[]) {
    int width = 640;
    int height = 480;

    //char* image_path = "c:\\yuv\\img_yuv";
    char* image_path = "c:\\yuv\\jpgimage1_image_640_480.yuv";

    vector<Mat> temp = readYUV(image_path, width, height);
    vector<Mat> channels;

    channels.push_back(temp.at(0));
    channels.push_back(enlarge(temp.at(1)));
    channels.push_back(enlarge(temp.at(2)));

    Mat yuv = merge(channels);
    Mat bgr = cvtYUV2BGR(yuv);

    //Mat yuv = Mat::zeros(height, width, CV_8UC3);
    //merge(channels, yuv);

    //Mat bgr = Mat::zeros(height, width, CV_8UC3);
    //cvtColor(yuv, bgr, COLOR_YUV2BGR);

    imshow("img", bgr);
    waitKey(0);

    return 0;
}

C++:关键函数介绍

  • fin.open(image_path, ios::binary);

参考:std::ifstream::open

原型:

void open (const char* filename,  ios_base::openmode mode = ios_base::in);

参数介绍:

image_path - filename,指定打开的文件名

ios::binary - mode,操作文件的方式,ios::binary 表明已二进制模式操作而不是文本模式(Operations are performed in binary mode rather than text.

  • Mat img_Y = Mat::zeros(height, width, CV_8UC1);

参考:Mat::zeros

原型:

static MatExpr Mat::zeros(int rows, int cols, int type)

参数介绍:

height - rows,行数,即图像高

width - cols,列数,即图像宽

CV_8UC1 - type,图像类型,此处为单通道无符号 8 位整数

  • fin.read(&ch, sizeof(ch));

参考:std::istream::read

原型:

istream& read (char* s, streamsize n);

参数介绍:

&ch - s,字符指针,保存提取的数据

sizeof(ch) - n,提取的字节数

  • img_Y.at<uchar>(i, j) = (uchar)ch;

参考:Mat::at

原型:

template<typename T> T& Mat::at(int i, int j)

函数功能:返回指定数组元素的索引


PythonYUV420pRGB

在网上找到一个参考:

Python读取YUV

能够运行(import Image 改成 from PIL import Image),不过他使用的是 PIL 模块,目前最常用的图像处理库是 OpenCV

在知乎上的一个讨论,给了我灵感:

Python如何正确读取YUV二进制文件(为数字)?

  • 读取二进制图像文件并解析为 YUV 单通道图像:

    def read_YUV420(image_path, rows, cols):
        """
        读取YUV文件,解析为Y, U, V图像
        :param image_path: YUV图像路径
        :param rows: 给定高
        :param cols: 给定宽
        :return: 列表,[Y, U, V]
        """
        # create Y
        gray = np.zeros((rows, cols), np.uint8)
        print type(gray)
        print gray.shape
    
        # create U,V
        img_U = np.zeros((rows / 2, cols / 2), np.uint8)
        print type(img_U)
        print img_U.shape
    
        img_V = np.zeros((rows / 2, cols / 2), np.uint8)
        print type(img_V)
        print img_V.shape
    
        with open(image_path, 'rb') as reader:
            for i in xrange(rows):
                for j in xrange(cols):
                    gray[i, j] = ord(reader.read(1))
    
            for i in xrange(rows / 2):
                for j in xrange(cols / 2):
                    img_U[i, j] = ord(reader.read(1))
    
            for i in xrange(rows / 2):
                for j in xrange(cols / 2):
                    img_V[i, j] = ord(reader.read(1))
    
        return [gray, img_U, img_V]
    
  • 合并单通道图像并转换为 RGB 图像:

    def merge_YUV2RGB_v1(Y, U, V):
        """
        转换YUV图像为RGB格式(放大U、V)
        :param Y: Y分量图像
        :param U: U分量图像
        :param V: V分量图像
        :return: RGB格式图像
        """
        # Y分量图像比U、V分量图像大一倍,想要合并3个分量,需要先放大U、V分量和Y分量一样大小
        enlarge_U = cv2.resize(U, (0, 0), fx=2.0, fy=2.0, interpolation=cv2.INTER_CUBIC)
        enlarge_V = cv2.resize(V, (0, 0), fx=2.0, fy=2.0, interpolation=cv2.INTER_CUBIC)
    
        # 合并YUV3通道
        img_YUV = cv2.merge([Y, enlarge_U, enlarge_V])
    
        dst = cv2.cvtColor(img_YUV, cv2.COLOR_YUV2BGR)
        return dst
    
  • 还有另一种方法

    def merge_YUV2RGB_v2(Y, U, V):
        """
        转换YUV图像为RGB格式(缩小Y)
        :param Y: Y分量图像
        :param U: U分量图像
        :param V: V分量图像
        :return: RGB格式图像
        """
        rows, cols = Y.shape[:2]
    
        # 先缩小Y分量,合并3通道,转换为RGB格式图像后,再放大至原来大小
        shrink_Y = cv2.resize(Y, (cols / 2, rows / 2), interpolation=cv2.INTER_AREA)
    
        # 合并YUV3通道
        img_YUV = cv2.merge([shrink_Y, U, V])
    
        dst = cv2.cvtColor(img_YUV, cv2.COLOR_YUV2BGR)
        cv2.COLOR_YUV2BGR_I420
    
        # 放大
        enlarge_dst = cv2.resize(dst, (0, 0), fx=2.0, fy=2.0, interpolation=cv2.INTER_CUBIC)
        return enlarge_dst
    

完整代码如下:

# -*- coding: utf-8 -*-

import cv2
import numpy as np


def read_YUV420(image_path, rows, cols):
    """
    读取YUV文件,解析为Y, U, V图像
    :param image_path: YUV图像路径
    :param rows: 给定高
    :param cols: 给定宽
    :return: 列表,[Y, U, V]
    """
    # create Y
    gray = np.zeros((rows, cols), np.uint8)
    print type(gray)
    print gray.shape

    # create U,V
    img_U = np.zeros((rows / 2, cols / 2), np.uint8)
    print type(img_U)
    print img_U.shape

    img_V = np.zeros((rows / 2, cols / 2), np.uint8)
    print type(img_V)
    print img_V.shape

    with open(image_path, 'rb') as reader:
        for i in xrange(rows):
            for j in xrange(cols):
                gray[i, j] = ord(reader.read(1))

        for i in xrange(rows / 2):
            for j in xrange(cols / 2):
                img_U[i, j] = ord(reader.read(1))

        for i in xrange(rows / 2):
            for j in xrange(cols / 2):
                img_V[i, j] = ord(reader.read(1))

    return [gray, img_U, img_V]


def merge_YUV2RGB_v1(Y, U, V):
    """
    转换YUV图像为RGB格式(放大U、V)
    :param Y: Y分量图像
    :param U: U分量图像
    :param V: V分量图像
    :return: RGB格式图像
    """
    # Y分量图像比U、V分量图像大一倍,想要合并3个分量,需要先放大U、V分量和Y分量一样大小
    enlarge_U = cv2.resize(U, (0, 0), fx=2.0, fy=2.0, interpolation=cv2.INTER_CUBIC)
    enlarge_V = cv2.resize(V, (0, 0), fx=2.0, fy=2.0, interpolation=cv2.INTER_CUBIC)

    # 合并YUV3通道
    img_YUV = cv2.merge([Y, enlarge_U, enlarge_V])

    dst = cv2.cvtColor(img_YUV, cv2.COLOR_YUV2BGR)
    return dst


def merge_YUV2RGB_v2(Y, U, V):
    """
    转换YUV图像为RGB格式(缩小Y)
    :param Y: Y分量图像
    :param U: U分量图像
    :param V: V分量图像
    :return: RGB格式图像
    """
    rows, cols = Y.shape[:2]

    # 先缩小Y分量,合并3通道,转换为RGB格式图像后,再放大至原来大小
    shrink_Y = cv2.resize(Y, (cols / 2, rows / 2), interpolation=cv2.INTER_AREA)

    # 合并YUV3通道
    img_YUV = cv2.merge([shrink_Y, U, V])

    dst = cv2.cvtColor(img_YUV, cv2.COLOR_YUV2BGR)
    cv2.COLOR_YUV2BGR_I420

    # 放大
    enlarge_dst = cv2.resize(dst, (0, 0), fx=2.0, fy=2.0, interpolation=cv2.INTER_CUBIC)
    return enlarge_dst


if __name__ == '__main__':
    rows = 480
    cols = 640
    image_path = 'C:\\yuv\\jpgimage1_image_640_480.yuv'

    Y, U, V = read_YUV420(image_path, rows, cols)

    dst = merge_YUV2RGB_v1(Y, U, V)

    cv2.imshow("dst", dst)
    cv2.waitKey(0)

Python:关键函数介绍

  • gray = np.zeros((rows, cols), np.uint8)

参考:numpy.zeros

原型:

numpy.zeros(shape, dtype=float, order='C')

(rows, cols) - shape,数组形状

np.uint8 - dtype,默认是 float ,当前设置为 8 位无符号整数

  • with open(image_path, 'rb') as reader:

参考:[open(name[, mode[, buffering]])](https://docs.python.org/2.7/library/functions.html?highlight=open#open)

  • gray[i, j] = ord(reader.read(1))

参考:ord(c)

原型:ord(c)

函数功能:返回单个字符字符串的整数值


PythonYUV420pRGB 方法二

之前的学习,让我又有了另一种更简单的方法

树莓派(Raspberry Pi)中PiCamera+OpenCV的使用

直接上程序:

# -*- coding: utf-8 -*-

import cv2
import numpy as np

if __name__ == '__main__':
    rows = 240
    cols = 320

    with open("c:\\zhujian\\yuv\\img2_yuv", 'rb') as reader:
        bin_y = reader.read(rows * cols)
        print type(bin_y)
        print len(bin_y)

        num_y = np.fromstring(bin_y, np.uint8)
        print type(num_y)
        print num_y.shape
        img_y = np.reshape(num_y, (rows, cols))
        print type(img_y)
        print img_y.shape

        cv2.imshow("img", img_y)
        cv2.waitKey(0)

        bin_u = reader.read(rows * cols / 4)
        num_u = np.fromstring(bin_u, np.uint8)
        img_u = np.reshape(num_u, (rows / 2, cols / 2))

        bin_v = reader.read(rows * cols / 4)
        num_v = np.fromstring(bin_v, np.uint8)
        img_v = np.reshape(num_v, (rows / 2, cols / 2))

        enlarge_u = cv2.resize(img_u, dsize=(cols, rows), interpolation=cv2.INTER_CUBIC)
        print enlarge_u.shape
        enlarge_v = cv2.resize(img_v, dsize=(cols, rows), interpolation=cv2.INTER_CUBIC)

        dst = cv2.merge([img_y, enlarge_u, enlarge_v])
        bgr = cv2.cvtColor(dst, cv2.COLOR_YUV2BGR)

        cv2.imshow("dst", bgr)
        cv2.waitKey(0)

Python:关键函数介绍

  • num_y = np.fromstring(bin_y, np.uint8)

参考:numpy.fromstring

原型:

numpy.fromstring(string, dtype=float, count=-1, sep='')

函数功能:转换一维包含二进制数据或者文本数据的数组(A new 1-D array initialized from raw binary or text data in a string.

  • img_y = np.reshape(num_y, (rows, cols))

参考:numpy.reshape

原型:

numpy.reshape(a, newshape, order='C')[source]

函数功能:转换数组形状大小,更改后数据变换参考链接(Gives a new shape to an array without changing its data.


CYUV420pRGB

使用 C 语言进行 YUV 图像的操作,和 C++ 类似

  • 首先就是对图像文件进行读取,新建空白的 YUV 分量,读取二进制图像文件,写入图像中;
  • 在对 UV 分量进行放大,合并 3 个分量为一个 3 通道
  • 转换 YUV 图像为 RGB 图像

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cv.h>
#include <highgui.h>

int main(int argc, char* argv[]) {
    int width = 640;
    int height = 480;

    IplImage* img_Y = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1);
    IplImage* img_U = cvCreateImage(cvSize(width / 2, height / 2), IPL_DEPTH_8U, 1);
    IplImage* img_V = cvCreateImage(cvSize(width / 2, height / 2), IPL_DEPTH_8U, 1);
    IplImage* enlarge_U = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1);
    IplImage* enlarge_V = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1);
    IplImage* YUV = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);
    IplImage* RGB = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);

    // 打开二进制图像文件
    FILE* fp = NULL;
    if ((fp = fopen("c:\\zhujian\\yuv\\jpgimage1_image_640_480.yuv", "rb")) == NULL) {
        printf("Error: open file failed\n");
        exit(0);
    }
    // 读取Y分量
    for (int i = 0; i < height; i++) {
        uchar* ptr = (uchar*)(img_Y->imageData + i*img_Y->widthStep);
        for (int j = 0; j < width; j++) {
            ptr[j] = (uchar)fgetc(fp);
        }
    }
    // 读取U分量
    for (int i = 0; i < height / 2; i++) {
        uchar* ptr = (uchar*)(img_U->imageData + i*img_U->widthStep);
        for (int j = 0; j < width / 2; j++) {
            ptr[j] = (uchar)fgetc(fp);
        }
    }
    // 读取V分量
    for (int i = 0; i < height / 2; i++) {
        uchar* ptr = (uchar*)(img_V->imageData + i*img_V->widthStep);
        for (int j = 0; j < width / 2; j++) {
            ptr[j] = (uchar)fgetc(fp);
        }
    }
    // 读取文件结束后,关闭文件指针
    fclose(fp);

    // 放大U、V分量
    cvResize(img_U, enlarge_U, CV_INTER_CUBIC);
    cvResize(img_V, enlarge_V, CV_INTER_CUBIC);
    // 合并Y、U、V分量为一个3通道YUV图像
    cvMerge(img_Y, enlarge_U, enlarge_V, NULL, YUV);
    // 转换YUV图像为RGB图像
    cvCvtColor(YUV, RGB, CV_YUV2BGR);

    // 显示图像
    cvNamedWindow("img", CV_WINDOW_NORMAL);
    cvShowImage("img", RGB);
    cvWaitKey(0);

    cvDestroyWindow("img");
    cvReleaseImage(&RGB);
    cvReleaseImage(&YUV);
    cvReleaseImage(&enlarge_V);
    cvReleaseImage(&enlarge_U);
    cvReleaseImage(&img_V);
    cvReleaseImage(&img_U);
    cvReleaseImage(&img_Y);

    return 0;
}

当前运行环境是 Win7+VS2013+OpenCV2.4.13C 版本的 OpenCV 需要在程序结束前消除掉 IplImage 结构体,明显感觉在这个部分速度慢了很多


C:关键函数介绍

  • if ((fp = fopen("c:\\zhujian\\yuv\\jpgimage1_image_640_480.yuv", "rb")) == NULL) {

参考:fopen

原型:

FILE * fopen ( const char * filename, const char * mode );
  • ptr[j] = (uchar)fgetc(fp);

参考:fgetc

原型:
int fgetc ( FILE * stream );

函数功能:从流中读取一个字符(Get character from stream


YUV 图片资源

YUV420格式图片 和 视频 测试用

2张yuv格式图像

  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值