最终卷!实验三!2024 CSU 多媒体技术与应用实验三(详细版)

软件设备

Visual Studio2022

实验内容

1、YUV视频文件显示程序为基础,结合图像融合原理与方法,设计并实现一个给YUV视频添加动态字幕的程序(类似卡拉OK动态字幕)。

2、设计实现一个视频特效程序,能够将两个视频实现淡入淡出。

3、设计实现一个视频特效程序,能够将两个(或多个)视频拼接成一个宽幅视频。

4、试将水波纹模拟程序和YUV视频显示程序结合起来,实现在视频中叠加水波纹(选做)。

内容1:添加动态字幕

  1. 首先,创建一个WIN32项目,如何创建项目?请参考这篇文章:全新超详细!2024 CSU 多媒体技术与应用实验一(详细版)-CSDN博客

  2. 导入我们的素材:

  1. (附:如果您感兴趣的话,可以做一下视频文件的显示内容,跟实验部分没关系,但是可以显示多种类型的视频格式,具体请参考教材中的代码,效果如下,当然我不是为了放我们gege的视频)

  2. 回到实验部分,接下来我们要完成的是如何显示一个YUV的视频格式;首先,在framework.h最下面添加一个头文件。

  3. 然后,回到主函数,在最前面添加如下宏定义:

#define MAX_LOADSTRING 100
#define IMAGE_WIDTH    352
#define IMAGE_HIGHT    288
​
typedef struct COLOR
{
    BYTE b;
    BYTE g;
    BYTE r;
}RGBCOLOR;
  1. 设置用于视频加载和背景图片(background.bmp)的全局变量,复制下面的代码块中的即可,图片给的少了融合图像的部分,我们内容1只用到了det_image,内容二开始会用到det_image2,所以您不妨先复制下来再说,待会都用得到的;

    //用于加载视频文件
    static FILE* ifp;  //file pointer
    const char* filename = "foreman.yuv";
    static BYTE      mybuf[45621248]; //arry,store the video file
    static BYTE* pBity, * pBitu, * pBitv;
    static int       y[288][352], u[144][176], v[144][176];
    //背景图像
    static FILE* ifpback;
    const char* filenameback = "background.bmp";
    static unsigned char mybufback[IMAGE_WIDTH * IMAGE_HIGHT * 3 + 100];
    static BITMAPFILEHEADER* pbmfh;
    static BITMAPINFO* pbmi;
    static BYTE* pbits;
    static int              cxDib, cyDib;
    //融合图像
    static COLOR det_image[IMAGE_HIGHT][IMAGE_WIDTH];//要显示的目标图像1
    static COLOR det_image2[IMAGE_HIGHT][IMAGE_WIDTH];//要显示的目标图像2
    static int n = 0;

  2. 在主函数中添加如下代码,用来打开视频文件和图像文件:

