最终卷!实验三!2024 CSU 多媒体技术与应用实验三(详细版)
软件设备
Visual Studio2022
实验内容
1、YUV视频文件显示程序为基础,结合图像融合原理与方法,设计并实现一个给YUV视频添加动态字幕的程序(类似卡拉OK动态字幕)。
2、设计实现一个视频特效程序,能够将两个视频实现淡入淡出。
3、设计实现一个视频特效程序,能够将两个(或多个)视频拼接成一个宽幅视频。
4、试将水波纹模拟程序和YUV视频显示程序结合起来,实现在视频中叠加水波纹(选做)。
内容1:添加动态字幕
- 首先,创建一个WIN32项目,如何创建项目?请参考这篇文章:https://blog.csdn.net/cds008/article/details/137749511?spm=1001.2014.3001.5501
- 导入我们的素材:
- (附:如果您感兴趣的话,可以做一下视频文件的显示内容,跟实验部分没关系,但是可以显示多种类型的视频格式,具体请参考教材中的代码,效果如下,当然我不是为了放我们gege的视频)
- 回到实验部分,接下来我们要完成的是如何显示一个YUV的视频格式;首先,在framework.h最下面添加一个头文件。
- 然后,回到主函数,在最前面添加如下宏定义:
1 |
|
设置用于视频加载和背景图片(background.bmp)的全局变量,复制下面的代码块中的即可,图片给的少了融合图像的部分,我们内容1只用到了det_image,内容二开始会用到det_image2,所以您不妨先复制下来再说,待会都用得到的;
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;在主函数中添加如下代码,用来打开视频文件和图像文件:
1 | //打开视频文件 |
添加一个计时器(你懂的,在Resource.h文件中添加);
初始化计时器,没错,就是放在InitInstance函数下面,实验二中提到过:
- 主窗口过程函数新增代码(图片没截取全,代码我放在下面了)(我这儿代码跟书上的不太一样,注意别出现图片倒转的情况了)原理解释:其实就是读取每一帧的YUV矩阵,然后转成了RGB,要么从头读要么从尾读,都可以的。注意!!!!HDC的定义处别写错了:
1 | LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) |
- 显示效果就是这样子的:

- 接下来我们做字幕的部分,咱不着急,先从普通字幕开始做起,原理是什么呢?其实原理就是图片的下方有一个灰色的背景图,这个背景图只有文字那一部分不是黑的,那么我们在读取背景的时候,如果这个像素是黑的,那么我们就将这个像素的值用视频帧的rgb值来替代,如果这个像素不是黑的,那么就用背景图的像素来替代。那么如何添加你自己的字幕呢?我们可以用windows自带的画图工具去添加文字,然后保存为bmp文件即可,background.bmp是老师给的软件资源里面的,字幕别是黑色就行:
- 然后我们将第10步的代码中添加图像融合的步骤,像下面这样子:
1 | case WM_TIMER: |
14.单字幕效果就是这样子的啦:
然后我们来完成动态字幕的效果吧!首先!我们添加一个随机变量changeColor来随时间的变化而变化,让changeColor随着n变化即可。
然后再添加这样一个else if就可以啦,意思就是y小于这个值时显示绿色,y大于这个值时显示原来的字幕,(颜色是自己定义的)
动态字幕的效果如下:
内容2:淡入淡出
- 添加全局变量:
- 主函数中打开视频2(跟内容1如出一辙的):
- 修改主过程函数:
1 | 关键代码如下: |
效果如下:
内容3:宽幅视频
这个也比较简单,在上面代码的基础上,直接修改主过程函数如下:
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
101for (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);- 效果如下:
- 效果如下:
到此,实验三完成了。
参考文章:
https://www.codenong.com/cs106759209/#google_vignette
更多内容可以查看:https://blog.csdn.net/cds008/article/details/137892392
有问题可在评论区交流~
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 CDS007的个人博客!
评论