【视频的动态对比】

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



前言

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

无论的图像生成、修复、去污等等,如生成对抗方法;亦或是图像分割,如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)
关键字作用
loopGIF循环的整数次数。0 表示永远循环
disposal0 - 未指定处置。1 - 不要丢弃。2 - 恢复到背景颜色。3 - 恢复到以前的内容。
save_alltrue:保存图像的所有帧。false:保存多帧图像的第一帧。
transparency透明度颜色指数;
optimize压缩调色板消除未使用的颜色
duration单位毫秒,每帧的显示持续时间。传递一个常量持续时间的单个整数,或 列表或元组以分别设置每个帧的持续时间。
append_images要追加为附加帧的图像列表。每个列表中的图像可以是单帧或多帧图像
palette对保存的图像使用指定的调色板

两者的对比效果如下:

前者
后者

致谢

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值