09_contours.py

Download
python 292 lines 8.4 KB
  1"""
  209. 윤곽선 검출
  3- findContours, drawContours
  4- 윤곽선 계층 구조
  5- 윤곽선 근사 (approxPolyDP)
  6"""
  7
  8import cv2
  9import numpy as np
 10
 11
 12def create_shapes_image():
 13    """도형이 있는 이진 이미지"""
 14    img = np.zeros((400, 500), dtype=np.uint8)
 15
 16    # 사각형
 17    cv2.rectangle(img, (50, 50), (150, 150), 255, -1)
 18
 19    # 원
 20    cv2.circle(img, (300, 100), 50, 255, -1)
 21
 22    # 삼각형
 23    pts = np.array([[400, 50], [350, 150], [450, 150]], np.int32)
 24    cv2.fillPoly(img, [pts], 255)
 25
 26    # 중첩된 사각형 (계층 구조)
 27    cv2.rectangle(img, (50, 200), (200, 350), 255, -1)
 28    cv2.rectangle(img, (80, 230), (170, 320), 0, -1)  # 내부 구멍
 29    cv2.rectangle(img, (100, 250), (150, 290), 255, -1)  # 구멍 안의 객체
 30
 31    # 별 모양
 32    pts_star = np.array([
 33        [350, 200], [365, 250], [420, 250], [375, 280],
 34        [390, 330], [350, 300], [310, 330], [325, 280],
 35        [280, 250], [335, 250]
 36    ], np.int32)
 37    cv2.fillPoly(img, [pts_star], 255)
 38
 39    return img
 40
 41
 42def find_contours_demo():
 43    """윤곽선 찾기 데모"""
 44    print("=" * 50)
 45    print("윤곽선 찾기 (findContours)")
 46    print("=" * 50)
 47
 48    img = create_shapes_image()
 49
 50    # 윤곽선 찾기
 51    # RETR_EXTERNAL: 외부 윤곽선만
 52    # RETR_LIST: 모든 윤곽선 (계층 무시)
 53    # RETR_TREE: 모든 윤곽선 (계층 포함)
 54    # RETR_CCOMP: 2단계 계층
 55
 56    contours, hierarchy = cv2.findContours(
 57        img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE
 58    )
 59
 60    print(f"검출된 윤곽선 수: {len(contours)}")
 61    print(f"계층 구조 shape: {hierarchy.shape if hierarchy is not None else None}")
 62
 63    # 검색 모드 설명
 64    print("\n검색 모드 (Retrieval Mode):")
 65    print("  RETR_EXTERNAL: 가장 바깥 윤곽선만")
 66    print("  RETR_LIST: 모든 윤곽선 (평평하게)")
 67    print("  RETR_TREE: 전체 계층 구조")
 68    print("  RETR_CCOMP: 2단계 계층")
 69
 70    return img, contours, hierarchy
 71
 72
 73def draw_contours_demo():
 74    """윤곽선 그리기 데모"""
 75    print("\n" + "=" * 50)
 76    print("윤곽선 그리기 (drawContours)")
 77    print("=" * 50)
 78
 79    img = create_shapes_image()
 80    contours, _ = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
 81
 82    # 컬러 이미지로 변환 (그리기용)
 83    color_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
 84
 85    # 모든 윤곽선 그리기
 86    all_contours = color_img.copy()
 87    cv2.drawContours(all_contours, contours, -1, (0, 255, 0), 2)
 88
 89    # 특정 윤곽선만 그리기
 90    specific = color_img.copy()
 91    cv2.drawContours(specific, contours, 0, (255, 0, 0), 2)  # 첫 번째 윤곽선
 92    cv2.drawContours(specific, contours, 1, (0, 255, 0), 2)  # 두 번째 윤곽선
 93
 94    # 채우기
 95    filled = color_img.copy()
 96    cv2.drawContours(filled, contours, 0, (0, 0, 255), -1)  # thickness=-1 → 채우기
 97
 98    print("drawContours 파라미터:")
 99    print("  contourIdx=-1: 모든 윤곽선")
