[图像处理] YUV图像处理入门4

9 yuv420图像截取

本程序中的函数主要是对YUV420P视频数据流的第一帧图像进行截取。类似opencv中的rect函数,函数的代码如下所示:

/**
 * @file 9 yuv_clip.cpp
 * @author luohen
 * @brief yuv image clip
 * @date 2018-12-08
 *
 */

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

using namespace std;

/**
 * @brief
 *
 * @param w width of input yuv420p file
 * @param h height of input yuv420p file
 * @param sx clipped initial position x of origin y image
 * @param sy clipped initial position y of origin y image
 * @param sw wdith of clipped image
 * @param sh height of clipped image
 * @param url location of input yuv420p file
 * @return int
 */
int yuv420_clip(int w, int h, int sx, int sy, int sw, int sh, const char *url)
{
	//reading yuv file
	FILE *input_fp;
	//writing yuv file
	FILE *output_fp = fopen("video_result/output_clip.yuv", "wb+");

	if ((input_fp = fopen(url, "rb")) == NULL)
	{
		printf("%s open error!\n", url);
		return -1;
	}
	else
	{
		printf("%s open.\n", url);
	}

	//origin image
	unsigned char *pic = new unsigned char[w * h * 3 / 2];
	//clipped image
	unsigned char *pic_clip = new unsigned char[sw * sh * 3 / 2];

	// y length of origin image
	int size_y = w * h;
	// yu length of origin image
	int size_yu = w * h + w * h / 4;
	// y length of clipped image
	int size_sy = sw * sh;
	// yu length of clipped image
	int size_syu = sw * sh + sw * sh / 4;

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

	//y clip
	for (int j = 0; j < sh; j++)
	{
		for (int k = 0; k < sw; k++)
		{
			pic_clip[j * sw + k] = pic[(sx + j) * w + (sy + k)];
		}
	}

	//sw_uv,sh_uv
	int sw_uv = sw / 2;
	int sh_uv = sh / 2;

	//u clip
	for (int j = 0; j < sh_uv; j++)
	{
		for (int k = 0; k < sw_uv; k++)
		{
			pic_clip[size_sy + j * sw_uv + k] = pic[size_y + (sx / 2 + j) * w / 2 + (sy / 2 + k)];
		}
	}

	//v clip
	for (int j = 0; j < sh_uv; j++)
	{
		for (int k = 0; k < sw_uv; k++)
		{
			pic_clip[size_syu + j * sw_uv + k] = pic[size_yu + (sx / 2 + j) * w / 2 + (sy / 2 + k)];
		}
	}

	fwrite(pic_clip, 1, sw * sh * 3 / 2, output_fp);

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

/**
 * @brief main
 *
 * @return int
 */
int main()
{
	int state = yuv420_clip(352, 288, 60, 50, 176, 144, "video/akiyo.yuv");
	return 0;
}

调用函数为:

int yuv420_clip(int w, int h, int sx, int sy, int sw, int sh, const char *url);

这段代码指的是对于宽高为w,h的yuv图像,相对于y分量来说截取宽为sw,高为sh的部分,截取部分左上角顶点坐标为(sx,sy);对于uv分量来截取宽高分别为sw/2,sh/2,截取部分左上角顶点坐标为(sx/2,sy/2)。然后分别对y,u,v分量截图。

这段程序sx=60,sy=50,sw=176,sh=144。最终的结果如下图:


10 yuv420图像帧差法运动检测

本程序中的函数主要是截取YUV420P视频数据流的第1帧图像和第200图像,通过帧差法对两幅图像的y分量进行对比实现运动检测。类似opencv中的absdiff函数,函数的代码如下所示:

/**
 * @file 10 yuv_framedifference.cpp
 * @author luohen
 * @brief Frame difference method of y
 * @date 2018-12-10
 *
 */

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

using namespace std;

/**
 * @brief
 *
 * @param pFrame1		the first frame
 * @param pFrame2		the second frame
 * @param pResult		the result image
 * @param w				width of input yuv420p file
 * @param h				height of input yuv420p file
 * @param yThreshold	threshold value
 */
void yuv420_Framedifference(unsigned char *pFrame1, unsigned char *pFrame2, unsigned char *pResult, int w, int h, int yThreshold)
{
	//the first frame
	unsigned char *Y1 = new unsigned char[w * h];
	//the second frame
	unsigned char *Y2 = new unsigned char[w * h];

	//copy y
	memcpy(Y1, pFrame1, w * h);
	memcpy(Y2, pFrame2, w * h);
	for (int y = 0; y < h; y++)
	{
		for (int x = 0; x < w; x++)
		{
			int i = y * w + x;
			//diff
			int temp = abs((int)Y1[i] - (int)Y2[i]);
			if (temp > yThreshold)
			{
				pResult[i] = 255;
			}
			else
			{
				pResult[i] = 0;
			}
		}
	}
	delete[] Y1;
	delete[] Y2;
}

/**
 * @brief main function
 *
 * @return int
 */
int main()
{
	//the yuv image size
	int w = 352, h = 288;

	//reading yuv file
	FILE *input_fp;
	//writing yuv file
	//the first yuv image
	FILE *fp1 = fopen("video_result/akiyo1.yuv", "wb+");
	//the second yuv image
	FILE *fp2 = fopen("video_result/akiyo2.yuv", "wb+");
	//the binary image of frame difference
	FILE *fp3 = fopen("video_result/output_diff.y", "wb+");

	const char * url = "video/akiyo.yuv";
	if ((input_fp = fopen(url, "rb")) == NULL)
	{
		printf("%s open error!\n", url);
		return 0;
	}
	else
	{
		printf("%s open.\n", url);
	}

	//result
	unsigned char *pResult = new unsigned char[w * h];
	//the first image
	unsigned char *pFrame1 = new unsigned char[w * h * 3 / 2];
	//the second image
	unsigned char *pFrame2 = new unsigned char[w * h * 3 / 2];

	//used for read frames
	unsigned char originalFrame[352 * 288 * 3 / 2];
	//reading image for a loop
	for (int i = 0; i < 200; i++)
	{
		//fread function automatically moves the pointer
		//take the first frame
		if (i == 0)
		{
			fread(pFrame1, w * h * 3 / 2, 1, input_fp);
		}
		//take the second frame
		if (i == 199)
		{
			fread(pFrame2, w * h * 3 / 2, 1, input_fp);
		}
		//Skip intermediate frame
		else
		{
			fread(originalFrame, w * h * 3 / 2, 1, input_fp);
		}
	}

	/* another way to read images
	fread(pFrame1, w * h * 3 / 2, 1, input_fp);
	int p = 199 * w*h * 3 / 2;
	//move the pointer
	fseek(input_fp, p, SEEK_SET);
	fread(pFrame2, w * h * 3 / 2, 1, input_fp);
	*/

	//the threshold is 30
	yuv420_Framedifference(pFrame1, pFrame2, pResult, w, h, 30);

	fwrite(pFrame1, 1, w * h * 3 / 2, fp1);
	fwrite(pFrame2, 1, w * h * 3 / 2, fp2);
	fwrite(pResult, 1, w * h, fp3);

	delete[] pResult;
	delete[] pFrame1;
	delete[] pFrame2;

	fclose(input_fp);
	fclose(fp1);
	fclose(fp2);
	fclose(fp3);
}

调用函数为:

 void yuv420_Framedifference(unsigned char *pFrame1, unsigned char *pFrame2, unsigned char *pResult, int w, int h, int yThreshold);

这段代码中忽略了uv分量的影响。计算第一帧y分量与第二帧y分量的差。然后通过类似opencv中的threshold函数,对于帧差得到的图像中像素值大于给定阈值yThreshold的点,其像素值置为255,小于阈值的点像素值置为0。也就是说像素值为255的区域就是运动区域,为0的区域就是背景区域。这是最基本的运动检测算法,当然这段代码改进的地方有:通过uv分量的差综合得到背景区域;或者引入三帧差法。

对于主函数中,由于akiyo.yuv是一个视频流,通过fread函数每读完一张图像,fread中的文件指针会直接移动到当前文件读取位置,这样就可以用fread函数读完视频中所有的图像。

当然这种方式比较傻,可以直接通过下列代码,先读取第一帧图像,然后通过fseek函数,将fread的文件指针,从SEEK_SET(文件开始处)移动到第199帧图像的结尾处,再用fread函数读取第200帧图像。

fseek(input_fp, p, SEEK_SET);

fread(pFrame1, w * h * 3 / 2, 1, input_fp);

int p = 199 * w*h * 3 / 2;

fseek(input_fp, p, SEEK_SET);

fread(pFrame2, w * h * 3 / 2, 1, input_fp);

所提取的第1帧图像、第200帧图像以及帧差结果的二值图如图所示:

11 二值图像膨胀和腐蚀

本程序中的函数主要是对帧差法提取的运动区域二值图像进行膨胀和腐蚀操作,函数的代码如下所示:

/**
 * @file 11 yuv_dilate_erode.cpp
 * @author luohen
 * @brief dilate and erode of yuv image
 * @date 2018-12-10
 * 
 */

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

using namespace std;

/**
 * @brief erode
 * 
 * @param pFrame		the input binary image
 * @param pdilateResult	the output binary image
 * @param kernel		kernel of erode
 * @param w				width of image
 * @param h				height of image
 */
void yuv420_erode(unsigned char *pFrame, unsigned char *pErodeResult, int kernel, int w, int h)
{
	//copy data
	unsigned char *srcImg = new unsigned char[w * h];
	memcpy((char *)srcImg, (char *)pFrame, w * h);

	//kernel除中心之外像素点个数
	int nErodeThreshold = kernel * kernel - 1;
	//对于中点要erode像素区域步长
	int erodeDist = (kernel - 1) / 2;
	//ignore edge point
	for (int i = erodeDist; i < h - erodeDist; i++)
	{
		for (int j = erodeDist; j < w - erodeDist; j++)
		{
			// 如果值不为0才进行处理
			if (srcImg[i * w + j] != 0)
			{
				int iPointCount = 0;
				// 根据此点的邻域判断此点是否需要删除
				for (int r = -erodeDist; r <= erodeDist; r++)
				{
					for (int c = -erodeDist; c <= erodeDist; c++)
					{
						//统计不为0的个数
						if (srcImg[(i + r) * w + j + c] != 0)
						{
							iPointCount++;
						}
					}
				}
				// 如果邻域中不为0的个数小于阈值,则中心点像素值设置为0
				if (iPointCount < nErodeThreshold)
				{
					pErodeResult[i * w + j] = 0;
				}
				else
				{
					pErodeResult[i * w + j] = 255;
				}
			}
			else
			{
				pErodeResult[i * w + j] = 0;
			}
		}
	}
	delete[] srcImg;
	return;
}

/**
 * @brief dilate
 * 
 * @param pFrame		the input binary image
 * @param pdilateResult	the output binary image
 * @param kernel		kernel of dilate
 * @param w				width of image
 * @param h				height of image
 */
void yuv420_dilate(unsigned char *pFrame, unsigned char *pdilateResult, int kernel, int w, int h)
{
	//copy data
	unsigned char *srcImg = new unsigned char[w * h];
	memcpy((char *)srcImg, (char *)pFrame, w * h);

	//对于中点要erode像素区域步长
	int erodeDist = (kernel - 1) / 2;
	//ignore edge point
	for (int i = erodeDist; i < h - erodeDist; i++)
	{
		for (int j = erodeDist; j < w - erodeDist; j++)
		{
			//对所有点进行判断
			int iPointCount = 0;
			// 根据此点的邻域判断此点是否需要删除
			for (int r = -erodeDist; r <= erodeDist; r++)
			{
				for (int c = -erodeDist; c <= erodeDist; c++)
				{
					//统计不为0的个数
					if (srcImg[(i + r) * w + j + c] != 0)
					{
						iPointCount++;
					}
				}
			}
			// 如果邻域中各像素点值都为0,则中心点像素值设置为0
			if (iPointCount == 0)
			{
				pdilateResult[i * w + j] = 0;
			}
			else
			{
				pdilateResult[i * w + j] = 255;
			}
		}
	}
	delete[] srcImg;
	return;
}

/**
 * @brief
 *
 * @param pFrame1		the first frame
 * @param pFrame2		the second frame
 * @param pResult		the result image
 * @param w				width of input yuv420p file
 * @param h				height of input yuv420p file
 * @param yThreshold	threshold value
 */
void yuv420_Framedifference(unsigned char *pFrame1, unsigned char *pFrame2, unsigned char *pResult, int w, int h, int yThreshold)
{
	//the first frame
	unsigned char *Y1 = new unsigned char[w * h];
	//the second frame
	unsigned char *Y2 = new unsigned char[w * h];
	;
	//copy y
	memcpy(Y1, pFrame1, w * h);
	memcpy(Y2, pFrame2, w * h);
	for (int y = 0; y < h; y++)
	{
		for (int x = 0; x < w; x++)
		{
			int i = y * w + x;
			//diff
			int temp = abs((int)Y1[i] - (int)Y2[i]);
			if (temp > yThreshold)
			{
				pResult[i] = 255;
			}
			else
			{
				pResult[i] = 0;
			}
		}
	}
	delete[] Y1;
	delete[] Y2;
}

/**
 * @brief main function
 *
 * @return int
 */
int main()
{
	//the yuv image size
	int w = 352, h = 288;

	//reading yuv file
	FILE *input_fp;
	//writing yuv file
	//the first yuv image
	FILE *fp1 = fopen("video_result/akiyo1.yuv", "wb+");
	//the second yuv image
	FILE *fp2 = fopen("video_result/akiyo2.yuv", "wb+");
	//the binary image of frame difference
	FILE *fp3 = fopen("video_result/akiyo_erode.y", "wb+");

	const char *url = "video/akiyo.yuv";
	if ((input_fp = fopen(url, "rb")) == NULL)
	{
		printf("%s open error!\n", url);
		return 0;
	}
	else
	{
		printf("%s open.\n", url);
	}

	//result
	unsigned char *pResult = new unsigned char[w * h];
	//the first image
	unsigned char *pFrame1 = new unsigned char[w * h * 3 / 2];
	//the second image
	unsigned char *pFrame2 = new unsigned char[w * h * 3 / 2];

	//used for read frames
	unsigned char originalFrame[352 * 288 * 3 / 2];
	//reading image for a loop
	for (int i = 0; i < 200; i++)
	{
		//fread function automatically moves the pointer
		//take the first frame
		if (i == 0)
		{
			fread(pFrame1, w * h * 3 / 2, 1, input_fp);
		}
		//take the second frame
		if (i == 199)
		{
			fread(pFrame2, w * h * 3 / 2, 1, input_fp);
		}
		//Skip intermediate frame
		else
		{
			fread(originalFrame, w * h * 3 / 2, 1, input_fp);
		}
	}

	/* another way to read images
	fread(pFrame1, w * h * 3 / 2, 1, input_fp);
	int p = 199 * w*h * 3 / 2;
	//move the pointer
	fseek(input_fp, p, SEEK_SET);
	fread(pFrame2, w * h * 3 / 2, 1, input_fp);
	*/

	//the threshold is 30
	yuv420_Framedifference(pFrame1, pFrame2, pResult, w, h, 30);

	//kernel size is 3 X 3.
	yuv420_erode(pResult, pResult, 3, w, h);
	//yuv420_dilate(pResult, pResult, 3, w, h);

	fwrite(pFrame1, 1, w * h * 3 / 2, fp1);
	fwrite(pFrame2, 1, w * h * 3 / 2, fp2);
	fwrite(pResult, 1, w * h, fp3);

	delete[] pResult;
	delete[] pFrame1;
	delete[] pFrame2;

	fclose(input_fp);
	fclose(fp1);
	fclose(fp2);
	fclose(fp3);
}

函数调用代码为:

void yuv420_dilate(unsigned char *pFrame, unsigned char *pdilateResult, int kernel, int w, int h);

void yuv420_erode(unsigned char *pFrame, unsigned char *pErodeResult, int kernel, int w, int h);

腐蚀膨胀的原理和作用见文章;

https://blog.csdn.net/qq_25847123/article/details/73744575

简单。这段代码是opencv中dilate和erode函数中的简化版。不同之处是,这段代码所用处理核只能是矩形的。此外对于边缘点,opencv中会用类似深度学习卷积神经网络中的padding操作,在周围填充点;这段代码就忽略边缘的点,直接处理中间的像素点。

代码很简单,通常对yuv图像处理就是读图,分离yuv分量,然后分别对像素点进行处理。

膨胀腐蚀的结果如图所示:

posted @ 2022-12-18 22:55  落痕的寒假  阅读(144)  评论(0编辑  收藏  举报