2018-12-11 17:36:03 LuohenYJ 阅读数 1351

12 yuv420转换为rgb(opencv mat)

yuv格式具有亮度信息和色彩信息分离的特点,但大多数图像处理操作都是基于RGB格式,而且自己造轮子工作量太大。因此通常都会将yuv转换为rgb,再用opencv等视觉库进行图像处理。

yuv转换为rgb有多种方法,比如公式法。但是推荐使用第三方库进行转换,比如ffmpeg,libyuv,opencv。其中ffmpeg是专门的视频音频处理软件,libyuv是谷歌开发的专门用于yuv基本图像处理(如旋转,缩放,格式转换)的视频库,libyuv主要用于android端。

ffmpeg,libyuv,opencv都是开源的。可以在网上查找资料。

本文简单介绍ffmpeg和libyuv的安装,opencv的安装教程很多就不介绍了。具体见文章:

https://blog.csdn.net/weixin_39393712/article/details/79583274


ffmpeg和libyuv的安装:

下载最新的ffmpeg的dev版和share版,ffmpeg严格区分x64和x86。下载网站为:

http://ffmpeg.zeranoe.com/builds/

Libyuv需要编译源文件,源文件地址:

https://chromium.googlesource.com/libyuv/libyuv/

https://github.com/seungrye/libyuv

编译步骤见:

https://blog.csdn.net/aabcd123456/article/details/78982528

获得源文件后先建立vs工程,然后将ffmpegdev版本文件夹中的include和lib整个目录复制到vs工程目录下。如图所示:

对于libyuv的libyuv文件和lib文件,将其分别复制到vs工程目录下的include目录和lib目录。如图所示:

通常include中包含的是所调用库头文件,lib包含的是静态链接库,当然ffmpeg需要将其动态链接库复制到vs工程目录下,即将ffmpeg,share版本文件夹中bin目录下对应的所有dll复制到项目路径下如图所示::

Dll和lib是windows系统下的动态链接库和静态链接库,linux系统下的静态链接库以.a结尾,linux系统下的动态链接库以.so或.so.y结尾。具体可以见文章:

https://www.cnblogs.com/general001/articles/3567446.html

对于ffmpeg,libyuv在linux系统下的编译使用,通过编译下载相关源代码,通过cmake或者make命令进行项目构建。推荐使用cmake软件,cmake非常有用,应有十分广泛。入门教程见:

http://www.cnblogs.com/52php/p/5681745.html

在windows平台下,通过vs就能够减少大量工作。vs平台链接ffmpeg和libyuv的头文件和lib文件,先在项目工程属性>C/C++>常规>附加包含目录,添加include目录,但是ffmpeg有许多错误,vs通常会开启SDL检查后,某些警告会成为错误。所以将sdl检查置为否。如下图所示:

接着在在项目工程属性>链接器>常规>附加库目录下,添加lib文件夹,如下图所示:

最后如果使用ffmpeg和libyuv库,需要添加头文件完成整个工作的配置。代码如下:

extern "C"

{

#include "include\libavcodec\avcodec.h"

#include "include\libavformat\avformat.h"

#include "include\libavutil\channel_layout.h"

#include "include\libavutil\common.h"

#include "include\libavutil\imgutils.h"

#include "include\libswscale\swscale.h"

#include "include\libavutil\imgutils.h"

#include "include\libavutil\opt.h"

#include "include\libavutil\mathematics.h"

#include "include\libavutil\samplefmt.h"

//libyuv

#include "include\libyuv\libyuv.h"

};

#pragma comment(lib, "avcodec.lib")

#pragma comment(lib, "avformat.lib")

#pragma comment(lib, "avdevice.lib")

#pragma comment(lib, "avfilter.lib")

#pragma comment(lib, "avutil.lib")

#pragma comment(lib, "postproc.lib")

#pragma comment(lib, "swresample.lib")

#pragma comment(lib, "swscale.lib")

//libyuv

#pragma comment(lib, "yuv.lib")

yuv420转rgb

接下来通过ffmpeg,libyuv,opencv实现yuv420转rgb,并进行性能分析。函数的代码如下所示:

/**
 * @file 12 yuv_transform.cpp
 * @author luohen
 * @brief YUV image transform to opencv rgb image
 * @date 2018-12-11
 * 
 */

