15_object_detection.py

Download
python 373 lines 11.2 KB
  1"""
  215. 객체 검출 기초
  3- Template Matching
  4- Haar Cascade
  5- HOG + SVM (개념)
  6"""
  7
  8import cv2
  9import numpy as np
 10
 11
 12def create_scene_with_objects():
 13    """객체가 있는 장면 이미지 생성"""
 14    scene = np.zeros((400, 600, 3), dtype=np.uint8)
 15    scene[:] = [200, 200, 200]
 16
 17    # 별 모양 객체 (찾을 대상)
 18    def draw_star(img, center, size, color):
 19        pts = []
 20        for i in range(5):
 21            outer = np.radians(i * 72 - 90)
 22            inner = np.radians(i * 72 + 36 - 90)
 23            pts.append([int(center[0] + size * np.cos(outer)),
 24                       int(center[1] + size * np.sin(outer))])
 25            pts.append([int(center[0] + size * 0.4 * np.cos(inner)),
 26                       int(center[1] + size * 0.4 * np.sin(inner))])
 27        cv2.fillPoly(img, [np.array(pts, np.int32)], color)
 28
 29    # 여러 별 배치
 30    draw_star(scene, (100, 100), 30, (0, 0, 150))
 31    draw_star(scene, (300, 200), 40, (0, 0, 180))
 32    draw_star(scene, (500, 300), 35, (0, 0, 160))
 33
 34    # 방해 객체
 35    cv2.circle(scene, (200, 300), 40, (150, 0, 0), -1)
 36    cv2.rectangle(scene, (400, 50), (480, 130), (0, 150, 0), -1)
 37
 38    # 템플릿 (별 하나)
 39    template = np.zeros((80, 80, 3), dtype=np.uint8)
 40    template[:] = [200, 200, 200]
 41    draw_star(template, (40, 40), 30, (0, 0, 150))
 42
 43    return scene, template
 44
 45
 46def template_matching_demo():
 47    """템플릿 매칭 데모"""
 48    print("=" * 50)
 49    print("템플릿 매칭 (Template Matching)")
 50    print("=" * 50)
 51
 52    scene, template = create_scene_with_objects()
 53
 54    # 그레이스케일 변환
 55    scene_gray = cv2.cvtColor(scene, cv2.COLOR_BGR2GRAY)
 56    template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
 57
 58    h, w = template_gray.shape
 59
 60    # 템플릿 매칭 방법들
 61    methods = [
 62        ('TM_CCOEFF', cv2.TM_CCOEFF),
 63        ('TM_CCOEFF_NORMED', cv2.TM_CCOEFF_NORMED),
 64        ('TM_CCORR_NORMED', cv2.TM_CCORR_NORMED),
 65        ('TM_SQDIFF_NORMED', cv2.TM_SQDIFF_NORMED),
 66    ]
 67
 68    print("\n매칭 방법 결과:")
 69
 70    for name, method in methods:
 71        result = cv2.matchTemplate(scene_gray, template_gray, method)
 72
 73        # 최소/최대 위치 찾기
 74        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
 75
 76        # SQDIFF는 최소값이 최적
 77        if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
 78            top_left = min_loc
 79            score = min_val
 80        else:
 81            top_left = max_loc
 82            score = max_val
 83
 84        print(f"  {name}: score={score:.4f}, loc={top_left}")
 85
 86        # 결과 시각화
 87        scene_copy = scene.copy()
 88        bottom_right = (top_left[0] + w, top_left[1] + h)
 89        cv2.rectangle(scene_copy, top_left, bottom_right, (0, 255, 0), 2)
 90        cv2.imwrite(f'template_{name}.jpg', scene_copy)
 91
 92    print("\n매칭 방법 특성:")
 93    print("  TM_SQDIFF: 차이 제곱합 (작을수록 좋음)")
 94    print("  TM_CCORR: 상관관계 (클수록 좋음)")
 95    print("  TM_CCOEFF: 상관계수 (클수록 좋음)")
 96    print("  _NORMED: 정규화 버전 (-1~1 또는 0~1)")
 97
 98    cv2.imwrite('template_scene.jpg', scene)
 99    cv2.imwrite('template_template.jpg', template)
