【视频的动态对比】

news2025/4/4 17:24:54

写在前面:本博客仅作记录学习之用,部分图片来自网络,如需引用请注明出处,同时如有侵犯您的权益,请联系删除!


文章目录

  • 前言
  • 图像修复
    • 人脸与关键点检测
    • 修复图像
    • 修复视频
  • 动态对比
  • 添加声音
    • 获取原视频音频
    • 融合声音
  • 视频转GIF
  • 致谢


前言

图像复原是指从已损坏、模糊或不完整的退化图像中恢复出清晰、完整的信息。照片用于记录和保存珍贵的回忆。然而,由于时间的流逝和照片质量的变化,一些照片可能会受到损坏或模糊。图像复原可以帮助修复退化照片,保护和保存重要的回忆。

无论的图像生成、修复、去污等等,如生成对抗方法;亦或是图像分割,如unet。对于方法的结果都需要进行有效的展示,凸显方法的优越性能,往往细节的部分才是我们所更加关注的。但往往在图像本身很难观察到这些细节,因此局部放大和动态对比都是展现的有效手段。

对于局部放大可参考:【学习记录】图片局部放大

本文主要是实现视频的动态对比,实现以下的效果:

修复前
修复后
动态对比

图像修复

人脸与关键点检测

人脸检测和关键点,主要依赖于Dlib。cnn_face_detection_model_v1()get_frontal_face_detector(),都是人脸检测算法,用于从图像或视频中检测和定位人脸,各有优势。

  • cnn_face_detection_model_v1()是深度学习算法。get_frontal_face_detector() 使用的是基于 Haar 特征的级联分类器。

  • cnn_face_detection_model_v1()通常具有较高的准确度和鲁棒性,能够识别各种角度、姿态和光照条件下的人脸。get_frontal_face_detector() 可能在某些特殊情况下表现较差。

  • cnn_face_detection_model_v1()在部署时需要更多的计算资源。get_frontal_face_detector() 比较轻量级,训练和部署相对较简单。

  • cnn_face_detection_model_v1()具有更好的准确度和鲁棒性,它通常更适用于对人脸进行更精细的分析和识别,如人脸验证、表情识别等。get_frontal_face_detector() 则更适用于一些对实时性要求较高的应用场景,如实时视频中的人脸检测。

如果具有GPU建议使用cnn_face_detection_model_v1(),相反建议使用get_frontal_face_detector(),毕竟还是很快,不是块一星半点,尤其在处理较大的尺寸的图像上。

# torch判断是都GPU是否可用
    if torch.cuda.is_available():
        face_detector = dlib.cnn_face_detection_model_v1('./pretrain_models/mmod_human_face_detector.dat')
    else:
        face_detector = dlib.get_frontal_face_detector()  # cpu
    lmk_predictor = dlib.shape_predictor('./pretrain_models/shape_predictor_5_face_landmarks.dat')
    template_path = './pretrain_models/FFHQ_template.npy'

当然也可以使用轻量的人脸检测网络来实现,如构建高效、精准的人脸识别系统——RetinaFace、FaceNet和MySQL基础上的实践与总结里面提到了常见的对齐,RetinaFace关键点检测方法以及五官模板的制作。

def detect_video_align_faces(img, face_detector, lmk_predictor, template_path, template_scale=2, size_threshold=999):
    # 对齐输出的大小,取决了修复网络的输入大小,此处512为例
    align_out_size = (512, 512)
    # 对齐的模板,这里采用FFHQ的对齐模型
    ref_points = np.load(template_path) / template_scale
    # 检测关键点
    face_dets = face_detector(img, 1)
	# 是否有人脸
    assert len(face_dets) > 0, 'No faces detected'
    aligned_faces = []
    tform_params = []
    for det in face_dets:
    	# 框的信息,cnn_face_detection_model_v1与get_frontal_face_detector的些许区别
        if isinstance(face_detector, dlib.cnn_face_detection_model_v1):
            rec = det.rect  # for cnn_face_detection_model_v1
        else:
            rec = det # for get_frontal_face_detector
        # 太大不做处理
        if rec.width() > size_threshold or rec.height() > size_threshold:
            print('Face is too large')
            break

        landmark_points = lmk_predictor(img, rec)
        single_points = []
        # 获取5关键点的坐标
        for i in range(5):
            single_points.append([landmark_points.part(i).x, landmark_points.part(i).y])
        single_points = np.array(single_points)
        # 按照模板进行对齐,并返回仿射变换参数以变换回原图
        tform = trans.SimilarityTransform()  # 相似变换
        tform.estimate(single_points, ref_points)
        tmp_face = trans.warp(img, tform.inverse, output_shape=align_out_size, order=3)
        return [aligned_faces, tform_params]

