深度学习在数据准备准备方面,有时我们需要把一个长视频截取成一张张的图片,来扩充我们的数据量,但是在按照一定的帧率去截取图片试我们会发现有好多相似且重复的图片出现,但是我们增加训练样本时有需要样式不同风格不同角度不同的一系列的图片去扩充,这个时候就需要在视频截图过程中简单的去判断一下前后之前图片的相似程度
目录
简单的查阅资料以后发现有以下这几种简单的且快捷的算法可以比较图像之间的相似度
基于互信息(Mutual Information)计算图片的相似度
综合上面的比较方法,我最终选定的是差异值哈希+明汉距离算法,精确度较高,且速度也非常快,你们也可以试一试各个算法的效果比较实现代码都在上面可以直接运行
-
简单的查阅资料以后发现有以下这几种简单的且快捷的算法可以比较图像之间的相似度
-
余弦距离
- 把图片表示成一个向量,通过计算向量之间的余弦距离来表征两张图片的相似度(值越大相似度越高)
from PIL import Image from numpy import average, dot, linalg def get_img(image, size=(64, 64)): # 利用image对图像大小重新设置, Image.ANTIALIAS为高质量的 image = image.resize(size, Image.ANTIALIAS) # 将图片转换为L模式,其为灰度图,其每个像素用8个bit表示 image = image.convert('L') return image # 计算图片的余弦距离 def image_similarity(image1, image2): image1 = get_thum(image1) image2 = get_thum(image2) images = [image1, image2] vectors = [] norms = [] for image in images: vector = [] for i in image.getdata(): vector.append(average(i)) vectors.append(vector) #2范数 norms.append(linalg.norm(vector, 2)) a, b = vectors a_norm, b_norm = norms # dot返回的是点积,对二维数组(矩阵)进行计算 res = dot(a / a_norm, b / b_norm) return res
-
哈希算法(采用汉明距离计算)
- 用汉明距离去计算图像指纹获得去的值就是衡量两个图片之间的差异(值越大图片越不像,值越小图像相似度越高)
- 图像指纹就是图片的身份的象征,指纹的计算方法就是按照一定的哈希算法得到一组二进制数字,
- 哈希的计算法方式有以下几种
- aHash:平均哈希
-
import cv2 def ahash(image): # 将图片缩放为8*8的 image = cv2.resize(image, (8, 8), interpolation=cv2.INTER_CUBIC) # 将图片转化为灰度图 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) # s为像素和初始灰度值,hash_str为哈希值初始值 s = 0 # 遍历像素累加和 for i in range(8): for j in range(8): s = s + gray[i, j] # 计算像素平均值 avg = s / 64 # 灰度大于平均值为1相反为0,得到图片的平均哈希值,此时得到的hash值为64位的01字符串 ahash_str = '' for i in range(8): for j in range(8): if gray[i, j] > avg: ahash_str = ahash_str + '1' else: ahash_str = ahash_str + '0' result = '' # 将上面的hash值转换为16位的 for i in range(0, 64, 4): result += ''.join('%x' % int(ahash_str[i: i + 4], 2)) # print("ahash值:",result) return result
感知哈希
-
import cv2 import numpy as np def phash(img): # 加载并调整图片为32*32的灰度图片 img1 = cv2.resize(img, (32, 32),cv2.COLOR_RGB2GRAY) # 创建二维列表 h, w = img.shape[:2] vis0 = np.zeros((h, w), np.float32) vis0[:h, :w] = img1 # DCT二维变换 """离散余弦变换(DCT)是种图像压缩算法,它将图像从像素域变换到频率域。然后一般图像都存在很多冗余和相关性的,所以转换到频率域之后,只有很少的一部分频率分量的系数才不为0,大部分系数都为0(或者说接近于0)。Phash哈希算法过于严格,不够精确,更适合搜索缩略图,为了获得更精确的结果可以选择感知哈希算法,它采用的是DCT(离散余弦变换)来降低频率的方法。""" # 离散余弦变换,得到dct系数矩阵 img_dct = cv2.dct(cv2.dct(vis0)) # 缩小DCT:DCT计算后的矩阵是32 * 32,保留左上角的8 * 8,这些代表的图片的最低频率 img_dct.resize(8,8) # 把list变成一维list img_list = np.array().flatten(img_dct.tolist()) # 计算均值 img_mean = cv2.mean(img_list) # 进一步减小DCT:大于平均值记录为1,反之记录为0. avg_list = ['0' if i<img_mean else '1' for i in img_list] return ''.join(['%x' % int(''.join(avg_list[x:x+4]),2) for x in range(0,64,4)])
差值哈希
-
import cv2 def dHash(img): # 差值哈希算法 # 先将图片压缩成9*8的小图,有72个像素点 img = cv2.resize(img, (9, 8)) # 转换灰度图 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # print(gray.shape) hash_str = '' # 计算差异值:dHash算法工作在相邻像素之间,这样每行9个像素之间产生了8个不同的差异,一共8行,则产生了64个差异值,或者是32位01字符串 for i in range(8): for j in range(8): if gray[i, j] > gray[i, j+1]: hash_str = hash_str+'1' else: hash_str = hash_str+'0' return hash_str
-
汉明距离计
def cmpHash(hash1, hash2): # Hash值对比 # 算法中1和0顺序组合起来的即是图片的指纹hash。顺序不固定,但是比较的时候必须是相同的顺序。 # 对比两幅图的指纹,计算汉明距离,即两个64位的hash值有多少是不一样的,不同的位数越小,图片越相似 # 汉明距离:一组二进制数据变成另一组数据所需要的步骤,可以衡量两图的差异,汉明距离越小,则相似度越高。汉明距离为0,即两张图片完全一样 n = 0 # hash长度不同则返回-1代表传参出错 if len(hash1) != len(hash2): return -1 # 遍历判断 for i in range(len(hash1)): # 不相等则n计数+1,n最终为相似度 if hash1[i] != hash2[i]: n = n + 1 return n
- 用汉明距离去计算图像指纹获得去的值就是衡量两个图片之间的差异(值越大图片越不像,值越小图像相似度越高)
-
直方图计算图片的相似度
- 利用直方图计算图片的相似度时,是按照颜色的全局分布情况来看待的,无法对局部的色彩进行分析,同一张图片如果转化成为灰度图时,在计算其直方图时差距就更大了。对于灰度图可以将图片进行等分,然后在计算图片的相似度。
import cv2 # 直方图计算相似度 def calc_similar(img1, img2): # 计算图img的直方图 H1 = cv2.calcHist([img1], [1], None, [256], [0, 256]) # 对图片进行归一化处理 H1 = cv2.normalize(H1, H1, 0, 1, cv2.NORM_MINMAX, -1) # 计算图img2的直方图 H2 = cv2.calcHist([img2], [1], None, [256], [0, 256]) # 对图片进行归一化处理 H2 = cv2.normalize(H2, H2, 0, 1, cv2.NORM_MINMAX, -1) # 利用compareHist()进行比较相似度 similarity = cv2.compareHist(H1, H2, 0) return similarity
- 利用直方图计算图片的相似度时,是按照颜色的全局分布情况来看待的,无法对局部的色彩进行分析,同一张图片如果转化成为灰度图时,在计算其直方图时差距就更大了。对于灰度图可以将图片进行等分,然后在计算图片的相似度。
-
SSIM(结构相似度度量)计算图片的相似度
from skimage.measure import compare_ssim from scipy.misc import imread import numpy as np # 读取图片 img1 = imread('1.jpg') img2 = imread('2.jpg') # 使得两图片大小相等 img2 = np.resize(img2, (img1.shape[0], img1.shape[1], img1.shape[2])) ssim = compare_ssim(img1, img2, multichannel = True)
- SSIM是一种全参考的图像质量评价指标,分别从亮度、对比度、结构三个方面度量图像相似性。SSIM取值范围[0, 1],值越大,表示图像失真越小。在实际应用中,可以利用滑动窗将图像分块,令分块总数为N,考虑到窗口形状对分块的影响,采用高斯加权计算每一窗口的均值、方差以及协方差,然后计算对应块的结构相似度SSIM,最后将平均值作为两图像的结构相似性度量,即平均结构相似性SSIM。
-
基于互信息(Mutual Information)计算图片的相似度
from sklearn import metrics as mr from scipy.misc import imread import numpy as np img1 = imread('1.jpg') img2 = imread('2.jpg') # 使得两图片大小相等 img2 = np.resize(img2, (img1.shape[0], img1.shape[1], img1.shape[2])) img1 = np.reshape(img1, -1) img2 = np.reshape(img2, -1) mutual_infor = mr.mutual_info_score(img1, img2)
- 通过计算两个图片的互信息来表征他们之间的相似度,如果两张图片尺寸相同,还是能在一定程度上表征两张图片的相似性的。但是,大部分情况下图片的尺寸不相同,如果把两张图片尺寸调成相同的话,又会让原来很多的信息丢失,所以很难把握。经过实际验证,此种方法的确很难把握
-
综合上面的比较方法,我最终选定的是差异值哈希+明汉距离算法,精确度较高,且速度也非常快,你们也可以试一试各个算法的效果比较实现代码都在上面可以直接运行
-
-
下面开始进入正题,获取视频中的图片
-
cv读取视频,获取视频的总帧数,自己定义一个变量需要每隔多少帧获取一张图片,记录前后两张图片,进行图片相似度比较,差异比较大的时候记录帧数并且保存图片,差异不大的时候跳过,不保存图片,通过cap.get(propld)访问视频的某些功能,propld是0到16之间的数字。每个数字表示视频的属性
cv2.CAP_PROP_POS_MSEC 0 视频文件的当前位置(以毫秒为单位)或视频捕获时间戳 cv2.CAP_PROP_POS_FRAMES 1 基于0的索引将被解码/捕获下一帧 cv2.CAP_PROP_POS_AVI_RATIO 2 视频文件的相对位置:0 - 视频的开始,1 - 视频的结束 cv2.CAP_PROP_FRAME_WIDTH 3 帧的宽度 cv2.CAP_PROP_FRAME_HEIGHT 4 帧的高度 cv2.CAP_PROP_FPS 5 帧速 cv2.CAP_PROP_FOURCC 6 4个字符表示的视频编码器格式 cv2.CAP_PROP_FRAME_COUNT 7 帧数 cv2.CAP_PROP_FORMAT 8 byretrieve()返回的Mat对象的格式 cv2.CAP_PROP_MODE 9 指示当前捕获模式的后端特定值 cv2.CAP_PROP_BRIGHTNESS 10 图像的亮度(仅适用于相机) cv2.CAP_PROP_CONTRAST 11 图像对比度(仅适用于相机) cv2.CAP_PROP_SATURATION 12 图像的饱和度(仅适用于相机) cv2.CAP_PROP_HUE 13 图像的色相(仅适用于相机) cv2.CAP_PROP_GAIN 14 图像的增益(仅适用于相机) cv2.CAP_PROP_EXPOSURE 15 曝光(仅适用于相机) cv2.CAP_PROP_CONVERT_RGB 16 表示图像是否应转换为RGB的布尔标志
import cv2 def cmpHash(hash1, hash2): n = 0 if len(hash1) != len(hash2): return -1 for i in range(len(hash1)): if hash1[i] != hash2[i]: n = n + 1 return n def dHash(img): img = cv2.resize(img, (9, 8)) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) for i in range(8): for j in range(8): if gray[i, j] > gray[i, j+1]: hash_str = hash_str+'1' else: hash_str = hash_str+'0' return hash_str def get_fraem(file_path): # file_path是文件的绝对路径,防止路径中含有中文时报错,需要解码 cap = cv2.VideoCapture(file_path) # 记录前后图片的地址 dict_img = {"1": [], } # 每隔多少帧获取一张图片 img_fream = 60 # 差异值 img_dif = 20 if cap.isOpened(): # 当成功打开视频时cap.isOpened()返回True,否则返回False rate = int(cap.get(5)) # 帧速率 FrameNumber = cap.get(7) # 视频文件的帧数 # duration = FrameNumber/rate/60 # 帧速率/视频总帧数 是时间,除以60之后单位是分钟 # timeF = int(FrameNumber // rate) # 每秒获取1张图片 timeF = int(FrameNumber // img_fream) print(rate) print(timeF) print(FrameNumber) c = 1 while True: sm_img = 0 # 视频读取完毕退出 if c > timeF * rate + 1: print('######################') break # 视频读取完毕退出 if c > FrameNumber: print('******************') break # 读取当前帧数图片 success, frame = cap.read() # 帧数到你需要截取的帧数时 if c % rate == 0: # 记录上一张图片和当前图片 if "2" in dict_img: # 计算图片的差异值哈希 dict_img["1"] = dict_img["2"] else: dict_img["1"] =dHash(frame) # 计算图片的差异值哈希 dict_img["2"] = dHash(frame) # 明汉距离计算差异值 sm_img = cmpHash(dict_img["1"], dict_img["2"]) if sm_img: # 当差异值不为0且大于我们预设的阈值的时候打印当前帧数并且保存图片 if sm_img >=img_dif: cv2.imwrite('img/{}.jpg'.format(c), frame) print(c) c += 1 if not success: print('video is all read') break
-
-
可以分享一点我生成一些效果样本
-
样例1
-
-
-
结论
- 做这两个样本时我都是默认20的阈值没有细微调整,看效果都还挺不错,后期阈值细微上的调整可以使得重复的图片不是很多,各个角度的样本图都会有一些
- 做这两个样本时我都是默认20的阈值没有细微调整,看效果都还挺不错,后期阈值细微上的调整可以使得重复的图片不是很多,各个角度的样本图都会有一些
-
-
总结
- 以后优化猜想,在阈值方面做成可以自适应的,先统计全部的阈值放到set集合里面,再按照由小到大排序放入list,我猜想阈值一般在list长度的5/7左右附近,或是各位有更好的什么优化思路可以和我交流一波
- 以上就是全部内容,有什么不对的地方请尽管指出,我会马上修改,有什么疑问也可以在评论区评论,我都会解答
- 对你有帮助或是喜欢的朋友可以帮忙点个赞谢谢