100
101
102def multi_scale_template_demo():
103    """다중 스케일 템플릿 매칭"""
104    print("\n" + "=" * 50)
105    print("다중 스케일 템플릿 매칭")
106    print("=" * 50)
107
108    scene, template = create_scene_with_objects()
109    scene_gray = cv2.cvtColor(scene, cv2.COLOR_BGR2GRAY)
110    template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
111
112    best_match = None
113    best_val = -1
114    best_scale = 1.0
115    best_loc = (0, 0)
116
117    # 다양한 스케일로 매칭
118    scales = [0.5, 0.75, 1.0, 1.25, 1.5]
119
120    for scale in scales:
121        # 템플릿 크기 조정
122        new_w = int(template_gray.shape[1] * scale)
123        new_h = int(template_gray.shape[0] * scale)
124
125        if new_w > scene_gray.shape[1] or new_h > scene_gray.shape[0]:
126            continue
127
128        resized = cv2.resize(template_gray, (new_w, new_h))
129
130        # 매칭
131        result = cv2.matchTemplate(scene_gray, resized, cv2.TM_CCOEFF_NORMED)
132        _, max_val, _, max_loc = cv2.minMaxLoc(result)
133
134        print(f"  Scale {scale:.2f}: score={max_val:.4f}")
135
136        if max_val > best_val:
137            best_val = max_val
138            best_scale = scale
139            best_loc = max_loc
140            best_match = resized.shape
141
142    print(f"\n최적 스케일: {best_scale}, score={best_val:.4f}")
143
144    # 결과 그리기
145    result_img = scene.copy()
146    h, w = best_match
147    cv2.rectangle(result_img, best_loc, (best_loc[0]+w, best_loc[1]+h), (0, 255, 0), 2)
148    cv2.imwrite('multi_scale_result.jpg', result_img)
149
150
151def find_all_matches_demo():
152    """모든 매칭 찾기"""
153    print("\n" + "=" * 50)
154    print("모든 매칭 찾기")
155    print("=" * 50)
156
157    scene, template = create_scene_with_objects()
158    scene_gray = cv2.cvtColor(scene, cv2.COLOR_BGR2GRAY)
159    template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
160
161    h, w = template_gray.shape
162
163    # 템플릿 매칭
164    result = cv2.matchTemplate(scene_gray, template_gray, cv2.TM_CCOEFF_NORMED)
165
166    # 임계값 이상인 모든 위치 찾기
167    threshold = 0.6
168    locations = np.where(result >= threshold)
169
170    scene_copy = scene.copy()
171    match_count = 0
172
173    # 중복 제거를 위한 NMS 적용
174    boxes = []
175    for pt in zip(*locations[::-1]):
176        boxes.append([pt[0], pt[1], pt[0]+w, pt[1]+h])
177
178    # 간단한 NMS
179    boxes = np.array(boxes)
180    if len(boxes) > 0:
181        # 점수순 정렬
182        scores = [result[b[1], b[0]] for b in boxes]
183        indices = np.argsort(scores)[::-1]
184
185        keep = []
186        while len(indices) > 0:
187            i = indices[0]
188            keep.append(i)
189
190            # 다른 박스와의 겹침 계산
191            remaining = []
192            for j in indices[1:]:
193                # IoU 계산 (간단 버전)
194                x_overlap = max(0, min(boxes[i][2], boxes[j][2]) - max(boxes[i][0], boxes[j][0]))
195                y_overlap = max(0, min(boxes[i][3], boxes[j][3]) - max(boxes[i][1], boxes[j][1]))
196                overlap = x_overlap * y_overlap
197                area = w * h
198                iou = overlap / area
199
200                if iou < 0.5:  # 겹침이 50% 미만이면 유지
201                    remaining.append(j)
202
203            indices = np.array(remaining)
204
205        # 결과 그리기
206        for i in keep:
207            pt = (boxes[i][0], boxes[i][1])
208            cv2.rectangle(scene_copy, pt, (pt[0]+w, pt[1]+h), (0, 255, 0), 2)
209            score = result[pt[1], pt[0]]
210            cv2.putText(scene_copy, f'{score:.2f}', (pt[0], pt[1]-5),
211                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
212            match_count += 1
213
214    print(f"임계값: {threshold}")
215    print(f"검출된 객체 수: {match_count}")
216
217    cv2.imwrite('find_all_matches.jpg', scene_copy)
218
219
220def haar_cascade_demo():
221    """Haar Cascade 데모"""
222    print("\n" + "=" * 50)
223    print("Haar Cascade 검출기")
224    print("=" * 50)
225
226    # 테스트 이미지 (얼굴 시뮬레이션)
227    img = np.zeros((300, 400, 3), dtype=np.uint8)
228    img[:] = [200, 200, 200]
229
230    # 얼굴 형태 시뮬레이션
231    cv2.ellipse(img, (200, 150), (50, 60), 0, 0, 360, (180, 150, 130), -1)
232    cv2.circle(img, (180, 130), 8, (50, 50, 50), -1)  # 눈
233    cv2.circle(img, (220, 130), 8, (50, 50, 50), -1)
234    cv2.ellipse(img, (200, 170), (15, 8), 0, 0, 180, (50, 50, 50), 2)  # 입
235
236    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
237
238    # Haar Cascade 로드
239    cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
240    face_cascade = cv2.CascadeClassifier(cascade_path)
241
242    if face_cascade.empty():
243        print("Haar Cascade 파일을 찾을 수 없습니다.")
244    else:
245        # 검출
246        faces = face_cascade.detectMultiScale(
247            gray,
248            scaleFactor=1.1,
249            minNeighbors=5,
250            minSize=(30, 30)
251        )
252
253        result = img.copy()
254        for (x, y, w, h) in faces:
255            cv2.rectangle(result, (x, y), (x+w, y+h), (0, 255, 0), 2)
256
257        print(f"검출된 얼굴: {len(faces)}")
258
259        cv2.imwrite('haar_input.jpg', img)
260        cv2.imwrite('haar_result.jpg', result)
261
262    print("\nHaar Cascade 파라미터:")
263    print("  scaleFactor: 이미지 피라미드 스케일")
264    print("  minNeighbors: 검출 확정 최소 이웃 수")
265    print("  minSize: 최소 객체 크기")
266
267    print("\n사용 가능한 Haar Cascade:")
268    print(f"  경로: {cv2.data.haarcascades}")
269    print("  - haarcascade_frontalface_default.xml")
270    print("  - haarcascade_eye.xml")
271    print("  - haarcascade_smile.xml")
272    print("  - haarcascade_fullbody.xml")
273
274
275def hog_concept_demo():
276    """HOG 개념 데모"""
277    print("\n" + "=" * 50)
278    print("HOG (Histogram of Oriented Gradients)")
279    print("=" * 50)
280
281    # 테스트 이미지
282    img = np.zeros((128, 64, 3), dtype=np.uint8)
283    img[:] = [200, 200, 200]
284
285    # 사람 형태 시뮬레이션
286    cv2.ellipse(img, (32, 25), (12, 15), 0, 0, 360, (100, 100, 100), -1)  # 머리
287    cv2.rectangle(img, (20, 40), (44, 90), (100, 100, 100), -1)  # 몸통
288    cv2.rectangle(img, (18, 90), (30, 125), (100, 100, 100), -1)  # 왼쪽 다리
289    cv2.rectangle(img, (34, 90), (46, 125), (100, 100, 100), -1)  # 오른쪽 다리
290
291    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
292
293    # HOG 디스크립터 계산
294    # winSize: 검출 윈도우 크기
295    # blockSize: 블록 크기
296    # blockStride: 블록 이동 간격
297    # cellSize: 셀 크기
298    # nbins: 히스토그램 빈 수
299
300    hog = cv2.HOGDescriptor(
301        _winSize=(64, 128),
302        _blockSize=(16, 16),
303        _blockStride=(8, 8),
304        _cellSize=(8, 8),
305        _nbins=9
306    )
307
308    # HOG 특징 계산
309    features = hog.compute(gray)
310
311    print(f"입력 이미지: {gray.shape}")
312    print(f"HOG 특징 벡터 크기: {features.shape}")
313
314    print("\nHOG 특성:")
315    print("  - 그래디언트 방향 히스토그램")
316    print("  - 조명 변화에 강건")
317    print("  - 보행자 검출에 효과적")
318    print("  - SVM과 함께 사용")
319
320    # 기본 HOG 보행자 검출기
321    hog_detector = cv2.HOGDescriptor()
322    hog_detector.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
323
324    print("\n사전 학습된 HOG+SVM 검출기:")
325    print("  - cv2.HOGDescriptor_getDefaultPeopleDetector()")
326    print("  - 보행자 검출용 학습된 SVM 가중치")
327
328    cv2.imwrite('hog_input.jpg', img)
329
330
331def detection_comparison():
332    """검출 방법 비교"""
333    print("\n" + "=" * 50)
334    print("객체 검출 방법 비교")
335    print("=" * 50)
336
337    print("""
338    | 방법 | 장점 | 단점 | 용도 |
339    |------|------|------|------|
340    | Template Matching | 간단, 빠름 | 회전/스케일 불변X | 고정 패턴 |
341    | Haar Cascade | 빠름, 얼굴 특화 | 정확도 제한 | 얼굴 검출 |
342    | HOG+SVM | 정확, 강건 | 느림 | 보행자 검출 |
343    | 특징점 매칭 | 회전/스케일 불변 | 계산량 큼 | 객체 인식 |
344    | 딥러닝 (YOLO 등) | 매우 정확 | GPU 필요 | 범용 검출 |
345    """)
346
347
348def main():
349    """메인 함수"""
350    # 템플릿 매칭
351    template_matching_demo()
352
353    # 다중 스케일
354    multi_scale_template_demo()
355
356    # 모든 매칭 찾기
357    find_all_matches_demo()
358
359    # Haar Cascade
360    haar_cascade_demo()
361
362    # HOG 개념
363    hog_concept_demo()
364
365    # 방법 비교
366    detection_comparison()
367
368    print("\n객체 검출 기초 데모 완료!")
369
370
371if __name__ == '__main__':
372    main()