软件设备

Visual Studio2022

实验内容

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

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

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

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

内容1:添加动态字幕

  1. 首先,创建一个WIN32项目,如何创建项目?请参考这篇文章:https://blog.csdn.net/cds008/article/details/137749511?spm=1001.2014.3001.5501
  2. 导入我们的素材:

01新建项目导入素材

  1. (附:如果您感兴趣的话,可以做一下视频文件的显示内容,跟实验部分没关系,但是可以显示多种类型的视频格式,具体请参考教材中的代码,效果如下,当然我不是为了放我们gege的视频)02按照书本上代码实现视频的播放(不是实验内容)
  2. 回到实验部分,接下来我们要完成的是如何显示一个YUV的视频格式;首先,在framework.h最下面添加一个头文件。03头文件
  3. 然后,回到主函数,在最前面添加如下宏定义:04主函数头部
1
2
3
4
5
6
7
8
9
10
#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,所以您不妨先复制下来再说,待会都用得到的;05全局变量修改const

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //用于加载视频文件
    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. 在主函数中添加如下代码,用来打开视频文件和图像文件:06主函数代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//打开视频文件
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文件中添加);

    07计时器

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

08初始化计时器

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

09主窗口过程新增代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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. 显示效果就是这样子的:
10成功显示
  1. 接下来我们做字幕的部分,咱不着急,先从普通字幕开始做起,原理是什么呢?其实原理就是图片的下方有一个灰色的背景图,这个背景图只有文字那一部分不是黑的,那么我们在读取背景的时候,如果这个像素是黑的,那么我们就将这个像素的值用视频帧的rgb值来替代,如果这个像素不是黑的,那么就用背景图的像素来替代。那么如何添加你自己的字幕呢?我们可以用windows自带的画图工具去添加文字,然后保存为bmp文件即可,background.bmp是老师给的软件资源里面的,字幕别是黑色就行:12画图工具添加字幕
  2. 然后我们将第10步的代码中添加图像融合的步骤,像下面这样子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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.单字幕效果就是这样子的啦:

13单字幕效果

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

    14changeColor

  2. 然后再添加这样一个else if就可以啦,意思就是y小于这个值时显示绿色,y大于这个值时显示原来的字幕,(颜色是自己定义的)14增加一个变量让其随着帧数变化

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

16动态字幕效果

内容2:淡入淡出

  1. 添加全局变量:

17视频2变量

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

18打开视频2

  1. 修改主过程函数:

19修改WM_TIMER(淡入淡出+字幕版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
关键代码如下:
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. 效果如下:

    20效果

内容3:宽幅视频

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

    21添加宽幅视频

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    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. 效果如下:image-20240417222717925

到此,实验三完成了。

参考文章:

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

更多内容可以查看:https://blog.csdn.net/cds008/article/details/137892392

有问题可在评论区交流~