修复图像

传入上述对齐的人脸进行修复。

hq_faces = preact(aligned_faces, model)

结合使用的模型,将所有人脸进行恢复,并返回。其中图像的转tensor的流程与模型训练的保持一致即可。

def preact(LQ_faces, model):
    hq_faces = []
    for lq_face in LQ_faces:
        with torch.no_grad():
            lq_tensor = torch.tensor(lq_face.transpose(2, 0, 1)) / 255. * 2 - 1
            lq_tensor = lq_tensor.unsqueeze(0).float().to(model.device)
            output_SR = model.netG(lq_tensor)
        hq_faces.append(utils.tensor_to_img(output_SR))
    return hq_faces

再把修复的图像贴回原图,需要对齐时候的参数,此外加了一点mask避免贴回去的边界过于明显。

def video_faces_back(img, hq_faces, tform_params, upscale=1):
    h, w = img.shape[:2]
    img = cv2.resize(img, (int(w*upscale), int(h*upscale)), interpolation=cv2.INTER_CUBIC)
    for hq_img, tform in zip(hq_faces, tform_params):
        tform.params[0:2,0:2] /= upscale
        back_img = trans.warp(hq_img/255., tform, output_shape=[int(h*upscale), int(w*upscale)], order=3) * 255
        # blur mask to avoid border artifacts
        mask = (back_img == 0) 
        mask = cv2.blur(mask.astype(np.float32), (5, 5))
        mask = (mask > 0)
        img = img * mask + (1 - mask) * back_img 
    return img.astype(np.uint8)

修复视频

视频就是若干视频帧组成,因此可将每帧进行复原即可实现视频的复原。这种做法摒弃了帧间的关联,基于视频复原的方法效果回更好。这里使用修复网络为PSFR-GAN,想要了解的可前往【论文学习】PSFR-GAN:一种结合几何先验的渐进式复原网络。

使用OpenCV可实现读取每帧视频,接着将其进行修复组合为视频即可。

def enhence_video:
	# 构建模型,加载权重
	model = create_model()  
    model.load_pretrain_models()
    print('加载权重成功!')
    model.eval()
    
    # 检测和保存的视频路径
    video_path = 'head.mp4'
    video_save_path = "head_enhance.mp4"
	
	# 获取视频属性,帧率、帧宽高、编码
    capture = cv2.VideoCapture(video_path)
    video_fps = capture.get(cv2.CAP_PROP_FPS)
    frames = capture.get(cv2.CAP_PROP_FRAME_COUNT)
    width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))  # 获取原视频的宽
    height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))  # 获取原视频的高
    # 如果capture.get(cv2.CAP_PROP_FOURCC)报错,就直接使用(*"mp4v")
    # fourcc = int(capture.get(cv2.CAP_PROP_FOURCC))  # 视频的编码
    fourcc = cv2.VideoWriter_fourcc(*"mp4v")
    out = cv2.VideoWriter(video_save_path, fourcc, video_fps, (width, height))
    print('开始恢复视频!')
    
    # 计帧数
    count = 0
    while True:
        count += 1
        print(f"{count}/{frames}帧")
        ret, frame = capture.read()
        if ret:
        	# BGR -> RGB,很重要
            frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2RGB)
            
            # 调用上节的函数实现对齐、修复、贴回原图
            aligned_faces, tform_params = detect_video_align_faces(frame, face_detector, lmk_predictor, template_path)
            hq_faces = preact(aligned_faces, model)
            frame = video_faces_back(frame, hq_faces, tform_params, upscale=1)
            
			# RGB -> BGR,OpenCV 支持BGR
            frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
            out.write(frame)
        else:
            break
    capture.release()
    out.release()
    print(f'恢复结束!请在{video_save_path}查看结果')