#include "pch.h"
#include <iostream>
#include <opencv2/opencv.hpp>
#include <time.h>

extern "C"
{
#include "include\libavcodec\avcodec.h"
#include "include\libavformat\avformat.h"
#include "include\libavutil\channel_layout.h"
#include "include\libavutil\common.h"
#include "include\libavutil\imgutils.h"
#include "include\libswscale\swscale.h"
#include "include\libavutil\imgutils.h"
#include "include\libavutil\opt.h"
#include "include\libavutil\mathematics.h"
#include "include\libavutil\samplefmt.h"
//libyuv
#include "include\libyuv\libyuv.h"
};
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avdevice.lib")
#pragma comment(lib, "avfilter.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "postproc.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "swscale.lib")
//libyuv
#pragma comment(lib, "yuv.lib")

using namespace std;
using namespace cv;

/**
 * @brief
 *
 * @param pYUV		input yuv420 image
 * @param pBGR24 	output bgr24 image
 * @param width		width of input yuv420p image
 * @param height	height of input yuv420p image
 * @return
 */
bool ffmpeg_yuv2bgr(unsigned char *pYUV, unsigned char *pBGR24, int width, int height)
{
	AVPicture pFrameYUV, pFrameBGR;

	avpicture_fill(&pFrameYUV, pYUV, AV_PIX_FMT_YUV420P, width, height);
	avpicture_fill(&pFrameBGR, pBGR24, AV_PIX_FMT_BGR24, width, height);

	struct SwsContext *imgCtx = NULL;
	//初始化函数
	//原图高,宽,图像类型;输出图高,宽,图像类型;算法种类;其他
	imgCtx = sws_getContext(width, height, AV_PIX_FMT_YUV420P, width, height, AV_PIX_FMT_BGR24, SWS_BILINEAR, 0, 0, 0);

	if (imgCtx != NULL)
	{
		//执行函数
		//函数返回值;输入图像指针数组,图像颜色通道数组;扫描起点;扫描行数;输出图像指针数组,图像颜色通道数组;
		sws_scale(imgCtx, pFrameYUV.data, pFrameYUV.linesize, 0, height, pFrameBGR.data, pFrameBGR.linesize);
		//end
		if (imgCtx)
		{
			sws_freeContext(imgCtx);
			imgCtx = NULL;
		}
		return true;
	}
	else
	{
		sws_freeContext(imgCtx);
		imgCtx = NULL;
		return false;
	}
}

/**
 * @brief		transform function of ffmpeg
 *
 * @param w		width of input yuv420p image
 * @param h		height of input yuv420p image
 * @param pic	input yuv image
 * @return Mat	output rgb image(opencv mat)
 */
Mat yuv420_ffmpeg(int w, int h, unsigned char *pic)
{
	Mat bgrImg(h, w, CV_8UC3);
	unsigned char *pBGR24 = new unsigned char[w * h * 3];
	ffmpeg_yuv2bgr(pic, bgrImg.data, w, h);

	return bgrImg;
}

/**
 * @brief		transform function of libyuv
 *
 * @param w		width of input yuv420p image
 * @param h		height of input yuv420p image
 * @param pic	input yuv image
 * @return Mat	output rgb image(opencv mat)
 */
Mat yuv420_libyuv(int w, int h, unsigned char *pic)
{
	int size_src = w * h * 3 / 2;
	int size_dest = w * h * 4;

	//BGRA, A:Alpha(transparency,透明度)
	Mat matI420 = cv::Mat(h, w, CV_8UC4);

	libyuv::I420ToARGB((const uint8 *)pic, w,
					   (const uint8 *)(pic + w * h), w / 2,
					   (const uint8 *)(pic + w * h * 5 / 4), w / 2,
					   matI420.data, w * 4, w, h);
	//bgr
	Mat bgrImg;
	cvtColor(matI420, bgrImg, COLOR_BGRA2BGR);
	return bgrImg;
}

/**
 * @brief
 *
 * @param w
 * @param h
 * @param pic
 * @return Mat
 */
