背景
在之前学习爬虫项目中,得到的部分视频是有水印的,因此有能通过比较好的技术手段实现水印去除的需求。一般情况下,如果能获取到无水印的原始素材是最好,但某些网站本身的原始素材就是加了水印。对于这种情况,少量可以通过某些视频剪辑软件完成水印去除,但对于大量素材,依赖人工完成是不现实的。
描述
这篇文章将提供一种方法,描述在特定类型视频中,使用技术手段完成水印去除的实现,仅供参考学习,请合理使用,避免法律风险。
主要的实现方式其实非常简单,主要是各种现有工具的整合,最终也达到了比较好的效果。在限定品类之后,去除效果评估合格率达到了97%。
调研
在网上调研之后,主要有以下可参考的实现,可以看到,他们各自有不同的优势和缺点。
高端大气上AI
首先AI有比较高的接入成本和学习门槛,而且本身有些玄学。抛开算法,最终的效果还是依赖输入样本的训练。再回到我们素材自身,不同作者其水印会是变化的(id为水印)。算法训练,其实获得准确位置的能力有待确定。
总结缺点: 依赖众多,需要训练,Id+logo变化的情况预计训练模型不会容易适配,效果也不理想。ffmpeg delogo
实际是在水印位置添加滤镜,类似毛玻璃效果。这种是比较直接的方式,但问题的核心变成了如何获取水印的位置。还有个问题就是,ffmpeg delogo 的效果在不同视频素材不稳定。例如,如果一个视频帧水印位置画面内容比较多,去除水印后会比较明显。不过一般情况下,水印在右上或者左上,画面内容相对较少。
总结缺点:产生模糊区域,需要确定位置和大小。蒙版+opencv + python 逐帧处理
可否将视频处理成图片,然后再按每张图片来处理?当然理论是可行的,这就将问题变成了图片去水印,而且像openCV有比较成熟的去水印算法。但是几个问题。
首先是openCV的图片去水印需要一个蒙版,即有个纯色+水印的图片,当然也不太适合不同视频水印logo变化的情况。按每个视频创建蒙版也是没法做成自动化的。
另一个是视频处理成图片后,内容过大。测试中,一个19MB的1080P60hz视频,处理成图片变成了3GB大小,而且每帧处理也比较耗时,更不说合并成视频的耗时。
缺点明确: 生成蒙版+逐帧处理+耗时较大
- cv 获取固定图标+ id生成位置坐标
最后一种方案是折衷的方式,也是最终采用的。首先,水印的获取仍然使用openCV,不过是采用CV的图像识别。对于视频,也不是按照所有帧的方式,而是随机得选取某些帧获取截图,然后用cv去拿到水印坐标。这里有个前提,是水印中有部分是不变的,比如logo。先人工将这部分抠图下来,然后代入CV做识别,拿到了logo的坐标。因为不同帧会有变化,造成CV失败的误差,需要在失败的坐标中筛选成功率高的。
然后由于水印是 logo+id的形式,再根据id的字数和字体字号占的像素数,通过之前获取的坐标算出水印的大小。如此我们就知道了水印的位置和大小,就可以使用ffmpeg delogo做去除了。
总结,最后实际是采用了第四种方案和第二种的结合,当然这也是根据具体场景综合考虑的,未必就是通用和最优的实现。
技术方案
描述
如上面的介绍,最终识别部分方案可以汇总成如下的流程:既然已经知道了水印的位置和大小,就可以使用ffmpeg delogo 的方式来去除。绝大部分视频的处理效果是可以接受的。
抠图获取统一logo ---> 视频随机分帧 ---> CV识别logo坐标 ---> 根据视频作者昵称计算水印大小 ---> ffmpeg delogo去除水印。
计算水印位置大小核心代码
import cv2
from matplotlib import pyplot as plt
# source=input('source:')
# tpl=input('template:')
source = '/export/data/晴天独奏/mask/1.png'
tpl = 'mark_bili_1280.png'
img = cv2.imread(source, 0)
img2 = img.copy()
template = cv2.imread(tpl, 0)
# 非1080*1920需要等比例缩放
w, h = template.shape[::-1]
ow, oh = img.shape[::-1]
# # All the 6 methods for comparison in a list
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
#
# methods = ['cv2.TM_CCOEFF_NORMED']
for meth in methods:
img = img2.copy()
method = eval(meth)
# Apply template Matching
res = cv2.matchTemplate(img, template, method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv2.rectangle(img, top_left, bottom_right, 255, 1)
print('x={},y={},w={},h={}'.format(top_left[0], top_left[1], bottom_right[0]-top_left[0], bottom_right[1] - top_left[1]))
plt.subplot(121), plt.imshow(res, cmap='gray')
plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(img, cmap='gray')
plt.title('Detected Point'), plt.xticks([]), plt.yticks([])
plt.suptitle(meth)
plt.show()
比较识别效果
最终去除效果对比
视频分帧代码
import os
import sys
import cv2
video_name = sys.argv[1]
if video_name is None:
print("input video name!")
exit(1)
com = 'ffmpeg -ss 10 -i {} -f image2 -vframes 1 -y frame.png'.format(video_name)
os.system(com)
cv2.namedWindow('frame', 0)
img = cv2.imread('frame.png')
cv2.imshow('frame', img)
cv2.waitKey(0)
批量匹配水印
用于在批量的截图中标记识别的水印,并打印出坐标
import os
import cv2
# source=input('source:')
# tpl=input('template:')
tpl = '/export/code/github/demo/src/test/resources/mark/mark_bili_1280-1.png'
template = cv2.imread(tpl, 0)
# 非1080*1920需要等比例缩放
w, h = template.shape[::-1]
# # All the 6 methods for comparison in a list
# methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
# 'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
#
dir = '/export/data/BV1dT4y1E7w3/out/'
# dir = '/export/code/github/demo/data/out/'
files = []
for f in os.walk(dir):
f = f[2]
for x in f:
if '.png' not in x:
continue
files.append(x)
break
count = 1
for f in files:
source = dir + f
img = cv2.imread(source, 0)
img2 = img.copy()
ow, oh = img.shape[::-1]
meth = 'cv2.TM_CCOEFF_NORMED'
img = img2.copy()
method = eval(meth)
# Apply template Matching
res = cv2.matchTemplate(img, template, method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv2.rectangle(img, top_left, bottom_right, 255, 1)
cv2.imwrite(source + ".mark.png", img)
print('id:{} x={}:y={}:w={}:h={}'.format(count, top_left[0], top_left[1], bottom_right[0] - top_left[0], bottom_right[1] - top_left[1]))
count = count + 1
遇到的问题
- 1.识别错位
错位有以下几个原因造成。
- 1.1 部分视频分辨率不统一,如变成1280*720。 解决办法是额外使用该分辨率下的logo 模板做CV识别。
- 1.2 视频网站在某个时间之后更换了logo样式。 解决办法是只选取固定时间点之后的视频。
- 1.3 水印中作者昵称部分,因为中英文字体大小,造成水印去除残留。 解决办法是根据不同字体用对应不同像素计算水印大小。以及多加些额外的冗余值,保证去除范围比实际水印范围大。
- 1.4 其他识别错位。有极少数视频因为内容的原因,随机取的帧无法正确得到水印位置。解决方法是设置阈值,比如随机20帧,只有某个坐标重复达到或超过5次,才作为有效坐标,否则,重新截图分帧,再次失败。实际验证效果不是很理想,遂超过次数后,判断为失败,过滤掉。还有就是本身没有水印,这种目前只能以来人工介入。
- ffmpeg 消耗CPU资源过高
这个主要是开始没有太注意ffmpeg的参数,默认占满全部CPU,导致开始时候卡顿。 可以通过-threads
参数设置占用的CPU - 视频水印去除后毛玻璃效果过于明显
这种情况因为完全和视频内容有关系,目前的方案依赖ffmpeg,暂时无解。能想到的是优化ffmpeg的去水印算法,非现实快速可达的方案。 - 项目中都是java,有没有方法将如上包括openCV, ffmpeg的调用使用java实现?
这算是个尝试,当然答案是可以实现的,有现成的Bytedeco
,可以避免自己写大量命令行调用。
总结
以上就是本篇文章的所有内容,限于篇幅,部分细节没有全部补充。过程中某些情况下,虽然已经知道了方法,但是仍然需要花大量的时间去调试和验证才能知道最终的效果。当然最后也是在不断的实践下有了明显的提升。如之前所说,在控制输入样本的前提下,比如只选择右上角水印,尽量保证视频分辨率一致等,最终的评估合格率达到了97%,还是令人满意的。
参考资料
- ziweipolaris/watermark-removal: 通过水印减除方法去掉视频中的水印,快速但不完美
- 基于GAN的图像水印去除器,效果堪比PS高手 – 闪念基因 – 个人技术分享
- 毫秒级图像去噪!新AI系统完美去水印! - 云+社区 - 腾讯云
- 去噪、去水印、超分辨率,这款不用学习的神经网络无所不能 - 云+社区 - 腾讯云
- 【深度学习去水印】- CSDN
- 去噪、去水印、超分辨率,这款不用学习的神经网络无所不能 | 机器之心
- [论文分享(一)] 水印自动去除(上)---水印的自动识别和特征提取 - 知乎
- 短视频解析,去水印原理整理汇总-博客
- Python实现超简单【抖音】无水印视频批量下载fei347795790的博客-CSDN博客抖音批量下载 无水印
- 接近无损的视频去水印方法
- Python OpenCV去除图片水印_XerCis的博客-CSDN博客_cv2 去除水印
- python利用opencv去除水印方法 - 菲菲菲菲菲常新的新手 - 博客园
- 两种Python基于OpenCV的固定位置半透明水印去除方案 - 麦拂沙的个人空间 - OSCHINA
- 基于python的图片修复程序-可用于水印去除 - 简书
- JavaCV入门示例及UnsatisfiedLinkError异常踩坑记录
- Bytedeco - Home