相關(guān)
視頻疊加算法-黑色素材疊加
視頻疊加算法-彩色素材疊加
視頻疊加算法-彩色加亮融合
視頻疊加算法-彩色均值融合
引言
如果想在之上疊加一個靜止圖片很簡單,像ffmpeg的濾鏡、opencv等都能實現(xiàn)。但是假如文字擁有動畫,而且文字出現(xiàn)比較頻繁,全部使用序列的png圖像會很大。例如如下的素材:

這樣一來,只能圖片壓縮成視頻再往原視頻上疊加。由于視頻解碼得來的是yuv格式,沒有alpha通道,不能像常規(guī)圖像疊加算法那樣,將alpha通道值作為疊加權(quán)重進行疊加。
于是寫了疊加白色內(nèi)容的素材視頻到另一個視頻之上的算法(針對Ycbcr420p)。
算法實現(xiàn)
原視頻:

基于ffmpeg框架,不過只是用到了部分ffmpeg的結(jié)構(gòu)體和函數(shù),算法主體還是用c語言自主實現(xiàn)的。代碼中有注釋,關(guān)鍵處也已經(jīng)標注拿出來解釋。
#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數(shù)據(jù)寫入文件
}
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.截圖一幀素材數(shù)據(jù):

觀察可得,其實對于一幀文字疊加的素材視頻,有大多區(qū)域是可以跳過的,因為都是純黑色,也就是我們想跳過的全透明(因為其編碼成了視頻,并且選用的yuv的顏色空間,所以其實其y值是16,并非png中的透明),所以申請了一行數(shù)據(jù)的buff設(shè)置成為純黑的y值,如果該行是純黑色,就跳過該行。其實最好的處理方式是將文字的素材做成適當大小的視頻,盡量將視頻尺寸降低,然后在疊加時候通過目標坐標指定素材需要疊加的位置。從而減少歷遍的次數(shù),有效提升算法效率。

選取文字邊緣的色塊(即圖片中的紅色方塊區(qū)域),觀察其y值發(fā)現(xiàn),黑色區(qū)域y值都是16,當然通過計算公式也可知道。但是在白色邊緣地帶,非我們想要摳出的區(qū)域仍然有大于16的y值出現(xiàn)。其y值出現(xiàn)的頻率圖大致是:

選用16作拐點的話,會出現(xiàn)大量黑邊,所以選用40作為拐點來分離出文字信息。但是也會引發(fā)邊緣區(qū)域消失,粉末狀的區(qū)域別忽視的問題。另外選用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值應(yīng)該是128,無色偏。所以素材視頻中y值越大于16就使原視頻中對應(yīng)像素uv值越趨近128.
存在問題
應(yīng)該將素材視頻生成的尺寸縮小,通過指定坐標的方法融合,從而提升算法效率。
會忽略素材視頻中的細節(jié),最終視頻中有鋸齒。
待疊加的只能有白色,如果其他顏色會根據(jù)顏色的Y值大小轉(zhuǎn)換為白色,彩色素材視頻的疊加參看。