Mat yuv420_opencv(int w, int h, unsigned char *pic)
{
	//创建YUV mat
	cv::Mat yuvImg;
	yuvImg.create(h * 3 / 2, w, CV_8UC1);
	//数据保存为yuvImg.data
	memcpy(yuvImg.data, pic, w * h * 3 / 2 * sizeof(unsigned char));

	//转化为RGB图像
	cv::Mat bgrImg;
	cv::cvtColor(yuvImg, bgrImg, CV_YUV2BGR_I420);

	return bgrImg;
}

/**
 * @brief main
 *
 * @return int
 */
int main()
{
	clock_t start, end;
	double endtime;
	//Frequency of reading image
	int count_frame = 300;
	//视频路径
	char *url = (char *)"video/akiyo.yuv";
	int w = 352, h = 288;
	FILE *input_fp;
	if ((input_fp = fopen(url, "rb")) == NULL)
	{
		printf("%s open error!\n", url);
		return -1;
	}
	else
	{
		printf("%s open.\n", url);
	}

	unsigned char *pYuvBuf = new unsigned char[w * h * 3 / 2];

	fseek(input_fp, 0, SEEK_SET);
	//Timing starts
	start = clock();
	Mat ffmpeg_mat;
	for (int i = 0; i < count_frame; i++)
	{
		fread(pYuvBuf, sizeof(unsigned char), w * h * 3 / 2, input_fp);
		ffmpeg_mat = yuv420_ffmpeg(w, h, pYuvBuf);
	}

	//Timing end
	end = clock();
	endtime = (double)(end - start) / CLOCKS_PER_SEC;
	cout << "ffmpeg Total time:" << endtime << "s" << endl;
	cout << "ffmpeg Total time:" << endtime * 1000 << "ms" << endl;

	fseek(input_fp, 0, SEEK_SET);
	start = clock();
	Mat libyuv_mat;
	for (int i = 0; i < count_frame; i++)
	{
		fread(pYuvBuf, sizeof(unsigned char), w * h * 3 / 2, input_fp);
		libyuv_mat = yuv420_libyuv(w, h, pYuvBuf);
	}
	end = clock();
	endtime = (double)(end - start) / CLOCKS_PER_SEC;
	cout << "libyuv Total time:" << endtime << "s" << endl;			//s为单位
	cout << "libyuv Total time:" << endtime * 1000 << "ms" << endl; //ms为单位

	fseek(input_fp, 0, SEEK_SET);
	start = clock();
	Mat opencv_mat;
	for (int i = 0; i < count_frame; i++)
	{
		fread(pYuvBuf, sizeof(unsigned char), w * h * 3 / 2, input_fp);
		opencv_mat = yuv420_opencv(w, h, pYuvBuf);
	}
	end = clock();
	endtime = (double)(end - start) / CLOCKS_PER_SEC;
	cout << "opencv Total time:" << endtime << "s" << endl;
	cout << "opencv Total time:" << endtime * 1000 << "ms" << endl;

	system("pause");
	return 0;
}

调用函数为:

Mat yuv420_ffmpeg(int w, int h, unsigned char *pic);

Mat yuv420_libyuv(int w, int h, unsigned char *pic);

Mat yuv420_opencv(int w, int h, unsigned char *pic);

这段代码主要是分别用ffmpeg,libyuv,opencv实现yuv420转换为rgb,每种方法转换300张yuv420图像。对比三种方法转换所用时间,结果如下:

综合三种方法来说,ffmpeg速度最快,且ffmpeg最常用,因此推荐使用ffmpeg。如果仅仅对yuv图像进行处理或者android端,libyuv最为推荐。如果是安装ffmpeg或者libyuv较为麻烦,仅限于研究项目,建议使用opencv。

2018-12-11 17:15:52 LuohenYJ 阅读数 2892

目前数字图像处理技术已经应用生活各个方面,但是大部分教程都是利用第三方库(如opencv)对RGB图像格式进行处理。对于YUV图像格式的图像处理教程较少。于是博主搬运总结了多个大牛的文章,总结出来这个YUV图像像素处理入门教程。

这些大牛有:

雷霄骅(祝愿雷神在天堂安好)

https://blog.csdn.net/leixiaohua1020/article/details/50534150

其他两位朋友:

https://www.jianshu.com/p/8d60ad489bf4

https://github.com/kayawari/YUVProcessing/tree/master/sources


YUV图像空间简介

