音视频技术FFmpegIOS ffmpeg

视频叠加算法-白色素材叠加

2016-04-03  本文已影响1179人  Don_

相关

视频叠加算法-黑色素材叠加
视频叠加算法-彩色素材叠加
视频叠加算法-彩色加亮融合
视频叠加算法-彩色均值融合

引言

如果想在之上叠加一个静止图片很简单,像ffmpeg的滤镜、opencv等都能实现。但是假如文字拥有动画,而且文字出现比较频繁,全部使用序列的png图像会很大。例如如下的素材:

白色素材

这样一来,只能图片压缩成视频再往原视频上叠加。由于视频解码得来的是yuv格式,没有alpha通道,不能像常规图像叠加算法那样,将alpha通道值作为叠加权重进行叠加。
于是写了叠加白色内容的素材视频到另一个视频之上的算法(针对Ycbcr420p)。

算法实现

原视频:

input

基于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;

}

输出效果:

output

算法讨论

1.截图一帧素材数据:

frame

观察可得,其实对于一帧文字叠加的素材视频,有大多区域是可以跳过的,因为都是纯黑色,也就是我们想跳过的全透明(因为其编码成了视频,并且选用的yuv的颜色空间,所以其实其y值是16,并非png中的透明),所以申请了一行数据的buff设置成为纯黑的y值,如果该行是纯黑色,就跳过该行。其实最好的处理方式是将文字的素材做成适当大小的视频,尽量将视频尺寸降低,然后在叠加时候通过目标坐标指定素材需要叠加的位置。从而减少历遍的次数,有效提升算法效率。

frame&data

选取文字边缘的色块(即图片中的红色方块区域),观察其y值发现,黑色区域y值都是16,当然通过计算公式也可知道。但是在白色边缘地带,非我们想要抠出的区域仍然有大于16的y值出现。其y值出现的频率图大致是:

概率曲线

选用16作拐点的话,会出现大量黑边,所以选用40作为拐点来分离出文字信息。但是也会引发边缘区域消失,粉末状的区域别忽视的问题。另外选用200作为最大值。其处理算法为:

if y< 40
忽视
else if y< 200
按比例趋近白色
else
完全替换原视频y值

  1. ycbcr计算公式为:

Y’ = 0.257R' + 0.504G' + 0.098B' + 16
Cb' = -0.148
R' - 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.

存在问题

上一篇下一篇

猜你喜欢

热点阅读