相关
视频叠加算法-黑色素材叠加
视频叠加算法-彩色素材叠加
视频叠加算法-彩色加亮融合
视频叠加算法-彩色均值融合
引言
如果想在之上叠加一个静止图片很简单,像ffmpeg的滤镜、opencv等都能实现。但是假如文字拥有动画,而且文字出现比较频繁,全部使用序列的png图像会很大。例如如下的素材:
这样一来,只能图片压缩成视频再往原视频上叠加。由于视频解码得来的是yuv格式,没有alpha通道,不能像常规图像叠加算法那样,将alpha通道值作为叠加权重进行叠加。
于是写了叠加白色内容的素材视频到另一个视频之上的算法(针对Ycbcr420p)。
算法实现
原视频:
基于ffmpeg框架,不过只是用到了部分ffmpeg的结构体和函数,算法主体还是用c语言自主实现的。代码中有注释,关键处也已经标注拿出来解释。
#include <stdio.h>
#include <math.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/frame.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
#include <libavutil/pixfmt.h>
#ifdef __cplusplus
};
#endif
int init_frame(AVFrame* frame,int width,int height,uint8_t* dst_buff);
int mergeyuv(char* file, char* light,char* lightout,int width,int height);
int write_yuvframe(AVFrame *pFrame,FILE *out);
int frame_cover_white( AVFrame* dst_frame, AVFrame* src_frame, AVFrame* cover_frame);
int main (char** args, int argv)
{
char* file_in = "test.yuv";
char* light = "light.yuv";
char* file_out = "lightout.yuv";
int width = 480;
int height = 480;
mergeyuv(file_in ,light,file_out,width,height);
return 0;
}
//融合整个yuv文件
int mergeyuv(char* file, char* light,char* lightout,int width,int height)
{
//初始化帧、buff以及文件
AVFrame* readframe,*lightframe,*outframe;
readframe = av_frame_alloc();
lightframe = av_frame_alloc();
outframe = av_frame_alloc();
FILE* readfile = (FILE*)fopen(file,"rb");
FILE* lightfile = (FILE*)fopen(light,"rb");
FILE* outfile = (FILE*)fopen(lightout,"wb+");
if(lightfile==NULL||readframe==NULL||outfile==NULL)
return -1;
int length = width*height*3/2;
uint8_t* readbuff = (uint8_t*)malloc(length);
uint8_t* lightbuff = (uint8_t*)malloc(length);
uint8_t* outbuff = (uint8_t*)malloc(length);
init_frame(readframe,width,height,readbuff);
init_frame(lightframe,width,height,lightbuff);
init_frame(outframe,width,height,outbuff);
while(fread(readbuff,1,length,readfile))//读取原视频帧
{
if(fread(lightbuff,1,length,lightfile))//读取待叠加的帧
{
puts("mrege one frame");
frame_cover_white(readframe,readframe,lightframe);//算法是原址的
write_yuvframe(readframe,outfile);//将yuv数据写入文件
}
else
break;
}
fclose(readfile);
fclose(lightfile);
fclose(outfile);
free(readbuff);
free(lightbuff);
free(outbuff);
av_frame_free(&readframe);
av_frame_free(&lightframe);
av_frame_free(&outframe);
return 0;
}
//init 一帧
int init_frame(AVFrame* frame,int width,int height,uint8_t* dst_buff)
{
if(!avpicture_fill((AVPicture *) frame, dst_buff, AV_PIX_FMT_YUV420P,width,height))
{
puts("init frame error");
av_frame_free(&frame);
return NULL;
}
frame->width=width;
frame->height=height;
frame->format = AV_PIX_FMT_YUV420P;
return 0;
}
//叠加帧
int frame_cover_white( AVFrame* dst_frame, AVFrame* src_frame, AVFrame* cover_frame)
{
if(dst_frame == NULL || src_frame == NULL || cover_frame == NULL)//检查合法性
{
puts("frame_cover_white input or output frame is NULL");
return -1;
}
char* tempblack = (char*)malloc(sizeof(char) * cover_frame->linesize[0]);//参看算法讨论1
memset(tempblack, 16, cover_frame->linesize[0]);
int w2 = cover_frame->width;
int h2 = cover_frame->height;
int i = 0,j = 0;
int temp,a,u,v,a2;
float rat;
int yindex = 0;
int yindex2,uindex2;
for(i = 0;i<h2;i++)
{
yindex = i*cover_frame->linesize[0];
if(strncmp((char*)cover_frame->data[0]+yindex,tempblack,cover_frame->linesize[0])==0)//参看注1
{
continue;
}
yindex2 = i*src_frame->linesize[0];
uindex2 = (i>>1)*src_frame->linesize[1];
for(j=0;j<w2;j++)
{
a2 = cover_frame->data[0][yindex];
if(a2 <= 40)//参看算法讨论2
{
yindex++;
yindex2++;
if(j%2!=0)
uindex2++;
continue;
}
a = src_frame->data[0][yindex2];
rat = a2*a2/40000.0;//参看算法讨论2
if(rat > 1)
rat = 1;
temp = a+(255-a)*rat;
dst_frame->data[0][yindex2] = (char)temp;
u = src_frame->data[1][uindex2];
v = src_frame->data[2][uindex2];
dst_frame->data[1][uindex2]= u+(int)((128-u)*rat);//参看算法讨论3
dst_frame->data[2][uindex2]= v+(int)((128-v)*rat);
yindex++;
yindex2++;
if(j%2!=0)
uindex2++;
}
}
free(tempblack);
return 0;
}
int write_yuvframe(AVFrame *pFrame,FILE *out)
{
int height = pFrame->height,width = pFrame->width;
if(pFrame==NULL)
{
puts("error:write frame is null");
return -1;
}
if(out == NULL)
{
puts("give write file is null");
return -1;
}
int j = 0;
for (j = 0; j < height; j++)
fwrite(pFrame->data[0] + j * pFrame->linesize[0], 1, width, out);//参看注4
for (j = 0; j < height / 2; j++)
fwrite(pFrame->data[1] + j * pFrame->linesize[1], 1, width / 2, out);
for (j = 0; j < height / 2; j++)
fwrite(pFrame->data[2] + j * pFrame->linesize[2], 1, width / 2,out);
return 0;
}
输出效果:
算法讨论
1.截图一帧素材数据:
观察可得,其实对于一帧文字叠加的素材视频,有大多区域是可以跳过的,因为都是纯黑色,也就是我们想跳过的全透明(因为其编码成了视频,并且选用的yuv的颜色空间,所以其实其y值是16,并非png中的透明),所以申请了一行数据的buff设置成为纯黑的y值,如果该行是纯黑色,就跳过该行。其实最好的处理方式是将文字的素材做成适当大小的视频,尽量将视频尺寸降低,然后在叠加时候通过目标坐标指定素材需要叠加的位置。从而减少历遍的次数,有效提升算法效率。
选取文字边缘的色块(即图片中的红色方块区域),观察其y值发现,黑色区域y值都是16,当然通过计算公式也可知道。但是在白色边缘地带,非我们想要抠出的区域仍然有大于16的y值出现。其y值出现的频率图大致是:
选用16作拐点的话,会出现大量黑边,所以选用40作为拐点来分离出文字信息。但是也会引发边缘区域消失,粉末状的区域别忽视的问题。另外选用200作为最大值。其处理算法为:
if y< 40
忽视
else if y< 200
按比例趋近白色
else
完全替换原视频y值
- ycbcr计算公式为:
Y’ = 0.257R' + 0.504G' + 0.098B' + 16
Cb' = -0.148R' - 0.291G' + 0.439B' + 128
Cr' = 0.439R' - 0.368G' - 0.071B' + 128
'R = 1.164(Y’-16) + 1.596(Cr'-128)
B' = 1.164(Y’-16) + 2.017(Cb'-128)
G' = 1.164(Y’-16) - 0.813(Cr'-128) - 0.392(Cb'-128)
黑色和白色uv值应该是128,无色偏。所以素材视频中y值越大于16就使原视频中对应像素uv值越趋近128.
存在问题
应该将素材视频生成的尺寸缩小,通过指定坐标的方法融合,从而提升算法效率。
会忽略素材视频中的细节,最终视频中有锯齿。
待叠加的只能有白色,如果其他颜色会根据颜色的Y值大小转换为白色,彩色素材视频的叠加参看。