人们为了描述颜色,提出了多种颜色空间。常用的RGB颜色空间、YUV颜色空间、HSV颜色空间。被描述的颜色本身是客观独立的,不同的颜色空间只是从不同的角度去描述同一种对象。具体了解不同颜色空间可以看看这两篇文章:

https://www.cnblogs.com/xujianqing/p/5876875.html

https://www.cnblogs.com/justkong/p/6570914.html

总而言之,YUV颜色空间主要从亮度Y,色度U、浓度Y来描述颜色,其实亮度Y也可以理解成RGB图像中的灰度值。YUV颜色空间主要在多媒体流中使用较多。YUV空间最大的特点就是图像的亮度Y和色度UV是分离的。通常人对色度UV的敏感性要小于对亮度Y的敏感性。所以通常都会对UV进行压缩,甚至没有UV分量一样可以显示完整的图像。当只有Y分量的时候,图像表示为灰度图。

根据对UV压缩的程度不同和YUV的排列方式,人们提出了多种不同的YUV格式描述。要分析YUV图像,必须搞清楚到底所使用的YUV格式类似和图像大小。YUV格式具体可以参考这篇文章:

https://blog.csdn.net/asahinokawa/article/details/80596655

本教程主要针对yuv420P(又称I420)格式进行图像处理,其他YUV格式图像处理操作类似。yuv420P是较为常用的一种YUV图像格式。其内存结构图见下图。yuv420p它是先存完Y,再存U,最后存放V。YUV数量的比例为4:1:1。可以这样理解yuv420P,Y对应宽为w,高为h的图像,U和V对应宽为w/2,高为h/2的图像。因此描述一张高为h,宽的h图像,yuv420P所需空间大小为w*h*3/2个字节,而RGB空间通常需要w*h*3个字节。对于视频流传输YUV只需占用极少的频宽,学会对YUV格式图像进行处理是非常有用的。


YUV播放器

需要专用的YUV播放器展示YUV图像,最常用的YUV播放器是YUV Player Deluxe,下载地址为:

http://www.yuvplayer.com/

YUV Player Deluxe是一个免费的YUV文件播放器,但是需要实现注册。注册方法见该文章:

https://blog.csdn.net/lesen14/article/details/53178487

YUV图像以视频流的形式表示,由于在YUV格式的视频流中没有相关文件结构的信息,需要实现设置YUV的格式、帧宽、帧高以及帧率。展示视频akiyo,宽高为352,288,格式为yuv420P如下图所示:

在YUV Player Deluxe可以单独查看YUV各个分量的图像,当Y分量宽高为352,288;UV分量宽高为176,144。U、V分量在YUV播放器中也是当成Y分量进行播放的。


YUV图像处理所需知识

对YUV图像处理通常都是基于C/C++语言,也有人通过java,python实现。一般不需要使用任何第三方库,在本教程最后会介绍libyuv,ffmpeg等第三方库的使用。本教程主要是在vs2017下基于C语言进行YUV图像处理进行处理,会涉及C++少量知识,但是尽量不用C++,C++坑太多。

主要用到的C/C++标准库函数有:

fopen(文件打开函数);fread(读数据流函数);fwrite(写数据流函数);malloc(动态内存分配函数),malloc函数需要与free函数连用;fseek(重定义指针位置函数);还有new,delete函数,类似malloc,free函数。

所用视频样本为akiyo视频,涉及的图像分辨率主要有Qcif(176*144)、CIF(352*288)、D1(704*576)三种。

文章结构及代码

本文主要介绍了12种yuv基本图像处理操作,在接下来四篇文章进行讲述,主要涉及yuv图像的通道分离,图像截取,转换为rgb图等知识。链接如下:

YUV图像处理入门2

YUV图像处理入门3

YUV图像处理入门4

YUV图像处理入门5

文章所用到的代码、视频以及运行结果见:

https://download.csdn.net/download/luohenyj/10843907

https://github.com/luohenyueji/yuv420p-image-processing

2014-12-27 14:35:45 scottly1 阅读数 15306