动态对比

至此,原本的视频和修复的视频都得到了,接下就是如何实现动态的对比。

思路:同时读取两个视频将对应的视频帧拼接并绘制滑动的区分线。

def contrast_video(video1_path, video2_path, save_path):
    # 判断路径是否存在
    if not os.path.exists(video1_path) or not os.path.exists(video1_path):
        raise ValueError("视频路径不存在,请检查视频路径。")
    save_video_path = save_path
    # 读取两个视频
    capture1 = cv2.VideoCapture(video1_path)
    capture2 = cv2.VideoCapture(video2_path)

    # 获取视频属性,帧数、帧率、宽高
    video1_fps = capture1.get(cv2.CAP_PROP_FPS)			 # 获取原视频的帧率
    width1 = int(capture1.get(cv2.CAP_PROP_FRAME_WIDTH))  # 获取原视频的宽
    height1 = int(capture1.get(cv2.CAP_PROP_FRAME_HEIGHT))  # 获取原视频的高

    video2_fps = capture2.get(cv2.CAP_PROP_FPS)			 # 获取原视频的帧率
    width2 = int(capture2.get(cv2.CAP_PROP_FRAME_WIDTH))  # 获取原视频的宽
    height2 = int(capture2.get(cv2.CAP_PROP_FRAME_HEIGHT))  # 获取原视频的高

    # 检查属性,如果帧宽高不一致无法拼接
    if width1 != width2 or height1 != height2:
        raise ValueError(f"视频属性不合符要求。\n"
                         f"帧率:{video1_fps}{video2_fps}; \n"
                         f"帧大小:({width1},{height1}):({width2},{height2})")

    # 保存的编码、写入
    fourcc = cv2.VideoWriter_fourcc(*"mp4v")
    out = cv2.VideoWriter(save_video_path, fourcc, video1_fps, (width1, height1))
    # 区分线的大小,涉及到拼接处的坐标
    front_size = 1

    # 区分线的起始坐标
    start = 0
    # 区分线来回的标志位,默认方向向右
    right_flag = True
    while True:
        # 区分线向右
        if start < width1 and left_flag:
        	# 区分线在中间三分之一,每次两个像素,其他区域3个像素
            if width1 // 3 < start < width1//3 * 2:
                start += 2
            else:
                start += 3
        else:
        	# 区分线到了右边界,开始向左
            right_flag = False
            # 区分线在中间三分之一,每次两个像素,其他区域3个像素
            if width1 // 3 < start < width1 // 3 * 2:
                start -= 2
            else:
                start -= 3
            # 区分线到了左边界,开始向右
            if start < 0:
                right_flag = True

        # 读视频
        ret1, frame1 = capture1.read()
        ret2, frame2 = capture2.read()

        # 两个视频都读到
        if ret1 and ret2:
        	# 绘制区分线
            cv2.line(frame1, (start, 0), (start, height1), (255, 255, 255), front_size)
            # 拼接
            frame1[:, start + front_size:, :] = frame2[:, start + front_size:, :]
            # 写视频
            out.write(frame1)
        else:
            break
    capture1.release()
    capture2.release()
    out.release()

添加声音

至此,实现动态的对比视频也得到了,基本上任务就达到了。但是细心的会发现OpenCV处理后声音也没了。因此接下实现融合音频。很简单利用moviepy,几行代码就可实现。

获取原视频音频

	# 获取音频
	# 有音频的视频
    video_path = r'head.mp4'
    # 把其中音频保存为MP3
    mp3_path = r'head.mp3'
    get_audio_from_video(video_path, mp3_path)
  • get_audio_from_video函数的实现如下:
