写在前面:本博客仅作记录学习之用,部分图片来自网络,如需引用请注明出处,同时如有侵犯您的权益,请联系删除!
文章目录
- 前言
- 图像修复
- 人脸与关键点检测
- 修复图像
- 修复视频
- 动态对比
- 添加声音
- 获取原视频音频
- 融合声音
- 视频转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也成为了一种不错的选择。
这里提供了两种方法,如下:video2gif
、convert_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参数说明,更多信息点这里==>官网
关键字 | 作用 |
---|---|
loop | GIF循环的整数次数。0 表示永远循环 |
disposal | 0 - 未指定处置。1 - 不要丢弃。2 - 恢复到背景颜色。3 - 恢复到以前的内容。 |
save_all | true:保存图像的所有帧。false:保存多帧图像的第一帧。 |
transparency | 透明度颜色指数; |
optimize | 压缩调色板消除未使用的颜色 |
duration | 单位毫秒,每帧的显示持续时间。传递一个常量持续时间的单个整数,或 列表或元组以分别设置每个帧的持续时间。 |
append_images | 要追加为附加帧的图像列表。每个列表中的图像可以是单帧或多帧图像 |
palette | 对保存的图像使用指定的调色板 |
两者的对比效果如下:
![]() |
![]() |
致谢
素材为汶川地震的主持人宁远,让我们向所有在灾难中做出奉献的人们致以最诚挚的敬意和感谢。他们的善行和勇敢的行动永远值得铭记和赞美。让我们一同努力,为更美好的社会贡献自己的力量。