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()