def get_audio_from_video(video_path, save_path):
    from moviepy.editor import VideoFileClip
    video = VideoFileClip(video_path)
    audio = video.audio
    audio.write_audiofile(save_path)

融合声音

传入上述的音频的文件和对比视频即可。

	# 音频融合到合成视频
    result_video_path = r'result.mp4'
    add_audio_into_video(compare_video_path, mp3_path, result_video_path)
  • add_audio_into_video函数的实现如下:
def add_audio_into_video(video_path, audio_path,save_path):
    from moviepy.editor import AudioFileClip, VideoFileClip
    ad = AudioFileClip(audio_path)
    vd = VideoFileClip(video_path)
    vd2 = vd.set_audio(ad)  # 将提取到的音频和视频文件进行合成
    vd2.write_videofile(save_path)  # 输出新的视频文件
    # vd2.write_videofile(save_path, audio_bitrate="1000k", bitrate="2000k")

效果如下:

基于moviepy的视频添加音频


视频转GIF

视频有时候不是很方便,比如上传CSDN需要审核才能使用,总之会有一些不方便。因此GIF也成为了一种不错的选择。

这里提供了两种方法,如下:video2gifconvert_mp4_to_gif,功能差不多,但是效果些许差别,前者使用moviepy,后者使用PIL库保存。

	gifpath = r'enhence.gif'
    video2gif(enhance_video_path, gifpath)
    convert_video_to_gif(enhance_video_path, gifpath)
  • video2gif函数
    通过subclip(start_time, end_time)选择视频的时间转GIF;使用resize(0-1)实现对GIF尺寸的控制,太大会导致文件过大。
def video2gif(video_path, save_path):
    from moviepy.editor import VideoFileClip
    clip = VideoFileClip(video_path).subclip(0, 10).resize(1)  # 宽度和高度乘以 0.1   # 1~3s
    clip.write_gif(save_path)
  • convert_video_to_gif函数
    收集视频帧,实现转GIF。
def convert_video_to_gif(video_path,save_path,duration=80,multiple=3):
    from PIL import Image
    video_capture = cv2.VideoCapture(video_path)
    i = 0
    # 收集视频帧
    frames = []
    while True:
        ret, frame = video_capture.read()
        if not ret:
            break
        # 整除multiple才收集,实现倍速
        if i % multiple== 0:
            frame = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            frames.append(frame)
        i += 1
    begin= frames[0]
    # 利用save保存为GIF
    begin.save(save_path,
               format="GIF",
               append_images=frames[1:],
               save_all=True,
               duration=duration,
               loop=0,
               disposal=0)
  • save参数说明,更多信息点这里==>官网
关键字作用
loopGIF循环的整数次数。0 表示永远循环
disposal0 - 未指定处置。1 - 不要丢弃。2 - 恢复到背景颜色。3 - 恢复到以前的内容。
save_alltrue:保存图像的所有帧。false:保存多帧图像的第一帧。
transparency透明度颜色指数;
optimize压缩调色板消除未使用的颜色
duration单位毫秒,每帧的显示持续时间。传递一个常量持续时间的单个整数,或 列表或元组以分别设置每个帧的持续时间。
append_images要追加为附加帧的图像列表。每个列表中的图像可以是单帧或多帧图像
palette对保存的图像使用指定的调色板

两者的对比效果如下:

前者
后者

致谢

素材为汶川地震的主持人宁远,让我们向所有在灾难中做出奉献的人们致以最诚挚的敬意和感谢。他们的善行和勇敢的行动永远值得铭记和赞美。让我们一同努力,为更美好的社会贡献自己的力量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/779925.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

elementui全局给select option添加title属性

场景 有天边上的同事问了我一个问题&#xff0c;示例如下&#xff0c;有个数据特别长&#xff0c;导致下拉部分被横向撑大。希望在全局对所有的option进行处理&#xff0c;按照select的宽度&#xff0c;超出隐藏。 处理 方式一 第一眼看过去直接修改源码好了&#xff0c;修…

Mybatis基础模块-日志管理