RGB颜色空间

        关于RGB颜色空间,相信做图像处理的人基本都比较熟悉,还是说一下R、G、B三个分量,每个分量各占8位即一个字节,三个分量总共是3个字节,即24bit,三个分量可以组合出不同的颜色,即2^24 种。

       所以可以表示出的颜色数远远超过了俺们人类可以识别的范围。每个RGB分量其实都是表示成亮度,当三个相同时,就退化成我们所说的灰度图了,如三个分量都是0,此时就是黑色,三个分量都是255(8位可以表示的最大值),此时就是白色,下面一张图可以更形象的描述:



YUV颜色空间

        YUV三个分量Y表示(亮度), U 、V代表色度;

        关于YUV的解释看下面一篇文章中的分析,本人不再赘述:

        YUV格式详解


代码如下:

% BY SCOTT
% RGB2YUV
% Y = 0.299R + 0.587G + 0.114B 
% U = -0.147R - 0.289G + 0.436B 
% V = 0.615R - 0.515G - 0.100B  
% 
% YUV2RGB
% R = Y + 1.14V 
% G = Y - 0.39U - 0.58V 
% B = Y + 2.03U

clear all;
clc;
RGB = imread('test.jpg');
imshow(RGB);
RGB = mat2gray(RGB);
R = RGB(:,:,1);
G = RGB(:,:,2);
B = RGB(:,:,3);
x = size(RGB,1);
y = size(RGB,2);

% RGB2YUV
Y = 0.299*R + 0.587*G + 0.114*B;
U = -0.147*R- 0.289*G + 0.436*B;
V = 0.615*R - 0.515*G - 0.100*B;
YUV = cat(3, Y, U, V);
figure; imshow(YUV);

% YUV2RGB
RGB1 = zeros(size(RGB));
RGB1(:,:,1) = Y + 1.14 * V;
RGB1(:,:,2) = Y - 0.39 * U - 0.58 * V;
RGB1(:,:,3) = Y + 2.03 * U;
figure; imshow(RGB1)

% After YUV to RGB, The Image should same with original image.


运行结果分别如下所示:

                                                                        转换前RGB空间

  


转换后YUV空间

  


还原成RGB


2018-12-11 17:17:33 LuohenYJ 阅读数 1452

1 分离YUV420中YUV分量

本程序中的函数主要是将YUV420P视频数据流的第一帧图像中的Y、U、V三个分量分离开并保存成三个文件。函数的代码如下所示:

/**
 * @file	1 yuv_split.cpp
 * @author	luohen
 * @brief	split of yuv
 * @date	2018-12-07
 *
 */

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <iostream>

using namespace std;

/**
 * @brief
 *
 * @param url	location of input yuv420p file
 * @param w		width of input yuv420p file
 * @param h 	height of input yuv420p file
 * @return	int
 */
int yuv420_split(const char *url, int w, int h)
{
	//reading yuv image
	FILE *input_fp;
	if ((input_fp = fopen(url, "rb")) == NULL)
	{
		printf("%s open error!\n", url);
		return -1;
	}
	else
	{
		printf("%s open.\n", url);
	}

	//writing yuv image
	FILE *outputY_fp = fopen("video_result/output_420_y.y", "wb+");
	FILE *outputU_fp = fopen("video_result/output_420_u.y", "wb+");
	FILE *outputV_fp = fopen("video_result/output_420_v.y", "wb+");

	unsigned char *pic = new unsigned char[w * h * 3 / 2];

	//读数据,每次读取的字节数为sizeof(unsigned char)=1,共读取w*h*3/2次
	//reading data
	fread(pic, sizeof(unsigned char), w * h * 3 / 2, input_fp);
	//writing data
	//Y
	fwrite(pic, sizeof(unsigned char), w * h, outputY_fp);
	//U
	fwrite(pic + w * h, sizeof(unsigned char), w * h / 4, outputU_fp);
	//V
	fwrite(pic + w * h * 5 / 4, sizeof(unsigned char), w * h / 4, outputV_fp);

	//memory release and files closing
	delete[] pic;
	fclose(input_fp);
	fclose(outputY_fp);
	fclose(outputU_fp);
	fclose(outputV_fp);

	return 0;
}

/**
 * @brief main
 *
 * @return int
 */
int main()
{
	//Setting YUV information
	int state = yuv420_split("video/akiyo.yuv", 352, 288);
	return 0;
}

调用函数为:

int yuv420_split(const char *url, int w, int h);

