V2 로직(OpenCV 활용) - 100-hours-a-week/5-yeosa-wiki GitHub Wiki

1. 기존 로직의 문제점

문제점 설명
1. 의미적 유사성까지 포함됨 CLIP은 의미 기반 임베딩이므로, 내용이 다르더라도 비슷한 상황/구도/개념이라면 유사하게 판단됨예: 다른 장소에서 책 읽는 사진 → 중복으로 분류될 수 있음
2. 외형이 유사한 사진 오탐지 배경, 조명, 인물 포즈가 유사한 다른 장면도 클러스터로 묶임
3. 정작 진짜 중복은 누락 가능 예를 들어 해상도만 다른 사진, 확대·크롭된 동일 사진이 유사도 낮게 나올 수 있음
4. 분류 기준 모호함 의미 중심이므로, ‘진짜 중복’과 ‘비슷한 사진’의 경계가 불명확

2. 개선된 방식: pHash + Hamming Distance

a. pHash (Perceptual Hash)란?

가. 특징

  • 이미지의 시각적 특성을 정수형 hash 값(64bit 등)으로 표현하는 알고리즘

  • 주요 특징:

    • 이미지의 구조, 윤곽, 명암 등 perceptual 정보만 유지
    • 해상도, 색상, 포맷 변화에 강건
    • 동일 이미지의 hash는 거의 같고, 유사 이미지도 비슷하게 생성됨

    → pHash는 "이 이미지가 시각적으로 같은가?"를 판단하는 데 최적화된 fingerprint 방식

나. 계산 방식

[ pHash는 이미지의 시각적 구조(형태, 명암, 윤곽 등) 을 압축해 64비트(또는 그 이상)의 해시를 생성 ]

  1. 이미지 전처리
  • 이미지를 흑백(그레이스케일) 로 변환
  • 크기를 고정된 크기 (예: 32×32)로 리사이징
  1. 주파수 변환 (DCT)
  • Discrete Cosine Transform(이산 코사인 변환)을 적용
  • 이미지의 저주파 성분(전체적인 윤곽과 구조)을 추출
  1. DCT 결과의 상위 8×8 블록 선택
  • DC 성분(맨 왼쪽 상단 1개)을 제외한 상위 8×8의 DCT 계수만 추출
    • 즉, 총 64개의 숫자 → hash의 각 비트로 사용
  1. 평균값과 비교해 비트화
  • 추출한 64개의 값의 평균을 구함
  • 각 값이 평균보다 크면 1, 작으면 0 → 64비트 해시 생성

b. Hamming Distance란?

  • 두 binary 벡터 간 서로 다른 비트 수를 측정하는 거리 지표

  • 예시:

    hash1 = 10110100
    hash2 = 10111110
           → ↑ ↑
    → 해밍 거리 = 2
    
  • 0에 가까울수록 거의 동일한 이미지

  • 실무에서는 10 이하를 중복 기준으로 삼는 경우가 많음 (64bit hash 기준)


3. 개선된 로직 구조

a. 이미지 로딩

images = await image_loader.load_images(image_refs, 'GRAY')
  • 입력 이미지들을 회색조로 로딩 (pHash는 회색 이미지 기반이 효과적)

b. Perceptual Hash (pHash) 계산

def compute_hashes(images: List[np.ndarray]) -> np.ndarray:
    """
    입력 이미지 리스트에 대해 OpenCV pHash를 계산합니다.

    Args:
        images (List[np.ndarray]): OpenCV 이미지 배열 리스트

    Returns:
        np.ndarray: shape (N, 64) pHash 결과
    """
    hasher = cv2.img_hash.PHash_create()
    return np.vstack([hasher.compute(img) for img in images])

hashes = compute_hashes(images)  # → shape: (N, 64)
  • OpenCV의 cv2.img_hash.PHash_create() 사용
  • 각 이미지의 64비트 perceptual hash 벡터 계산

c. 해밍 거리 행렬 생성

