Python을 이용하여 중복 사진 정리하기 :: OpenCV, compare_ssim
1년 넘게 미룬 사진정리를 드디어 해야겠다고 마음먹었다. 내 사진첩에는 중복된 사진이 너무나도 많았는데 하나하나 손으로 할 수 없다고 생각했다. 그래서 python에 있는 이미지 인식을 이용하였다. 수천개의 사진의 이미지를 전부 대조할 수 없으니 우선적으로 처리할 수 있는 방안을 생각해보았다.
💡생각1 : 저장명이 겹치면 같은 파일이다.
ex)
6E5DD712-4FDF-4677-AF33-63E29F975035.jpeg
6E5DD712-4FDF-4677-AF33-63E29F975035 2.jpeg
💡생각1 : 용량이 같으면 같은 파일이다.
이 두가지 생각을 토대로 중복사진 정리를 해보았다.
1. Photo File
1) 사진 목록 불러오기
photo_list = []
for f in os.listdir('/Users/mizy/Desktop/mizy/Image/Photo'):
if 'jpeg' in f:
photo_list.append(f)
Photo폴더에 있는 사진 목록을 리스트에 저장한다.
os.listdir('path') : 경로에 있는 파일 목록 불러오기
2) 사진의 크기 구하기
photo_size = list(map(lambda x: os.path.getsize('/Users/mizy/Desktop/mizy/Image/Photo' + '/' + x), photo_list))
os.path.getsize('path/filename') : 파일 크기 구하기
3) 데이터프레임으로 만들기
# Find Same Photos
fsp = pd.DataFrame({'filename_raw':photo_list, 'size':photo_size})
print('사진의 갯수 :', len(fsp))
사진의 갯수 : 5081
'filename 숫자.jpeg' 형식으로 이루어진 사진 이름이 있는데 < 💡생각1 >에서 말한대로 저장명이 겹치고 맨 뒤에 ' 숫자'가 오면 같은 파일이라고 볼 수 있다. 따라서 ' 숫자'를 제거하여 파일명이 중복인 사진을 구분할 것이다.
4) ' 숫자' 제거하기
import re # 정규표현식
com = re.compile(' \d')
fsp['filename'] = list(map(lambda x: com.sub('', x), photo_list))
정규표현식을 이용하여 ' 숫자'에 해당하는 값을 지워주었다.
2. 겹치는 파일명 / 파일크기 확인
# Photo Value Counts
pvc = pd.DataFrame({'filename':fsp['filename'].value_counts().index, 'fn_counts':fsp['filename'].value_counts().values})
psvc = pd.DataFrame({'size':fsp['size'].value_counts().index, 'size_counts':fsp['size'].value_counts().values})
fsp = pd.merge(fsp, pvc, how = 'left', on = 'filename')
fsp = pd.merge(fsp, psvc, how = 'left', on = 'size')
fsp.sample(2)
각 파일별로 겹치는 이름과 크기가 몇 개씩인지 확인해보았다.
🙋🏻♀️Q. 이름은 같은데 크기가 다른게 있나?
for i in range(len(fsp)):
if (fsp['fn_counts'][i] > 1) & (fsp['size_counts'][i] == 1):
print(i)
→ 없다.
이름이 같으면 크기도 같음= 이름이 같은건 하나만 남겨도 됨
# Find Same Phto_Not Same Name
fsp_nsn = fsp.sort_values(['filename_raw'], ascending = False).drop_duplicates(['filename'], keep = 'first')
df.sort_values(['기준열']) : 정렬
df.drop_duplicates(['기준열']) : 중복제거
' 숫자'인 파일을 제거하고 싶어서 내림차순으로 정렬해주었다. 이후 숫자가 제거된 파일명을 기준으로 중복제거하였다.
print('남은 사진의 갯수 : {}\n지워진 사진의 갯수 : {}'.format(len(fsp_nsn), len(fsp)-len(fsp_nsn)))
남은 사진의 갯수 : 3877
지워진 사진의 갯수 : 1204
pvc_nsn = pd.DataFrame({'filename':fsp_nsn['filename'].value_counts().index, 'fn_counts_nsn':fsp_nsn['filename'].value_counts().values})
psvc_nsn = pd.DataFrame({'size':fsp_nsn['size'].value_counts().index, 'size_counts_nsn':fsp_nsn['size'].value_counts().values})
fsp_nsn = pd.merge(fsp_nsn, pvc_nsn, how = 'left', on = 'filename')
fsp_nsn = pd.merge(fsp_nsn, psvc_nsn, how = 'left', on = 'size')
🙋🏻♀️ Q. 이름 겹치는게 남아있나?
fsp_nsn[fsp_nsn['fn_counts_nsn']!=1]
→ 없다.
🙋🏻♀️ Q. 남아있는 사이즈 겹치는 것들의 갯수는?
print('사이즈 겹치는 사진의 갯수 :', len(fsp_nsn[fsp_nsn['size_counts_nsn']!=1]))
print('중복 사이즈의 갯수 :', len(psvc_nsn[psvc_nsn['size_counts_nsn']>1]))
사이즈 겹치는 사진의 갯수 : 60
중복 사이즈의 갯수 : 30
3. 이미지 비교
남은 것은 사이즈는 같지만 저장명은 완전히 다른 것들이다. 이미지 비교가 필요한 사진들이라 OpenCV와 skimage를 이용하였다.
1) 사이즈가 같은 두 이미지를 불러온다.
2) 이미지를 array로 변환했을 때 구조가 같다면 두 이미지에 차이가 있는지 확인한다.
3) 만약 두개의 이미지 구조가 같은데 차이가 존재한다면 그 사진은 직접 확인하기로 한다.
cv2.imread('path') : 이미지 읽기
import cv2 # OpenCV
from skimage.measure import compare_ssim
# 삭제될 사진의 리스트
delete = []
for i in range(len(psvc_nsn)):
# 중복된 크기(size)가 있는 경우
if psvc_nsn['size_counts_nsn'][i] == 2:
# 그 크기에 해당하는 사진을 불러온다.
temp = fsp_nsn[fsp_nsn['size']==psvc_nsn['size'][i]].reset_index(drop = True).sort_values(['filename'])
# 사진 읽기
imageA = cv2.imread('Photo/'+temp['filename_raw'][0])
imageB = cv2.imread('Photo/'+temp['filename_raw'][1])
# 이미지를 grayscale로 변환
grayA = cv2.cvtColor(imageA, cv2.COLOR_BGR2GRAY)
grayB = cv2.cvtColor(imageB, cv2.COLOR_BGR2GRAY)
# 이미지의 구조가 같다면 이미지 비교
if len(grayA)==len(grayB):
(score, diff) = compare_ssim(grayA, grayB, full=True)
# 차이가 없다면 하나는 delete에 넣어주기
if score == 1:
delete.append(temp['filename_raw'][1])
# 구조가 같지만 차이가 존재한다면 직접 확인하기
else:
print('확인해보시오! : ', temp['filename_raw'][0], '/', temp['filename_raw'][1], f'(score : {score})')
확인해보시오! : CB8D02A7-A4CD-4F8C-9459-DDB7D57BB5B2.jpeg / B72DC723-5AC4-42FE-8A46-CB68A16123D3.jpeg (score : 0.33035451451666076)
확인해보시오! : A1D66410-97E2-4E50-A605-47D589270D07.jpeg / 2A2FDD14-AE0B-4C93-89CA-F440646BBF8B.jpeg (score : 0.9999543491150039)
4. Result
1) Delete
앞전에 사진명 중복제거로 제거된 파일들을 delete리스트에 넣어준다.
# 중복제거된 것들은 delete 리스트에 넣어주기
delete = delete + list(fsp[~fsp['filename_raw'].isin(fsp_nsn['filename_raw'])]['filename_raw'])
print('삭제할 목록 :', len(delete))
삭제할 목록 : 1226
2) Result
전체데이터 - delete = 남길데이터
# result : 처음(fsp)데이터에서 - delete를 제외한 것
result = list(fsp[~fsp['filename_raw'].isin(delete)]['filename_raw'])
print('남길 목록 : ', len(result))
남길 목록 : 3855
3) 사진 옮기기
현재 정리되지 않은 사진들은 Photo에 있다. Delete와 Result 폴더에 버릴 사진과 남길 사진을 구분지어 옮긴다.
shutil.move('path/file', '이동할 경로') : 파일을 '이동할 경로'로 옮겨준다.
import shutil
for i in result:
shutil.move('Photo/'+i, '/Users/mizy/Desktop/mizy/Image/Result')
for i in delete:
shutil.move('Photo/'+i, '/Users/mizy/Desktop/mizy/Image/Delete')
무의미한 반복 작업을 하지 않고 중복 사진을 걸러낼 수 있어서 너무 상쾌했다. 약 1기가의 용량을 아낀셈이다.
정리된 Delete파일은 시원하게 휴지통에 넣어버렸다. :)
'Mizy's log' 카테고리의 다른 글
[ADP 실기] 20회-21회 데이터분석전문가 실기시험 문제 :: Python (0) | 2021.03.07 |
---|---|
GitHub 블로그 Minimal-mistakes 설정하기 :: MAC (0) | 2021.03.02 |
[컴활] 1급 실기 엑셀(Excel) 프로시저 정리 (0) | 2020.11.13 |
티스토리 광고 애드핏(Ad-fit) 설정하기 + 스킨편집 (0) | 2020.11.13 |
[ADP 실기] 18회-19회 데이터분석전문가 실기시험 문제 :: Python (0) | 2020.10.20 |
Comments