从代码可以看出,程序先是读入一段视频数据流。通过fread函数读取w*h*3/2个unsigned char长度的数据实现第一帧图像的读取,unsigned char占一个字节(通过sizeof(unsigned char)可以查看到),也就是说fread函数读取w*h*3/2字节的数据就可以实现一帧图像的读取。

其中这段代码的fread函数是指每次读取1个字节的数据,一共读取w*h(y的长度)+(w/2*h/2)(u的长度)+ (w/2*h/2)(v的长度)=w*h*3/2次。

fread(pic, sizeof(unsigned char), w * h * 3 / 2, input_fp);

写成下列形式也是一样的。

fread(pic, w * h * 3 / 2*sizeof(unsigned char), 1, input_fp);

fwrite函数也是一样的用法。先存Y,再存UV。对于Y,U,Y分离后存储的格式可以是yuv格式也可以是单独的y格式。分离后的Y分量(352,288),U分量(176,144),V分量(176,144)。结果如下图所示:


2 YUV420灰度化

本程序中的函数主要是将YUV420P视频数据流的第一帧图像变为灰度图像。函数的代码如下所示:

/**
 * @file 2 yuv_gray.cpp
 * @author luohen
 * @brief gray scale of yuv
 * @date 2018-12-07
 *
 */

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <iostream>

using namespace std;

/**
 * @brief
 *
 * @param url	location of input yuv420p file
 * @param w		width of input yuv420p file
 * @param h 	height of input yuv420p file
 * @return	int
 */
int yuv420_gray(const char *url, int w, int h)
{
	//reading yuv image
	FILE *input_fp;
	if ((input_fp = fopen(url, "rb")) == NULL)
	{
		printf("%s open error!\n", url);
		return -1;
	}
	else
	{
		printf("%s open.\n", url);
	}
	//writing yuv image
	FILE *outputGray_fp = fopen("video_result/output_gray.yuv", "wb+");

	unsigned char *pic = new unsigned char[w * h * 3 / 2];

	fread(pic, sizeof(unsigned char), w * h * 3 / 2, input_fp);
	//Gray
	//把pic+w*h开始所有的数据置为128,色度分量取值范围是-128至127,量化后范围为0至255
	//uv=128,实现灰度化
	memset(pic + w * h, 128, w * h / 2);
	fwrite(pic, sizeof(unsigned char), w * h * 3 / 2, outputGray_fp);

	delete[] pic;
	fclose(input_fp);
	fclose(outputGray_fp);
	return 0;
}

/**
 * @brief main函数
 *
 * @return int
 */
int main()
{
	int state = yuv420_gray("video/akiyo.yuv", 352, 288);
	return 0;
}

调用函数为:

int yuv420_gray(const char *url, int w, int h);

这段函数主要是将U、V分量置为128,从而得到灰度图像。将U、V置为128而不是0,主要原因是U、V本来的取值范围大概是-127到128(可能更大),因为YUV的数据流是无符号的,所以将其量化为0到255。UV的最初取值范围可以通过RGB与YUV的转换公式理解。具体见文章:

https://www.cnblogs.com/armlinux/archive/2012/02/15/2396763.html

最终得到的YUV灰度图像,UV分量都存在只是为128而已。事实上只提取出Y分量效果也是一样的。结果如下图所示:


3 YUV420亮度减半

本程序中的函数主要是将YUV420P视频数据流的第一帧图像亮度减半。函数的代码如下所示:

/**
 * @file 3 yuv_halfy.cpp
 * @author luohen
 * @brief Half of Y value
 * @date 2018-12-07
 *
 */

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <iostream>

using namespace std;

/**
 * @brief
 *
 * @param url	location of input yuv420p file
 * @param w		width of input yuv420p file
 * @param h 	height of input yuv420p file
 * @return	int
 */
int yuv420_half(const char *url, int w, int h)
{
	//reading yuv image
	FILE *input_fp;
	if ((input_fp = fopen(url, "rb")) == NULL)
	{
		printf("%s open error!\n", url);
		return -1;
	}
	else
	{
		printf("%s open.\n", url);
	}

	//writing yuv image
	FILE *output_fp = fopen("video_result/output_half.yuv", "wb+");

	unsigned char *pic = new unsigned char[w * h * 3 / 2];

	fread(pic, sizeof(unsigned char), w * h * 3 / 2, input_fp);
	//half of Y
	for (int j = 0; j < w * h; j++)
	{
		unsigned char temp = pic[j] / 2;
		//printf("%d,\n",temp);
		pic[j] = temp;
	}
	fwrite(pic, 1, w * h * 3 / 2, output_fp);

	delete[] pic;
	fclose(input_fp);
	fclose(output_fp);
	return 0;
}