def compute_hamming_matrix(hashes: np.ndarray) -> np.ndarray:
    """
    이미지 해시 간 해밍 거리 행렬을 계산합니다.

    Args:
        hashes (np.ndarray): shape (N, 64) 해시 배열

    Returns:
        np.ndarray: shape (N, N) 해밍 거리 행렬
    """
    expanded = np.expand_dims(hashes, axis=1)
    xor_matrix = np.bitwise_xor(expanded, hashes)
    hamming_matrix = np.unpackbits(xor_matrix, axis=-1).sum(axis=-1)
    return hamming_matrix.astype(np.uint8)

hamming_matrix = compute_hamming_matrix(hashes)  # → shape: (N, N)
  • 두 hash 간 비트 단위 XOR → np.unpackbits로 해밍 거리 계산

d. DBSCAN 클러스터링

def cluster_with_dbscan(
    hamming_matrix: np.ndarray, eps: int = 10, min_samples: int = 2
) -> np.ndarray:
    """
    해밍 거리 기반 DBSCAN 클러스터링을 수행합니다.

    Args:
        hamming_matrix (np.ndarray): 사전 계산된 거리 행렬 (N, N)
        eps (int, optional): 최대 해밍 거리 임계값. Defaults to 10.
        min_samples (int, optional): 최소 클러스터 크기. Defaults to 2.

    Returns:
        np.ndarray: shape (N,) 클러스터 레이블 (-1은 노이즈)
    """
    clustering = DBSCAN(
        metric="precomputed", eps=eps, min_samples=min_samples
    )
    return clustering.fit_predict(hamming_matrix)
    
    
labels = cluster_with_dbscan(hamming_matrix, eps=10, min_samples=2)
  • eps=10 이하는 거의 동일한 이미지로 간주
  • min_samples=2: 최소 2장 이상이 있어야 클러스터로 인정

e. 결과 그룹핑

def group_by_labels(labels: np.ndarray, file_names: List[str]) -> List[List[str]]:
    """
    DBSCAN 레이블을 기반으로 파일명을 클러스터별로 그룹화합니다.

    Args:
        labels (np.ndarray): DBSCAN 결과 레이블 (N,)
        file_names (List[str]): 파일명 리스트

    Returns:
        List[List[str]]: 유사 이미지 그룹 목록
    """
    groups = {}

    for idx, label in enumerate(labels):
        if label == -1:
            continue  # 노이즈 제외
        groups.setdefault(label, []).append(file_names[idx])

    return list(groups.values())
    
    
groups = group_by_labels(labels, image_refs)
  • 클러스터 ID 기준으로 중복 이미지 그룹 생성

4. 기존 로직 vs 개선 로직 비교 요약

항목 기존 방식 (CLIP 기반) 개선 방식 (pHash 기반)
유사도 기준 CLIP 임베딩 간 cosine similarity pHash 간 Hamming distance
주요 특징 의미 기반 유사도 (semantic similarity) 이미지 구조 기반 유사도 (visual similarity)
단점 다른 장면이라도 ‘비슷한 의미’면 묶임 (ex: 두 사람이 각기 다른 장소에서 책을 읽는 장면) -
장점 조명, 색상, 배경이 조금 달라도 같은 개념이면 유사함 픽셀 기반이므로 ‘실제 중복 사진’ 판별에 적합
클러스터링 DBSCAN (cosine distance 기반) DBSCAN (Hamming distance 기반)
입력 이미지 임베딩 (torch.Tensor) OpenCV 이미지 (np.ndarray, GRAY)
성능 의미적 유사 이미지 포함 위험 있음 오직 ‘거의 동일한 이미지’만 묶음

5. 개선 효과 요약

효과 설명
‘진짜 중복 사진’만 탐지 의미적 유사 이미지가 아니라 시각적으로 동일한 이미지만 묶임
빠른 처리 속도 pHash는 경량 연산으로 GPU 없이도 빠르게 처리 가능
노이즈에 강함 색상·밝기 변화, 경미한 회전 등에도 동일한 hash 출력 가능
DBSCAN 기반 확장성 유지 threshold 조정 (eps)만으로 유연한 판별 가능