文章目录 1. 适配器模式2. Log2.1 默认实现StdOutImpl2.2 Log4jImpl 3. LogFactory4. 解析配置和应用4.1 settings配置4.2 解析 5. jdbc日志5. 1 类图5.2 BaseJdbcLogger5.3 ConnectionLogger5.4 ConnectionLogger的具体应用 1. 适配器模式 适配器使接口不兼容的对象可以相互合…

用QFramework来重构 祖玛游戏

资料 Unity - 祖玛游戏 GitHub 说明 用QF一个场景就够了&#xff0c;在UIRoot下切换预制体达到面板切换。 但测试中当然要有一个直接跳到测试面板的 测试脚本&#xff0c;保留测试Scene&#xff08;不然初学者也不知道怎么恢复测试Scene&#xff09;&#xff0c;所以全文按S…

SpringBoot整合Spring Security实现权限控制

文章目录 Spring Security介绍Spring Security案例1、快速搭建一个springboot工程2、导入SpringSecurity整合springboot工程3、认证3.1、登录流程校验3.2、入门案例的原理3.3、实现思路3.4、实现认证流程&#xff08;自定义&#xff09;3.5、正式实现3.5.1 实现数据库的校验3.5…

Linux 内核 ASoC 基本数据结构

Linux 内核 ASoC 框架建立了新的抽象&#xff0c;并通过一些中间层&#xff0c;将这些抽象接入 ALSA 音频框架。 Linux 内核 ASoC 设备驱动的结构如下图&#xff1a; Linux 内核 ASoC 设备驱动程序在 Linux 内核中扮演多个角色。 Linux 内核 ASoC 设备驱动程序在初始化阶段向…

MATLAB遗传算法求解带容量约束的物流配送选址问题实例

MATLAB遗传算法求解带容量约束的物流配送选址问题实例 作者&#xff1a;麦哥爱西芹 MATLAB遗传算法求解带容量约束物流配送中心选址问题代码实例 遗传算法编程问题实例&#xff1a; 在经度范围为(116, 118)&#xff0c;纬度范围为(38, 40)的矩形区域内&#xff0c;散布着37个需…

第116天:免杀对抗-EDRSyscall-hookDLL反射注入白加黑隐写分离加载器

知识点 #知识点&#xff1a; 1、DLL劫持-自写&导入 2、DLL劫持-重写&分离 3、syscall-底层&项目#章节点&#xff1a; 编译代码面-ShellCode-混淆 编译代码面-编辑执行器-编写 编译代码面-分离加载器-编写 程序文件面-特征码定位-修改 程序文件面-加壳花指令-资源 …

常微分方程建模R包ecode(一)——构建常微分方程系统

常微分方程在诸多研究领域中有着广泛应用&#xff0c;本文希望向大家介绍笔者于近期开发的R包ecode&#xff0c;该包采用简洁易懂的语法帮助大家在R环境中构建常微分方程&#xff0c;并便利地调用R图形接口&#xff0c;研究常微分方程系统的相速矢量场、平衡点、稳定点等解析性…

基于linux下的高并发服务器开发(第二章)- 2.20 kill、raise、abort函数

03 / 信号的5种默认处理动作 当程序运行的过程中异常终止或崩溃&#xff0c;操作系统会将程序当时的内存状态记录下来&#xff0c;保存在一个文件中&#xff0c;这种行为就叫做Core Dump&#xff08;中文有的翻译成“核心转储”)。我们可以认为 core dump 是“内存快照”&#…

X86设备启动过程

文章目录 一、电源自检二、BIOS自检三、引导设备选择四、主引导记录4.1 0x7c0 五、加载操作系统 x86计算机启动过程&#xff0c;主要分为这几个阶段&#xff1a;电源自检、BIOS自检、引导设备的选择、主引导记录、加载操作系统。 一、电源自检 当我们按下开关键后&#xff0c;…

消息队列总结(3)- RabbitMQ Kafka RocketMQ高可用方案