/**
 * @brief main函数
 *
 * @return int
 */
int main()
{
	int state = yuv420_half("video/akiyo.yuv", 352, 288);
	return 0;
}

调用函数为:

int yuv420_half(const char *url, int w, int h);

这段函数主要是将Y分量减半,从而得到灰度图像。而其他UV分量不需要调整。实际上YUV图像处理套路就是将YUV三个分量分别看成三张灰度图像,分别进行图像处理。除了YUV分量大小不一,其他与RGB像素处理一样。结果如下图所示:


4 YUV420添加边框

本程序中的函数主要是为YUV420P视频数据流的第一帧图像添加边框。函数的代码如下所示:

/**
 * @file 4 yuv_border.cpp
 * @author luohen
 * @brief  add a border to yuv
 * @date 2018-12-07
 *
 */

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <iostream>

using namespace std;

/**
 * @brief
 *
 * @param url	location of input yuv420p file
 * @param w		width of input yuv420p file
 * @param h 	height of input yuv420p file
 * @return	int
 */
int yuv420_border(const char *url, int w, int h)
{
	//reading yuv image
	FILE *input_fp;
	if ((input_fp = fopen(url, "rb")) == NULL)
	{
		printf("%s open error!\n", url);
		return -1;
	}
	else
	{
		printf("%s open.\n", url);
	}

	//writing yuv image
	FILE *output_fp = fopen("video_result/output_border.yuv", "wb+");

	//border width
	int border = 30;
	unsigned char *pic = new unsigned char[w * h * 3 / 2];

	//reading y
	fread(pic, 1, w * h * 3 / 2, input_fp);
	//y
	for (int j = 0; j < h; j++)
	{
		for (int k = 0; k < w; k++)
		{
			if (k < border || k >(w - border) || j < border || j >(h - border))
			{
				//0最暗,255最亮
				pic[j * w + k] = 0;
				//pic[j*w+k]=255;
			}
		}
	}

	fwrite(pic, 1, w * h * 3 / 2, output_fp);
	delete[] pic;
	fclose(input_fp);
	fclose(output_fp);
	return 0;
}

/**
 * @brief main函数
 *
 * @return int
 */
int main()
{
	int state = yuv420_border("video/akiyo.yuv", 352, 288);
	return 0;
}

调用函数为:

int yuv420_border(const char *url, int w, int h);

这段函数主要是调整图像边缘的Y分量数值,从而为图像添加边框。其中Y的初始值就是0-255,和灰度图一样,y为0时图像最暗,为255图像最暗。但是这段程序并没有实现严格意义上的添加图像边框,应该使得uv分量相同位置处的值为128(因为copy雷神代码。就懒得自己写了)。

结果如下图所示:

但是想了下,还是不能全copy雷神的代码。uv分量相同位置处的值为128,代码如下:

/**
 * @file 4 yuv_border.cpp
 * @author luohen
 * @brief  add a border to yuv
 * @date 2018-12-07
 *
 */

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <iostream>

using namespace std;

/**
 * @brief
 *
 * @param url	location of input yuv420p file
 * @param w		width of input yuv420p file
 * @param h 	height of input yuv420p file
 * @return	int
 */
