14_feature_matching.py

Download
python 357 lines 10.4 KB
  1"""
  214. 특징점 매칭
  3- BFMatcher (Brute Force)
  4- FLANN 매처
  5- 좋은 매칭 선별 (ratio test)
  6- Homography 계산
  7"""
  8
  9import cv2
 10import numpy as np
 11
 12
 13def create_test_images():
 14    """매칭용 테스트 이미지 쌍 생성"""
 15    # 원본 이미지
 16    img1 = np.zeros((300, 400, 3), dtype=np.uint8)
 17    img1[:] = [200, 200, 200]
 18
 19    # 특징이 있는 패턴
 20    cv2.rectangle(img1, (50, 50), (150, 150), (50, 50, 50), -1)
 21    cv2.circle(img1, (250, 100), 40, (100, 100, 100), -1)
 22    cv2.rectangle(img1, (300, 150), (380, 250), (80, 80, 80), -1)
 23
 24    # 체커보드 패턴 추가
 25    for i in range(3):
 26        for j in range(3):
 27            x, y = 100 + i * 30, 180 + j * 30
 28            if (i + j) % 2 == 0:
 29                cv2.rectangle(img1, (x, y), (x + 30, y + 30), (0, 0, 0), -1)
 30
 31    cv2.putText(img1, 'MATCH', (150, 280), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 2)
 32
 33    # 변환된 이미지 (회전 + 스케일)
 34    h, w = img1.shape[:2]
 35    center = (w // 2, h // 2)
 36    M = cv2.getRotationMatrix2D(center, 15, 0.9)  # 15도 회전, 0.9배 스케일
 37    img2 = cv2.warpAffine(img1, M, (w, h), borderValue=(200, 200, 200))
 38
 39    return img1, img2
 40
 41
 42def bf_matcher_demo():
 43    """Brute Force 매처 데모"""
 44    print("=" * 50)
 45    print("BFMatcher (Brute Force Matcher)")
 46    print("=" * 50)
 47
 48    img1, img2 = create_test_images()
 49    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
 50    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
 51
 52    # ORB 특징점 검출
 53    orb = cv2.ORB_create()
 54    kp1, des1 = orb.detectAndCompute(gray1, None)
 55    kp2, des2 = orb.detectAndCompute(gray2, None)
 56
 57    # BFMatcher 생성
 58    # NORM_HAMMING: 이진 디스크립터용 (ORB, BRIEF)
 59    # NORM_L2: 실수 디스크립터용 (SIFT, SURF)
 60    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
 61
 62    # 매칭
 63    matches = bf.match(des1, des2)
 64
 65    # 거리순 정렬
 66    matches = sorted(matches, key=lambda x: x.distance)
 67
 68    print(f"키포인트: img1={len(kp1)}, img2={len(kp2)}")
 69    print(f"매칭 수: {len(matches)}")
 70
 71    # 상위 매칭 정보
 72    print("\n상위 5개 매칭:")
 73    for i, m in enumerate(matches[:5]):
 74        print(f"  {i}: queryIdx={m.queryIdx}, trainIdx={m.trainIdx}, distance={m.distance:.1f}")
 75
 76    # 결과 그리기
 77    result = cv2.drawMatches(
 78        img1, kp1, img2, kp2,
 79        matches[:20], None,
 80        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
 81    )
 82
 83    print("\nBFMatcher 특성:")
 84    print("  - 모든 쌍 비교 (O(n*m))")
 85    print("  - crossCheck=True: 상호 최근접만 선택")
 86    print("  - 정확하지만 느림")
 87
 88    cv2.imwrite('bf_match_img1.jpg', img1)
 89    cv2.imwrite('bf_match_img2.jpg', img2)
 90    cv2.imwrite('bf_match_result.jpg', result)
 91
 92
 93def knn_match_demo():
 94    """KNN 매칭과 Ratio Test 데모"""
 95    print("\n" + "=" * 50)
 96    print("KNN 매칭 + Ratio Test")
 97    print("=" * 50)
 98
 99    img1, img2 = create_test_images()
100    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
101    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
102
103    orb = cv2.ORB_create()
104    kp1, des1 = orb.detectAndCompute(gray1, None)
105    kp2, des2 = orb.detectAndCompute(gray2, None)
106
107    # BFMatcher (crossCheck=False for knnMatch)
108    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
109
110    # KNN 매칭 (k=2)
111    matches = bf.knnMatch(des1, des2, k=2)
112
113    print(f"KNN 매칭 수: {len(matches)}")
114
115    # Lowe's Ratio Test
116    # 최근접 거리 / 차선 거리 < threshold
117    good_matches = []
118    for m, n in matches:
119        if m.distance < 0.75 * n.distance:
120            good_matches.append(m)
121
122    print(f"Ratio Test 후: {len(good_matches)}")
123
124    # 결과 그리기
125    result = cv2.drawMatches(
126        img1, kp1, img2, kp2,
127        good_matches, None,
128        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
129    )
130
131    print("\nRatio Test (Lowe's ratio):")
132    print("  - 최근접 / 차선 < 0.75 (보통 0.7~0.8)")
133    print("  - 모호한 매칭 제거")
134    print("  - 오매칭 감소")
135
136    cv2.imwrite('knn_match_result.jpg', result)
137
138    return kp1, kp2, good_matches
139
140
141def flann_matcher_demo():
142    """FLANN 매처 데모"""
143    print("\n" + "=" * 50)
144    print("FLANN Matcher")
145    print("=" * 50)
146
147    img1, img2 = create_test_images()
148    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
149    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
150
151    try:
152        # SIFT 사용 (실수 디스크립터)
153        sift = cv2.SIFT_create()
154        kp1, des1 = sift.detectAndCompute(gray1, None)
155        kp2, des2 = sift.detectAndCompute(gray2, None)
156
157        # FLANN 파라미터 (SIFT/SURF용)
158        FLANN_INDEX_KDTREE = 1
159        index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
160        search_params = dict(checks=50)
161
162    except AttributeError:
163        # SIFT 없으면 ORB 사용
164        print("SIFT 없음, ORB 사용")
165        orb = cv2.ORB_create()
166        kp1, des1 = orb.detectAndCompute(gray1, None)
167        kp2, des2 = orb.detectAndCompute(gray2, None)
168
169        # FLANN 파라미터 (ORB용)
170        FLANN_INDEX_LSH = 6
171        index_params = dict(
172            algorithm=FLANN_INDEX_LSH,
173            table_number=6,
174            key_size=12,
175            multi_probe_level=1
176        )
177        search_params = dict(checks=50)
178
179    # FLANN 매처 생성
180    flann = cv2.FlannBasedMatcher(index_params, search_params)
181
182    # KNN 매칭
183    matches = flann.knnMatch(des1, des2, k=2)
184
185    # Ratio Test
186    good_matches = []
187    for pair in matches:
188        if len(pair) == 2:
189            m, n = pair
190            if m.distance < 0.75 * n.distance:
191                good_matches.append(m)
192
193    print(f"FLANN 매칭: {len(matches)} → 좋은 매칭: {len(good_matches)}")
194
195    # 결과 그리기
196    result = cv2.drawMatches(
197        img1, kp1, img2, kp2,
198        good_matches, None,
199        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
200    )
201
202    print("\nFLANN 특성:")
203    print("  - 근사 최근접 이웃 탐색")
204    print("  - 대규모 데이터에 효율적")
205    print("  - KD-Tree (SIFT) 또는 LSH (ORB)")
206
207    cv2.imwrite('flann_match_result.jpg', result)
208
209
210def homography_demo():
211    """호모그래피 계산 데모"""
212    print("\n" + "=" * 50)
213    print("호모그래피 (Homography)")
214    print("=" * 50)
215
216    img1, img2 = create_test_images()
217    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
218    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
219
220    orb = cv2.ORB_create()
221    kp1, des1 = orb.detectAndCompute(gray1, None)
222    kp2, des2 = orb.detectAndCompute(gray2, None)
223
224    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
225    matches = bf.knnMatch(des1, des2, k=2)
226
227    # Ratio Test
228    good_matches = []
229    for m, n in matches:
230        if m.distance < 0.75 * n.distance:
231            good_matches.append(m)
232
233    print(f"좋은 매칭 수: {len(good_matches)}")
234
235    if len(good_matches) >= 4:
236        # 매칭점 좌표 추출
237        src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
238        dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
239
240        # 호모그래피 계산 (RANSAC)
241        H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
242        matches_mask = mask.ravel().tolist()
243
244        inliers = sum(matches_mask)
245        print(f"인라이어: {inliers}/{len(good_matches)}")
246
247        if H is not None:
248            print(f"\n호모그래피 행렬:\n{H}")
249
250            # img1의 경계를 img2에 투영
251            h, w = img1.shape[:2]
252            pts = np.float32([[0, 0], [w, 0], [w, h], [0, h]]).reshape(-1, 1, 2)
253            dst = cv2.perspectiveTransform(pts, H)
254
255            # img2에 경계 그리기
256            result = img2.copy()
257            dst = np.int32(dst)
258            cv2.polylines(result, [dst], True, (0, 255, 0), 3)
259
260            cv2.imwrite('homography_result.jpg', result)
261
262            # 매칭 시각화 (인라이어만)
263            draw_params = dict(
264                matchColor=(0, 255, 0),
265                singlePointColor=None,
266                matchesMask=matches_mask,
267                flags=2
268            )
269            match_result = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, **draw_params)
270            cv2.imwrite('homography_matches.jpg', match_result)
271
272    print("\n호모그래피 용도:")
273    print("  - 이미지 정합 (Image Registration)")
274    print("  - 파노라마 스티칭")
275    print("  - 객체 인식 (위치 추정)")
276    print("  - 증강 현실")
277
278
279def match_object_demo():
280    """객체 매칭 데모"""
281    print("\n" + "=" * 50)
282    print("객체 매칭 실습")
283    print("=" * 50)
284
285    # 템플릿 이미지 (찾을 객체)
286    template = np.zeros((100, 100, 3), dtype=np.uint8)
287    template[:] = [200, 200, 200]
288    cv2.rectangle(template, (10, 10), (90, 90), (50, 50, 50), -1)
289    cv2.circle(template, (50, 50), 20, (100, 100, 100), -1)
290
291    # 장면 이미지 (객체가 포함된)
292    scene = np.zeros((300, 400, 3), dtype=np.uint8)
293    scene[:] = [180, 180, 180]
294
295    # 템플릿을 장면에 배치 (회전 및 스케일 적용)
296    h, w = template.shape[:2]
297    center = (w // 2, h // 2)
298    M = cv2.getRotationMatrix2D(center, 30, 0.8)
299    rotated_template = cv2.warpAffine(template, M, (w, h), borderValue=(180, 180, 180))
300
301    # 장면에 붙이기
302    scene[100:200, 150:250] = rotated_template
303
304    # 다른 객체 추가 (방해물)
305    cv2.circle(scene, (80, 80), 30, (120, 120, 120), -1)
306    cv2.rectangle(scene, (300, 200), (380, 280), (90, 90, 90), -1)
307
308    # 특징점 매칭
309    gray_template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
310    gray_scene = cv2.cvtColor(scene, cv2.COLOR_BGR2GRAY)
311
312    orb = cv2.ORB_create()
313    kp1, des1 = orb.detectAndCompute(gray_template, None)
314    kp2, des2 = orb.detectAndCompute(gray_scene, None)
315
316    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
317    matches = bf.knnMatch(des1, des2, k=2)
318
319    good = []
320    for m, n in matches:
321        if m.distance < 0.75 * n.distance:
322            good.append(m)
323
324    print(f"템플릿 키포인트: {len(kp1)}")
325    print(f"장면 키포인트: {len(kp2)}")
326    print(f"좋은 매칭: {len(good)}")
327
328    # 결과 시각화
329    result = cv2.drawMatches(template, kp1, scene, kp2, good, None)
330    cv2.imwrite('object_template.jpg', template)
331    cv2.imwrite('object_scene.jpg', scene)
332    cv2.imwrite('object_match.jpg', result)
333
334
335def main():
336    """메인 함수"""
337    # BF 매처
338    bf_matcher_demo()
339
340    # KNN 매칭
341    knn_match_demo()
342
343    # FLANN 매처
344    flann_matcher_demo()
345
346    # 호모그래피
347    homography_demo()
348
349    # 객체 매칭
350    match_object_demo()
351
352    print("\n특징점 매칭 데모 완료!")
353
354
355if __name__ == '__main__':
356    main()