目录 1. 什么是高可用&#xff1f; 1.1 常见的高可用方法 1.2 消息队列的高可用 2. RabbitMQ的高可用方案 2.1 镜像队列 2.2 消息生产的确认机制 2.3 消息的持久化 3. Kafka的高可用方案 3.1 消息备份 3.2 ISR & IEO & HW 3.3 消息生产的确认机制 4. Rocke…

在虚拟机中安装anaconda和pytorch

首先我用的是VMware&#xff0c;ubuntu16.04. 首先建议安装anaconda,登录官网Free Download | Anaconda 下载完成后&#xff0c;来到安装文件目录处&#xff0c;打开终端&#xff0c; 然后在终端输入bash <anaconda文件名> 然后就一直enter和yes到底&#xff0c;直到安…

【后端面经】微服务构架 (1-3) | 熔断:熔断-恢复-熔断-恢复,抖来抖去怎么办?

文章目录 一、前置知识1、什么是熔断?2、什么是限流?3、什么是降级?4、怎么判断微服务出现了问题?A、指标有哪些?B、阈值如何选择?C、超过阈值之后,要不要持续一段时间才触发熔断?5、服务恢复正常二、面试环节1、面试准备2、面试基本思路三、总结 在微服务构架中…

【OC总结 面向对象 + 内存管理 + runtime】

文章目录 前言面向对象1.1 一个NSObject对象占用多少内存&#xff1f;1.2 iOS的继承链 & 对象的指针指向了哪里&#xff1f;1.3 OC的类的信息存放在哪里&#xff1f;-isa指针1.4 isMemberOfClass & isKindOfClass Runtime1.4 讲一下OC的消息机制1.5 消息转发机制流程1.…

React 中 {} 的应用

列表渲染&#xff1a; 1.使用数组的 map 方法&#xff08;因为map 可以映射&#xff09; 2、列表要添加 key 属性&#xff0c;值要唯一 // 导入 // 用来获取 dom元素 import { createRoot } from "react-dom/client";// 内容 const contentArr [{ id: 1, name: &…

Spring Cloud【分布式配置中心(Spring Cloud Config 、Config配置总控中心搭建、Config配置读取规则)】(九)

目录 服务网关Gateway实现用户鉴权_网关全局过滤器加入JWT 鉴权 分布式配置中心_Spring Cloud Config 分布式配置中心_Config配置总控中心搭建 分布式配置中心_Config配置读取规则 服务网关Gateway实现用户鉴权_网关全局过滤器加入JWT 鉴权 配置跳过验证路由 org:my:jwt…

C++笔记之函数对象functors与可调用对象

C笔记之函数对象functors与可调用对象 code review! 文章目录 C笔记之函数对象functors与可调用对象0.函数对象&#xff08;Function Objects&#xff09;&#xff0c;也称为functors1.函数对象与可调用对象的关系2.可调用对象几种形式2.1. 使用函数对象2.2. 使用普通函数指针…

Linux 打包Qt程序到无Qt环境Linux系统下运行,问题记录

Linux 环境下Qt开发的摄像头程序用到了opencv的库&#xff0c;需要跟Qt环境一起打包。 1.打包所有关联库用的是脚本程序。 #!/bin/bashLibDir$PWD"/lib" Target$1lib_array($(ldd $Target | grep -o "/.*" | grep -o "/.*/[^[:space:]]*"))$(m…

谈谈跨域?!

1.什么是跨域 跨域是一个网页脚本访问另外一个网页的内容&#xff0c;如果这两个网页的协议、端口&#xff0c;域名有一个不同就会产生跨域问题&#xff0c;浏览器具有一个同源策略&#xff0c;是一个安全策略&#xff0c;为了避免被恶意修改数据或者操作dom。 2.如何解决跨域…

为什么学习SpringSpring框架核心与设计思想(IOC与DI)?

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE进阶 目录 文章目录 一、Spring是什么&#xff1f; 二、为什么要学习框架&#xff1f; 三、Spring核心概念 3.1 什么是容器&#xff1f; 3.2 什么是IOC&#xff1f; 四、再谈Spring中的 IOC 五…