//打开视频文件
fopen_s(&ifp, filename, "r");
fread(mybuf, 45621248, 1, ifp);
pBity = mybuf;
pBitu = mybuf + 352 * 288;
pBitv = mybuf + 352 * 288 + 176 * 144;
​
//打开图像文件
fopen_s(&ifpback, filenameback, "r");
fread(mybufback, 307200, 1, ifpback);
pbmfh = (BITMAPFILEHEADER*)mybufback;
pbmi = (BITMAPINFO*)(pbmfh + 1);
pbits = (BYTE*)pbmfh + pbmfh->bfOffBits;
cxDib = pbmi->bmiHeader.biWidth;
cyDib = pbmi->bmiHeader.biHeight;
  1. 添加一个计时器(你懂的,在Resource.h文件中添加);

  1. 初始化计时器,没错,就是放在InitInstance函数下面,实验二中提到过:

  1. 主窗口过程函数新增代码(图片没截取全,代码我放在下面了)(我这儿代码跟书上的不太一样,注意别出现图片倒转的情况了)原理解释:其实就是读取每一帧的YUV矩阵,然后转成了RGB,要么从头读要么从尾读,都可以的。注意!!!!HDC的定义处别写错了,以给的代码块为准:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    PAINTSTRUCT ps;
    HDC hdc;//!!!!!!!!!!!!!!!!!!!!!!!
    int changeColor;
    switch (message)
    {
        //新添加代码的开始处
    case WM_TIMER:
    hdc = GetDC(hWnd);
    n = n + 1;
​
    if (n > 299) n = 0;
    pBity = pBity + (352 * 288 + 2 * (176 * 144)) * n;
    pBitu = pBity + 352 * 288;
    pBitv = pBitu + 176 * 144;
​
    //read a new frame of the yuv file
    for (int i = 0; i < 144; i++)
        for (int j = 0; j < 176; j++)
        {
            u[i][j] = *(pBitu + j + 176 * (i));
            v[i][j] = *(pBitv + j + 176 * (i));
        }
    //read y,and translate yuv int rgb and display the pixel
    for (int i = 0; i < 288; i++)
        for (int j = 0; j < 352; j++)
        {
            //read y
            y[i][j] = *(pBity + j + (i) * 352);
            y2[i][j] = *(pBity2 + j + (i) * 352);
            //translate
            int r = (298 * (y[i][j] - 16) + 409 * (v[i / 2][j / 2] - 128) + 128) >> 8;
            if (r < 0) r = 0;
            if (r > 255) r = 255;
            int g = (298 * (y[i][j] - 16) - 100 * (u[i / 2][j / 2] - 128) - 208 * (v[i / 2][j / 2] - 128) +
                128) >> 8;
            if (g < 0) g = 0;
            if (g > 255) g = 255;
            int b = (298 * (y[i][j] - 16) + 516 * (u[i / 2][j / 2] - 128) + 128) >> 8;
            if (b < 0) b = 0;
            if (b > 255) b = 255;
            //存储到目标图像矩阵
            det_image[288 - i - 1][j].r = r;
            det_image[288 - i - 1][j].g = g;
            det_image[288 - i - 1][j].b = b;
        }
    // 显示当前帧
    SetDIBitsToDevice(hdc,
        30,
        20,
        352,
        288,
        0,
        0,
        0,
        288,
        det_image,
        pbmi,
        DIB_RGB_COLORS);
    // 恢复指向图像数据首字节
    pBity = mybuf;
    ReleaseDC(hWnd, hdc);
    break;
    // 新添加代码结束
  1. 显示效果就是这样子的:

  1. 接下来我们做字幕的部分,咱不着急,先从普通字幕开始做起,原理是什么呢?其实原理就是图片的下方有一个灰色的背景图,这个背景图只有文字那一部分不是黑的,那么我们在读取背景的时候,如果这个像素是黑的,那么我们就将这个像素的值用视频帧的rgb值来替代,如果这个像素不是黑的,那么就用背景图的像素来替代。那么如何添加你自己的字幕呢?我们可以用windows自带的画图工具去添加文字,然后保存为bmp文件即可,background.bmp是老师给的软件资源里面的,字幕别是黑色就行:

  2. 然后我们将第10步的代码中添加图像融合的步骤,像下面这样子:

case WM_TIMER:
hdc = GetDC(hWnd);
n = n + 1;
if (n>299) n = 0;
pBity = pBity + (352 * 288 + 2 * (176 * 144))*n;
pBitu = pBity + 352 * 288;
pBitv = pBitu + 176 * 144;
//read a new frame of the yuv file
for (int i = 0; i<144; i++)
for (int j = 0; j<176; j++)
{
u[i][j] = *(pBitu + j + 176 * (i));
v[i][j] = *(pBitv + j + 176 * (i));
}
//read y,and translate yuv int rgb and display the pixel
for (int i = 0; i<288; i++)
for (int j = 0; j<352; j++)
{
//read y
y[i][j] = *(pBity + j + (i)* 352);
//translate
int r = (298 * (y[i][j] - 16) + 409 * (v[i / 2][j / 2] - 128) + 128) >> 8;
if (r<0) r = 0;
if (r>255) r = 255;
int g = (298 * (y[i][j] - 16) - 100 * (u[i / 2][j / 2] - 128) - 208 * (v[i / 2][j / 2] - 128) + 
128) >> 8;
if (g<0) g = 0;
if (g>255) g = 255;
int b = (298 * (y[i][j] - 16) + 516 * (u[i / 2][j / 2] - 128) + 128) >> 8;
if (b<0) b = 0;
if (b>255) b = 255;
// 取字幕图标图像的像素值
int rback = *(pbits + 2 + j * 3 + (cyDib - i - 1)*cxDib * 3);
int gback = *(pbits + 1 + j * 3 + (cyDib - i - 1)*cxDib * 3);
int bback = *(pbits + 0 + j * 3 + (cyDib - i - 1)*cxDib * 3);
 // 如果当前字幕图标图像像素值是黑色,就传送视频像素值到目标图像
if (rback ==0 && gback ==0 && bback ==0)
{
det_image[288 - i - 1][j].r = r;
det_image[288 - i - 1][j].g = g;
det_image[288 - i - 1][j].b = b;
}else//否则,就传送字幕图标图像的当前像素值到目标图像
{
det_image[288 - i - 1][j].r = rback;
det_image[288 - i - 1][j].g = gback;
det_image[288 - i - 1][j].b = bback;
}
}
SetDIBitsToDevice(hdc,
30,
20,
352,
288,
0,
0,
0,
288,
det_image,
pbmi,
DIB_RGB_COLORS);
pBity = mybuf; // let pBity to point at the first place of the file
ReleaseDC(hWnd, hdc);
break;

14.单字幕效果就是这样子的啦:

  1. 然后我们来完成动态字幕的效果吧!首先!我们添加一个随机变量changeColor来随时间的变化而变化,让changeColor随着n变化即可。

  2. 然后再添加这样一个else if就可以啦,意思就是y小于这个值时显示绿色,y大于这个值时显示原来的字幕,(颜色是自己定义的)

  1. 动态字幕的效果如下:

内容2:淡入淡出

  1. 添加全局变量:

  1. 主函数中打开视频2(跟内容1如出一辙的):

  1. 修改主过程函数:

关键代码如下:
    for (int i = 0; i < 288; i++)
    for (int j = 0; j < 352; j++)
    {
        //read y
        y[i][j] = *(pBity + j + (i) * 352);
        y2[i][j] = *(pBity2 + j + (i) * 352);
        //translate
        int r = (298 * (y[i][j] - 16) + 409 * (v[i / 2][j / 2] - 128) + 128) >> 8;
        if (r < 0) r = 0;
        if (r > 255) r = 255;
        int g = (298 * (y[i][j] - 16) - 100 * (u[i / 2][j / 2] - 128) - 208 * (v[i / 2][j / 2] - 128) +
            128) >> 8;
        if (g < 0) g = 0;
        if (g > 255) g = 255;
        int b = (298 * (y[i][j] - 16) + 516 * (u[i / 2][j / 2] - 128) + 128) >> 8;
        if (b < 0) b = 0;
        if (b > 255) b = 255;
​
        //处理第2个视频
        int r2 = (298 * (y2[i][j] - 16) + 409 * (v2[i / 2][j / 2] - 128) + 128) >> 8;
        if (r2 < 0) r2 = 0;
        if (r2 > 255) r2 = 255;
        int g2 = (298 * (y2[i][j] - 16) - 100 * (u2[i / 2][j / 2] - 128) - 208 * (v2[i / 2][j / 2] - 128) +
            128) >> 8;
        if (g2 < 0) g2 = 0;
        if (g2 > 255) g2 = 255;
        int b2 = (298 * (y2[i][j] - 16) + 516 * (u2[i / 2][j / 2] - 128) + 128) >> 8;
        if (b2 < 0) b2 = 0;
        if (b2 > 255) b2 = 255;
        //定义透明度参数
        double para = n / 300.0;
        两帧图像的当前像素的融合,结果放入目标图像矩阵中
        det_image[288 - i - 1][j].r = r * (1 - para) + r2 * para;
        det_image[288 - i - 1][j].g = g * (1 - para) + g2 * para;
        det_image[288 - i - 1][j].b = b * (1 - para) + b2 * para;
​
        //取字幕图标图像的像素值
        int rback = *(pbits + 2 + j * 3 + (cyDib - i - 1) * cxDib * 3);
        int gback = *(pbits + 1 + j * 3 + (cyDib - i - 1) * cxDib * 3);
        int bback = *(pbits + 0 + j * 3 + (cyDib - i - 1) * cxDib * 3);
        //如果当前字幕图标图像像素值是黑色,就传送视频像素值到目标图像
        if (rback == 0 && gback == 0 && bback == 0)
        {
            /*det_image[288 - i - 1][j].r = r;
            det_image[288 - i - 1][j].g = g;
            det_image[288 - i - 1][j].b = b;*/
            det_image[288 - i - 1][j].r = r * (1 - para) + r2 * para;
            det_image[288 - i - 1][j].g = g * (1 - para) + g2 * para;
            det_image[288 - i - 1][j].b = b * (1 - para) + b2 * para;
        }
        //实现单字幕注释这个else if
        //左半部分的字幕颜色改变部分
        else if (j < changeColor) {
            det_image[288 - i - 1][j].r = 0;
            det_image[288 - i - 1][j].g = gback + 100;
            det_image[288 - i - 1][j].b = bback;
        }
​
        //右半部分颜色不改变
        else{
            det_image[288 - i - 1][j].r = rback;
            det_image[288 - i - 1][j].g = gback;
            det_image[288 - i - 1][j].b = bback;
        }
        
    }
  1. 效果如下:

内容3:宽幅视频

  1. 这个也比较简单,在上面代码的基础上,直接修改主过程函数如下:

  2. for (int i = 0; i < 288; i++)
        for (int j = 0; j < 352; j++)
        {
            //read y
            y[i][j] = *(pBity + j + (i) * 352);
            y2[i][j] = *(pBity2 + j + (i) * 352);
            //translate
            int r = (298 * (y[i][j] - 16) + 409 * (v[i / 2][j / 2] - 128) + 128) >> 8;
            if (r < 0) r = 0;
            if (r > 255) r = 255;
            int g = (298 * (y[i][j] - 16) - 100 * (u[i / 2][j / 2] - 128) - 208 * (v[i / 2][j / 2] - 128) +
                128) >> 8;
            if (g < 0) g = 0;
            if (g > 255) g = 255;
            int b = (298 * (y[i][j] - 16) + 516 * (u[i / 2][j / 2] - 128) + 128) >> 8;
            if (b < 0) b = 0;
            if (b > 255) b = 255;
    ​
            //处理第2个视频
            int r2 = (298 * (y2[i][j] - 16) + 409 * (v2[i / 2][j / 2] - 128) + 128) >> 8;
            if (r2 < 0) r2 = 0;
            if (r2 > 255) r2 = 255;
            int g2 = (298 * (y2[i][j] - 16) - 100 * (u2[i / 2][j / 2] - 128) - 208 * (v2[i / 2][j / 2] - 128) +
                128) >> 8;
            if (g2 < 0) g2 = 0;
            if (g2 > 255) g2 = 255;
            int b2 = (298 * (y2[i][j] - 16) + 516 * (u2[i / 2][j / 2] - 128) + 128) >> 8;
            if (b2 < 0) b2 = 0;
            if (b2 > 255) b2 = 255;
            //定义透明度参数
            double para = n / 300.0;
            两帧图像的当前像素的融合,结果放入目标图像矩阵中
            det_image[288 - i - 1][j].r = r * (1 - para) + r2 * para;
            det_image[288 - i - 1][j].g = g * (1 - para) + g2 * para;
            det_image[288 - i - 1][j].b = b * (1 - para) + b2 * para;
    ​
            //宽幅视频
            det_image[288 - i - 1][j].r = r;
            det_image[288 - i - 1][j].g = g;
            det_image[288 - i - 1][j].b = b;
            det_image2[288 - i - 1][j].r = r2;
            det_image2[288 - i - 1][j].g = g2;
            det_image2[288 - i - 1][j].b = b2;
    ​
    ​
    ​
            //取字幕图标图像的像素值
            int rback = *(pbits + 2 + j * 3 + (cyDib - i - 1) * cxDib * 3);
            int gback = *(pbits + 1 + j * 3 + (cyDib - i - 1) * cxDib * 3);
            int bback = *(pbits + 0 + j * 3 + (cyDib - i - 1) * cxDib * 3);
            //如果当前字幕图标图像像素值是黑色,就传送视频像素值到目标图像
            if (rback == 0 && gback == 0 && bback == 0)
            {
                /*det_image[288 - i - 1][j].r = r;
                det_image[288 - i - 1][j].g = g;
                det_image[288 - i - 1][j].b = b;*/
                det_image[288 - i - 1][j].r = r * (1 - para) + r2 * para;
                det_image[288 - i - 1][j].g = g * (1 - para) + g2 * para;
                det_image[288 - i - 1][j].b = b * (1 - para) + b2 * para;
            }
            //实现单字幕注释这个else if
            //左半部分的字幕颜色改变部分
            else if (j < changeColor) {
                det_image[288 - i - 1][j].r = 0;
                det_image[288 - i - 1][j].g = gback + 100;
                det_image[288 - i - 1][j].b = bback;
            }
    ​
            //右半部分颜色不改变
            else{
                det_image[288 - i - 1][j].r = rback;
                det_image[288 - i - 1][j].g = gback;
                det_image[288 - i - 1][j].b = bback;
            }
            
        }
    // 显示当前帧
    SetDIBitsToDevice(hdc,
        30,
        20,
        352,
        288,
        0,
        0,
        0,
        288,
        det_image,
        pbmi,
        DIB_RGB_COLORS);
    SetDIBitsToDevice(hdc,
        382,
        20,
        352,
        288,
        0,
        0,
        0,
        288,
        det_image2,
        pbmi,
        DIB_RGB_COLORS);
    1. 效果如下:

到此,实验三完成了。

参考文章:

https://www.codenong.com/cs106759209/#google_vignette

更多内容可以查看:https://cds007.github.io/

有问题可在评论区交流~

  • 19
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值