100    print("  contourIdx=n: n번째 윤곽선만")
101    print("  thickness=-1: 내부 채우기")
102
103    cv2.imwrite('contours_original.jpg', img)
104    cv2.imwrite('contours_all.jpg', all_contours)
105    cv2.imwrite('contours_specific.jpg', specific)
106    cv2.imwrite('contours_filled.jpg', filled)
107
108
109def contour_hierarchy_demo():
110    """윤곽선 계층 구조 데모"""
111    print("\n" + "=" * 50)
112    print("윤곽선 계층 구조")
113    print("=" * 50)
114
115    img = create_shapes_image()
116    contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
117
118    # hierarchy[i] = [Next, Previous, First_Child, Parent]
119    print("계층 구조 형식: [Next, Previous, First_Child, Parent]")
120    print("(-1은 해당 관계 없음)")
121
122    for i, h in enumerate(hierarchy[0]):
123        print(f"윤곽선 {i}: Next={h[0]}, Prev={h[1]}, Child={h[2]}, Parent={h[3]}")
124
125    # 외부 윤곽선만 (부모가 없는 것)
126    color_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
127    external_only = color_img.copy()
128
129    for i, h in enumerate(hierarchy[0]):
130        if h[3] == -1:  # 부모가 없음 = 외부 윤곽선
131            cv2.drawContours(external_only, contours, i, (0, 255, 0), 2)
132
133    cv2.imwrite('contours_external.jpg', external_only)
134
135
136def contour_approximation_demo():
137    """윤곽선 근사 데모"""
138    print("\n" + "=" * 50)
139    print("윤곽선 근사 (approxPolyDP)")
140    print("=" * 50)
141
142    # 복잡한 곡선 이미지
143    img = np.zeros((300, 400), dtype=np.uint8)
144    pts = []
145    for angle in range(0, 360, 5):
146        r = 80 + 20 * np.sin(5 * np.radians(angle))
147        x = int(200 + r * np.cos(np.radians(angle)))
148        y = int(150 + r * np.sin(np.radians(angle)))
149        pts.append([x, y])
150    pts = np.array(pts, np.int32)
151    cv2.fillPoly(img, [pts], 255)
152
153    contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
154
155    # 원본 윤곽선
156    color_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
157
158    # 다양한 epsilon 값으로 근사
159    epsilons = [0.01, 0.02, 0.05, 0.1]
160
161    for eps in epsilons:
162        approx_img = color_img.copy()
163        for cnt in contours:
164            # epsilon = 둘레의 비율
165            epsilon = eps * cv2.arcLength(cnt, True)
166            approx = cv2.approxPolyDP(cnt, epsilon, True)
167
168            print(f"epsilon={eps}: {len(cnt)} 점 → {len(approx)} 점")
169
170            cv2.drawContours(approx_img, [approx], -1, (0, 255, 0), 2)
171            # 꼭짓점 표시
172            for pt in approx:
173                cv2.circle(approx_img, tuple(pt[0]), 3, (0, 0, 255), -1)
174
175        cv2.imwrite(f'approx_eps_{eps}.jpg', approx_img)
176
177
178def contour_properties_demo():
179    """윤곽선 속성 데모"""
180    print("\n" + "=" * 50)
181    print("윤곽선 속성")
182    print("=" * 50)
183
184    img = create_shapes_image()
185    contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
186
187    for i, cnt in enumerate(contours):
188        # 면적
189        area = cv2.contourArea(cnt)
190
191        # 둘레 (호 길이)
192        perimeter = cv2.arcLength(cnt, True)
193
194        # 경계 사각형
195        x, y, w, h = cv2.boundingRect(cnt)
196
197        # 중심점 (모멘트)
198        M = cv2.moments(cnt)
199        if M['m00'] != 0:
200            cx = int(M['m10'] / M['m00'])
201            cy = int(M['m01'] / M['m00'])
202        else:
203            cx, cy = 0, 0
204
205        print(f"\n윤곽선 {i}:")
206        print(f"  면적: {area:.1f}")
207        print(f"  둘레: {perimeter:.1f}")
208        print(f"  경계 사각형: ({x}, {y}, {w}, {h})")
209        print(f"  중심: ({cx}, {cy})")
210
211
212def detect_shapes():
213    """도형 인식"""
214    print("\n" + "=" * 50)
215    print("도형 인식")
216    print("=" * 50)
217
218    img = create_shapes_image()
219    contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
220
221    color_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
222
223    for cnt in contours:
224        # 윤곽선 근사
225        epsilon = 0.04 * cv2.arcLength(cnt, True)
226        approx = cv2.approxPolyDP(cnt, epsilon, True)
227
228        # 중심점 계산
229        M = cv2.moments(cnt)
230        if M['m00'] != 0:
231            cx = int(M['m10'] / M['m00'])
232            cy = int(M['m01'] / M['m00'])
233        else:
234            continue
235
236        # 꼭짓점 수로 도형 판별
237        vertices = len(approx)
238
239        if vertices == 3:
240            shape = "Triangle"
241        elif vertices == 4:
242            # 정사각형 vs 직사각형
243            x, y, w, h = cv2.boundingRect(approx)
244            ar = w / float(h)
245            shape = "Square" if 0.95 <= ar <= 1.05 else "Rectangle"
246        elif vertices == 5:
247            shape = "Pentagon"
248        elif vertices > 5:
249            # 원 판별 (면적/둘레 비율)
250            area = cv2.contourArea(cnt)
251            perimeter = cv2.arcLength(cnt, True)
252            circularity = 4 * np.pi * area / (perimeter ** 2)
253            shape = "Circle" if circularity > 0.8 else f"Polygon({vertices})"
254        else:
255            shape = f"Polygon({vertices})"
256
257        # 결과 표시
258        cv2.drawContours(color_img, [approx], -1, (0, 255, 0), 2)
259        cv2.putText(color_img, shape, (cx - 30, cy),
260                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)
261
262        print(f"  {shape}: 꼭짓점 {vertices}개")
263
264    cv2.imwrite('shapes_detected.jpg', color_img)
265
266
267def main():
268    """메인 함수"""
269    # 윤곽선 찾기
270    find_contours_demo()
271
272    # 윤곽선 그리기
273    draw_contours_demo()
274
275    # 계층 구조
276    contour_hierarchy_demo()
277
278    # 윤곽선 근사
279    contour_approximation_demo()
280
281    # 윤곽선 속성
282    contour_properties_demo()
283
284    # 도형 인식
285    detect_shapes()
286
287    print("\n윤곽선 검출 데모 완료!")
288
289
290if __name__ == '__main__':
291    main()