tao2/modules/compare.py

388 lines
15 KiB
Python

import cv2
import numpy as np
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QVBoxLayout, QPushButton, QFileDialog, QLineEdit
import requests
from PIL import Image
import os
import tempfile
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
class ImageThread(QThread):
finished = pyqtSignal(list)
def __init__(self, original_img_path, target_img_paths):
super().__init__()
self.original_img_path = original_img_path
self.target_img_paths = target_img_paths
def run(self):
# 원본 이미지 읽기
original_img = cv2.imread(self.original_img_path)
# 대상 이미지 연관도 분석
similarities = []
for target_img_path in self.target_img_paths:
target_img = cv2.imread(target_img_path)
similarity = self.calculate_similarity_with_ORB(original_img, target_img)
# similarity = self.calculate_similarity_with_L2(original_img, target_img)
# similarity = self.calculate_similarity_new(original_img, target_img)
similarities.append(similarity)
logger.debug(f"{target_img_path} 의 연관도 : {similarity}")
# 연관도가 높은 순으로 정렬 및 결과 추출
# sorted_indices = np.argsort(similarities)[::-1]
sorted_indices = np.argsort(similarities)
sorted_target_img_paths = [self.target_img_paths[i] for i in sorted_indices]
self.finished.emit(sorted_target_img_paths)
def calculate_similarity_with_L2(img1, img2, method="L2"):
# 이미지 전처리
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
img1 = cv2.GaussianBlur(img1, (5, 5), 0)
img1 = cv2.equalizeHist(img1)
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
img2 = cv2.GaussianBlur(img2, (5, 5), 0)
img2 = cv2.equalizeHist(img2)
# 유사도 계산
if method == "L1":
similarity = cv2.norm(img1, img2, cv2.NORM_L1)
elif method == "L2":
similarity = cv2.norm(img1, img2, cv2.NORM_L2)
elif method == "cosine":
similarity = cv2.compareHist(img1, img2, cv2.CV_COMP_COSINE)
elif method == "jaccard":
similarity = cv2.compareHist(img1, img2, cv2.CV_COMP_JACCARD)
else:
raise ValueError("Invalid similarity method")
return similarity
def calculate_similarity_with_ORB(self, img1, img2):
# 이미지 특징 추출 (ORB)
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# 특징 매칭 (BFMatcher)
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.knnMatch(des1, des2, k=2)
# # 좋은 매칭만 추출
# good_matches = [m for m in matches if m.distance < 0.55]
# 좋은 매칭 추출 (lowe's ratio test)
good_matches = []
if len(matches) > 0: # matches 리스트가 비어 있는지 확인
for m, n in matches:
if m.distance < 0.7 * n.distance:
good_matches.append(m)
logger.debug(f"good_matches : {good_matches}")
# 연관도 계산
if len(kp1) > 0 and len(kp2) > 0:
similarity = len(good_matches) / ((len(kp1) + len(kp2)) / 2)
else:
similarity = 0.0
else:
similarity = 0.0
logger.debug("No matches found.") # 매칭이 없는 경우 메시지 출력
logger.debug(f"similarity : {similarity}")
return similarity
def calculate_similarity_new(self, img1, img2):
# 이미지 전처리
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
gray1 = cv2.GaussianBlur(gray1, (5, 5), 0)
gray2 = cv2.GaussianBlur(gray2, (5, 5), 0)
gray1 = cv2.equalizeHist(gray1)
gray2 = cv2.equalizeHist(gray2)
# 히스토그램 Equalize 후 dtype을 32bit float으로 변환
gray1 = cv2.equalizeHist(gray1).astype(np.float32)
gray2 = cv2.equalizeHist(gray2).astype(np.float32)
# Hu 불변 모멘트 계산
moments1 = cv2.HuMoments(cv2.moments(gray1))
moments2 = cv2.HuMoments(cv2.moments(gray2))
# Hu 불변 모멘트 벡터 간의 L2 거리 계산
l2_distance = cv2.norm(moments1, moments2, cv2.NORM_L2)
# L2 거리 계산
# l2_distance = cv2.norm(gray1, gray2, cv2.NORM_L2)
# 코사인 유사도 계산
# cosine_similarity = cv2.compareHist(gray1, gray2, cv2.CV_COMP_COSINE)
cosine_similarity = cv2.compareHist(gray1, gray2, 3)
# Jaccard 지수 계산
# jaccard_index = cv2.compareHist(gray1, gray2, cv2.CV_COMP_JACCARD)
jaccard_index = cv2.compareHist(gray1, gray2, 3)
gray11 = cv2.cvtColor(gray1, cv2.COLOR_BGR2GRAY).astype('uint8')
gray22 = cv2.cvtColor(gray2, cv2.COLOR_BGR2GRAY).astype('uint8')
# SIFT 특징 추출 및 매칭
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(gray11, None)
kp2, des2 = sift.detectAndCompute(gray22, None)
# FLANN 매칭
flann = cv2.FlannBasedMatcher()
matches = flann.knnMatch(des1, des2, k=2)
# 좋은 매칭 추출 (Lowe's ratio test)
good_matches = []
for m, n in matches:
if m.distance < 0.8 * n.distance:
good_matches.append([m])
# 모양 기반 유사도 계산
shape_similarity = len(good_matches) / (len(kp1) + len(kp2)) / 2
# 최종 유사도 계산 (가중치 조절 가능)
similarity = (0.4 * l2_distance) + (0.2 * cosine_similarity) + (0.2 * jaccard_index) + (0.2 * shape_similarity)
return similarity
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Image Similarity Analysis")
self.main_widget = QWidget()
self.setCentralWidget(self.main_widget)
# 레이아웃 설정
layout = QVBoxLayout()
self.main_widget.setLayout(layout)
ori_url = "https://img.danawa.com/prod_img/500000/800/949/img/9949800_1.jpg"
default_urls = ["https://m.hwangso114.com/web/product/big/202209/880f46bb32f942ec327140e0fe042afc.png", "https://ae01.alicdn.com/kf/S31b266a3d66a493d9e7f3a9dd4e25e49o.png"] # 기본값 리스트
# 원본 이미지 선택
self.original_img_label = QLabel("원본 이미지")
layout.addWidget(self.original_img_label)
self.original_img_path_edit = QLineEdit()
self.original_img_path_edit.setPlaceholderText("원본 이미지 경로를 입력하세요")
self.original_img_path_edit.setText(ori_url) # 기본값 설정
layout.addWidget(self.original_img_path_edit)
# 대상 이미지 선택
self.target_img_labels = []
self.target_img_path_edits = []
for i, default_url in enumerate(default_urls):
label = QLabel(f"대상 이미지 {i+1}")
layout.addWidget(label)
self.target_img_labels.append(label)
edit = QLineEdit()
edit.setPlaceholderText(f"대상 이미지 {i+1} 경로를 입력하세요")
layout.addWidget(edit)
edit.setText(default_url) # 기본값 설정
self.target_img_path_edits.append(edit)
# 비교 버튼
self.compare_button = QPushButton("비교")
self.compare_button.clicked.connect(self.compare_images)
layout.addWidget(self.compare_button)
# 팝업 창 설정
self.popup_window = QWidget()
self.popup_window.setWindowTitle("비교 결과")
self.popup_layout = QVBoxLayout()
self.popup_window.setLayout(self.popup_layout)
# 팝업 창에 원본 이미지 표시
self.original_img_popup_label = QLabel()
self.popup_layout.addWidget(self.original_img_popup_label)
# 팝업 창에 결과 표시
self.target_img_popup_labels = []
for i in range(3):
label = QLabel()
self.popup_layout.addWidget(label)
self.target_img_popup_labels.append(label)
def download_and_process_images(self, urls):
"""
URL 리스트에서 이미지를 다운받아 'img' 폴더에 저장합니다.
Args:
urls (list): 이미지 URL 리스트
Returns:
list: 로컬 파일 경로 리스트
"""
# 'img' 폴더 경로 확인 및 생성
img_folder_path = os.path.join(os.getcwd(), 'img')
if not os.path.exists(img_folder_path):
os.makedirs(img_folder_path)
local_paths = []
for i, url in enumerate(urls):
# if i == 0:
# # filename = "original.jpg"
# else:
filename = f"target{i}.jpg"
local_path = os.path.join(img_folder_path, filename)
# 이미지 다운로드
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"DNT": "1", # Do Not Track 요청 헤더 (사용자의 추적을 거부)
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1", # https로의 업그레이드를 요청
"Cache-Control": "max-age=0", # 캐시된 콘텐츠를 재사용하지 않도록 요청
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
with open(local_path, "wb") as f:
f.write(response.content)
# 이미지 처리 로직 (필요한 경우)
# 예: 썸네일 생성, 이미지 사이즈 조정 등
local_paths.append(local_path)
else:
logger.debug(f"Error downloading image: {url}")
return local_paths
def download_and_process_image(self, urls):
"""
URL 리스트에서 이미지를 다운받아 'img' 폴더에 저장합니다.
Args:
urls (list): 이미지 URL 리스트
Returns:
list: 로컬 파일 경로 리스트
"""
# 'img' 폴더 경로 확인 및 생성
img_folder_path = os.path.join(os.getcwd(), 'ori_img')
if not os.path.exists(img_folder_path):
os.makedirs(img_folder_path)
local_paths = []
for i, url in enumerate(urls):
if i == 0:
filename = "original.jpg"
local_path = os.path.join(img_folder_path, filename)
# 이미지 다운로드
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"DNT": "1", # Do Not Track 요청 헤더 (사용자의 추적을 거부)
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1", # https로의 업그레이드를 요청
"Cache-Control": "max-age=0", # 캐시된 콘텐츠를 재사용하지 않도록 요청
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
with open(local_path, "wb") as f:
f.write(response.content)
# 이미지 처리 로직 (필요한 경우)
# 예: 썸네일 생성, 이미지 사이즈 조정 등
local_paths.append(local_path)
else:
logger.debug(f"Error downloading image: {url}")
return local_paths
def compare_images(self):
"""
입력받은 URL 이미지들을 로컬에 저장하고, 비교 스레드를 시작합니다.
"""
# 입력 경로 확인
original_img_url = self.original_img_path_edit.text()
if not original_img_url:
return
# 원본 이미지 로컬 저장 및 경로
local_paths = self.download_and_process_image([original_img_url])
if not local_paths:
return
original_img_path = local_paths[0]
self.original_img_path = local_paths[0] # 클래스 변수 업데이트
# 대상 이미지 로컬 저장 및 경로 리스트
target_img_urls = [edit.text() for edit in self.target_img_path_edits]
target_img_paths = self.download_and_process_images(target_img_urls)
if not target_img_paths:
return
# 이미지 비교 스레드 시작
self.thread = ImageThread(original_img_path, target_img_paths)
self.thread.finished.connect(self.show_results)
self.thread.start()
def add_text_to_image(self, img, text):
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
font_color = (255, 0, 0) # 빨간색
font_thickness = 2
cv2.putText(img, text, (10, 30), font, font_scale, font_color, font_thickness)
def show_results(self, sorted_target_img_paths):
# 원본 이미지 팝업
original_img = cv2.imread(self.original_img_path)
original_img = cv2.resize(original_img, (200, 200))
self.add_text_to_image(original_img, "Original") # 원본 이미지에 텍스트 추가
qimage = QImage(original_img.data, original_img.shape[1], original_img.shape[0], QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(qimage)
self.original_img_popup_label.setPixmap(pixmap)
# 대상 이미지 팝업 업데이트
for i, label in enumerate(self.target_img_popup_labels):
if i < len(sorted_target_img_paths):
target_img_path = sorted_target_img_paths[i]
target_img = cv2.imread(target_img_path)
target_img = cv2.resize(target_img, (200, 200))
self.add_text_to_image(target_img, f"Target {i+1}") # 대상 이미지에 텍스트 추가
qimage = QImage(target_img.data, target_img.shape[1], target_img.shape[0], QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(qimage)
label.setPixmap(pixmap)
else:
label.setPixmap(QPixmap()) # 이미지가 없는 경우 비워둠
# 팝업 창 보여주기
self.popup_window.show()
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()