int yuv420_border(const char *url, int w, int h)
{
	//reading yuv image
	FILE *input_fp;
	if ((input_fp = fopen(url, "rb")) == NULL)
	{
		printf("%s open error!\n", url);
		return -1;
	}
	else
	{
		printf("%s open.\n", url);
	}

	//writing yuv image
	FILE *output_fp = fopen("video_result/output_border.yuv", "wb+");

	//border width
	int border = 30;
	unsigned char *pic = new unsigned char[w * h * 3 / 2];

	//reading y
	fread(pic, 1, w * h * 3 / 2, input_fp);
	//y
	for (int j = 0; j < h; j++)
	{
		for (int k = 0; k < w; k++)
		{
			if (k < border || k >(w - border) || j < border || j >(h - border))
			{
				//0最暗,255最亮
				pic[j * w + k] = 0;
				//pic[j*w+k]=255;
			}
		}
	}
	//u
	for (int j = 0; j < h / 2; j++)
	{
		for (int k = 0; k < w / 2; k++)
		{
			if (k < border / 2 || k >(w / 2 - border / 2) || j < border / 2 || j >(h / 2 - border / 2))
			{
				pic[w*h + j * w / 2 + k] = 128;
				//pic[j*w+k]=255;
			}
		}
	}
	//v
	for (int j = 0; j < h / 2; j++)
	{
		for (int k = 0; k < w / 2; k++)
		{
			if (k < border / 2 || k >(w / 2 - border / 2) || j < border / 2 || j >(h / 2 - border / 2))
			{
				pic[w*h + w / 2 * h / 2 + j * w / 2 + k] = 128;
				//pic[j*w+k]=255;
			}
		}
	}

	fwrite(pic, 1, w * h * 3 / 2, output_fp);
	delete[] pic;
	fclose(input_fp);
	fclose(output_fp);
	return 0;
}

/**
 * @brief main函数
 *
 * @return int
 */
int main()
{
	int state = yuv420_border("video/akiyo.yuv", 352, 288);
	return 0;
}

其中对uv处理时border要除以2,因u、v只有y的四分之一大小。

对u,v赋值代码如下,因为yuv420是以数据流依次存储。所以u处理时数据u从pic[w*h]开始,而处理v从pic[w*h+w/2*h/2]开始。

pic[w*h + j * w / 2 + k] = 128;

pic[w*h + w / 2 * h / 2 + j * w / 2 + k] = 128;

结果如图所示:

2016-02-03 21:47:37 jaych 阅读数 2193

最近在项目的过程中需要用到 YUV 的 Y通道数据,但是原始数据图像为RGB格式,所以自己写了一个RGB2YUV的程序,并且进行优化,对此总结如下。

RGB2YUV 原理

RGB及YUV是两种不同的颜色空间,具体可以换算关系如下:

这里写图片描述

根据该换算关系,我们直接可以得到Y通道数据。

程序1

void rgb2yuv2(unsigned char *R,unsigned char *G,unsigned char *B,unsigned char *Y,int len)
{
//这里应有对指针的有效性判断,此处略
    for(int i=len;i!=0;i--){
        *Y++ = *R*0.299 + *G*0.587 + *B*0.114;
        R++;B++;G++;
    }

}

优化思路

不过这里涉及大量的浮点数运算,使得程序运行十分缓慢,为了加速,我们可以采用查表法及定点运算进行优化。

1、查表法

因为R/G/B通道的系数都已经固定了,只要预先求出[0..255]这些数值乘以系数对应的数值,在读取到对应R通道数据时,可以通过查表形式获取。

2、定点运算

由于系数均为小数点3位数,我们可以 先乘以299再除以1000 达到 乘以0.299 的目的。
同时,为了简化乘法,采用移位操作,向右移位10bit,则表示除以1024,对应的,我们只要在生成 查表数值 的时候,乘以1024即可。

综合1、2,我们可以先将:

对于R通道,[0..255] 乘以 0.299,再乘以1024,得到 256个对应的数值。G/B通道也采取类似操作。
在计算Y通道时,取出R/G/B通道对应的数值,求和并右移10位,得到最终数据。

程序2

int array[256];
// 生成各个通道对应的数值,并保存至表格
void genArray(float factor){ // factor是对应通道的系数。
    int i=0;
    for(i=0;i<256;i++){
        array[i]=(int)(factor*i*1024+0.5); 
    }
}

// RGB转YUV
void rgb2yuv(unsigned char *R,unsigned char *G,unsigned char *B,unsigned char *Y,int len)
{
//这里应有对指针的有效性判断,此处略
         for(int i=len;i!=0;i--){
               *Y++ = (RT[*R++]+GT[*G++]+BT[*B++])>>10;
        }

}

经验证,实际得到的图像与程序1得到的图像类似,误差为±1

颜色空间YUV简介

阅读数 12665

颜色空间YUV简介

博文 来自: fengbingchun
没有更多推荐了,返回首页