----------------题目-------------------
摄影师小刘爱好摄影,有许多照片(不同格式,不同分辨率),有的是自己拍摄的,有的是朋友的相机帮忙拍到的。 但他很苦恼,因为有很多照片是类似的(比如,稍微偏了一点角度),请用程序帮他把类似的图片挑选出来。
1.准备数据(文章末尾有链接)
准备了120张图片,格式有png,jpg各占一半,且有三种大小1:1,4:3,full 均分。
2.实验设计思路
(1)统一图片格式,方便下一步的比较——300300的png格式。 (2)计算两张图片之间的距离,判定图片相似的相似度阈值 计算图片之间的距离有很多种方法,我选择了计算图片之间的余弦相似度。 相似度阈值的确定:构造一个相似的照片成对出现的样本,通过计算每一对的相似度值,选取最小且合理的值为阈值(0.86),即认为当相似度大于这个值时,就删去其中一张。 3.图片去重 根据筛选出来的结果,调整我们的阈值(0.87),再进一步得到更好的结果。 4.实验调整 整个实验做下来,运行时间过长(6小时),效果还是很好的,最后得到了36幅不同图片。现在想办法缩短一下时间,发现可以通过调整图片的比例和大小,设置了4个比例,得出下面的运行速度图(只有这4个点是可靠的)。 于是,我们现在使用100100的图片集来实现图片去重,最后得到35张图片,只花费了半个小时,可以说是非常值得了,还可以通过调整阈值(0.88)来弥补。
3.代码实现
(1)代码框架 data文件夹: mixt_pics–原始图片,混合着不同格式和大小的120张图片; Sim2_pics–构造的成对相似图片文件; unify_picsxx:将原始图片统一格式为.png,xx可取下面表格的4个值,下同; only_picsxx:去重后得到的图片集,xx意味着是对哪种类型的去重。 utils文件夹: CosSim.py–计算两张图片的余弦相似度; pic_prpt.py–画出对不同比例的处理速度曲线; TreadJug.py–找到相似图片的合理的相似度分界点 threadholdjudge(这里的相似指的是只偏了一点角度); unify_picform.py–统一图片的大小和格式。 (2)主要代码 unify_picform.py
import cv2
import os
outtype = '.png'
image_size_w = 200
image_size_h =160
source_path = "../data/mixt_pics/"
target_path = "../data/unify_pics54/"
if not os.path.exists(target_path):
os.makedirs(target_path)
image_list = os.listdir(source_path)
i = 0
for file in image_list:
image_source = cv2.imread(source_path + file)
print("处理中-->",file)
image = cv2.resize(image_source, (image_size_w, image_size_h), 0, 0, cv2.INTER_LINEAR)
cv2.imwrite(target_path + str(i) + outtype, image)
i = i + 1
print("批量处理完成")
CosSim.py
from numpy import average, linalg, dot
def CosSim(image1, image2):
images = [image1, image2]
vectors = []
norms = []
for image in images:
vector = []
for pixel_tuple in image.getdata():
vector.append(average(pixel_tuple))
vectors.append(vector)
norms.append(linalg.norm(vector, 2))
a, b = vectors
a_norm, b_norm = norms
res = dot(a / a_norm, b / b_norm)
return res
TreadJug.py
import datetime
import time
import os
from PIL import Image
from utils.CosSim import CosSim
start_dt = datetime.datetime.now()
print("start_datetime:", start_dt)
time.sleep(2)
for i in range(10000):
i += 1
Sim2_pics_path = "../data/Sim2_pics/"
Sim2_image_list = os.listdir(Sim2_pics_path)
Sim2_image_list.sort(key=lambda x:int(x[:-4]))
Sims = []
for i in range(0,len(Sim2_image_list),2):
img0 = Image.open(Sim2_pics_path+Sim2_image_list[i])
img1 = Image.open(Sim2_pics_path+Sim2_image_list[i+1])
sim = CosSim(img0,img1)
Sims.append(sim)
print(Sims)
print(min(Sims))
end_dt = datetime.datetime.now()
print("end_datetime:", end_dt)
print("time cost:", (end_dt - start_dt).seconds, "s")
pic_prpt.py
import datetime
import time
import matplotlib.pyplot as plt
from PIL import Image
import os
from utils.CosSim import CosSim
pics_path11 = "../data/unify_pics11/"
pics_list11 = os.listdir(pics_path11)
pics_path11s = "../data/unify_pics11s/"
pics_list11s = os.listdir(pics_path11s)
pics_path43 = "../data/unify_pics43/"
pics_list43 = os.listdir(pics_path43)
pics_path54 = "../data/unify_pics54/"
pics_list54 = os.listdir(pics_path54)
Pro_time = []
paths = [pics_path11,pics_path11s,pics_path43,pics_path54]
lists = [pics_list11,pics_list11s,pics_list43,pics_list54]
for i in range(len(paths)):
start_t = datetime.datetime.now()
for j in range(0,40,2):
img0 = Image.open(paths[i]+lists[i][j])
img1 = Image.open(paths[i]+lists[i][j+1])
similar = CosSim(img0,img1)
end_t = datetime.datetime.now()
Pro_time.append((end_t - start_t).seconds)
print(Pro_time)
x_name = ['300*300','100*100','240*180','200*160']
plt.plot(x_name, Pro_time, linewidth=4)
plt.title("Proportion velocity",fontsize=14)
plt.xlabel("Propt", fontsize=14)
plt.ylabel("velo", fontsize=14)
plt.tick_params(axis='both', labelsize=10)
plt.axis([0, 6, 0, 50])
plt.show()
Get_Only1.py
import datetime
import time
start_dt = datetime.datetime.now()
print("start_datetime:", start_dt)
time.sleep(2)
for i in range(10000):
i += 1
from PIL import Image
import os
from utils.CosSim import CosSim
import cv2
from tqdm import tqdm
import shutil
pics_path = "../data/unify_pics11s/"
picsonly_path = "../data/only_pics11s/"
pics_list = os.listdir(pics_path)
pics_list.sort(key=lambda x:int(x[:-4]))
threshold = 0.87
piconly_names = [pics_list[0]]
outtype = ".png"
if not os.path.exists(picsonly_path):
os.makedirs(picsonly_path)
shutil.rmtree(picsonly_path)
os.mkdir(picsonly_path)
for i in tqdm(range(1,len(pics_list))):
img0 = Image.open(pics_path+pics_list[i])
flag = 1
for j in range(len(piconly_names)-1,-1,-1):
img1 = Image.open(pics_path+piconly_names[j])
print("\n正在比较原始第{}张图片和去重后的第{}张图片".format(i,j))
similar = CosSim(img0,img1)
if CosSim(img0,img1) >= threshold:
flag = 0
break
if flag:
piconly_names.append(pics_list[i])
for _ in range(len(piconly_names)):
pico = cv2.imread(pics_path + piconly_names[_])
cv2.imwrite(picsonly_path + str(_) + outtype, pico)
end_dt = datetime.datetime.now()
print("end_datetime:", end_dt)
print("time cost:", (end_dt - start_dt).seconds, "s")
4.实现效果
(1)原始数据集(查看部分) (2)运行时间对比 处理300300时,运行了4356s。 处理100100时,运行了962s。 (3)去重后图片集(前者是通过300300得到的36张,后者是通过100100得到的35张)
在最终的结果中,我们基本上达到了去重(去掉只偏了一些角度的图片)的目标,还可以看见有一些旋转角度较大的图片(24.png,25.png)没有被去重,认识到一种方法–SIFT算法可以找到旋转以及尺度不变的特征点,我尝试了一下,处理效果如下(函数在main下debig_shift.py):
import cv2
import numpy as np
def detectAndDescribe(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
destriptor = cv2.SIFT_create()
kps, features = destriptor.detectAndCompute(gray, None)
kps = np.float32([kp.pt for kp in kps])
return (kps, features)
def matchKeyPoints(kpsA, kpsB, featuresA, featuresB, ratio=0.75, reprojThresh=4.0):
matcher = cv2.BFMatcher()
rawMatches = matcher.knnMatch(featuresA, featuresB, 2)
matches = []
for m in rawMatches:
if len(m) == 2 and m[0].distance < m[1].distance * ratio:
matches.append([m[0].trainIdx, m[0].queryIdx])
if len(matches) > 4:
ptsA = np.float32([kpsA[i] for (_, i) in matches])
ptsB = np.float32([kpsB[i] for (i, _) in matches])
(H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh)
return (matches, H, status)
return None
def drawMatches(imageA, imageB, kpsA, kpsB, matches, status):
(hA, wA) = imageA.shape[:2]
(hB, wB) = imageB.shape[:2]
vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")
vis[0:hA, 0:wA] = imageA
vis[0:hB, wA:] = imageB
for ((trainIdx, queryIdx), s) in zip(matches, status):
if s == 1:
ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
cv2.line(vis, ptA, ptB, (0, 255, 0), 1)
return vis
image1 = cv2.imread('../data/only_pics11/24.png')
image2 = cv2.imread('../data/only_pics11/25.png')
(kps_img1, features_img1) = detectAndDescribe(image1)
(kps_img2, features_img2) = detectAndDescribe(image2)
M = matchKeyPoints(kps_img1, kps_img2, features_img1, features_img2)
if M:
(matches, H, status) = M
print('888888')
vis = drawMatches(image1, image2, kps_img1, kps_img2, matches, status)
cv2.imshow("vis",vis)
cv2.waitKey()
cv2.destroyAllWindows()
|