OpenCV 비전 실무19장 · Vision 데이터 파이프라인9 / 21← 이전목차다음 →MetaContents

19장. Vision 데이터 파이프라인

결과 시각화와 보고

택배 회사는 하루가 끝나면 "접수 120, 파손 반송 3, 출고 117" 같은 일일 보고서를 냅니다. 사진 몇 장을 붙여 "이런 상자가 파손으로 빠졌다"고 보여 주기도 합니다. 숫자만 있으면 믿기 어렵고, 사진만 있으면 전체를 알 수 없어서 둘을 함께 냅니다.

상자 그림 격자, 클래스별 막대, 단계별 통과 수의 세 결과를 나란히 놓은 비교 그림

그림의 세 카드가 보고 단계의 결과입니다. 그림 위에 상자와 이름을 그린 격자로 "모델이 제대로 찾았는지"를 눈으로 보고, 클래스별 막대로 "무엇이 몇 개 들어왔는지"를 보고, 단계별 통과 수 표로 "어디서 몇 장이 빠졌는지"를 봅니다. 여기서 사진을 붙이는 일이 상자 그림 격자, 출고 개수가 클래스별 막대, "접수 120, 반송 3" 이 깔때기 표입니다.

단계마다 남은 그림 수를 위에서 아래로 줄어드는 막대로 그린 깔때기 그림

깔때기 그림은 위에서 아래로 읽습니다. 맨 위 다홍 막대가 들어온 그림 24장이고, 읽기에서 빈 파일과 깨진 파일 2장, 밝기에서 1장, 흐림에서 1장, 중복에서 2장이 빠져 18장이 남습니다. 다듬기는 그림을 빼지 않고 고치기만 하므로 18장 그대로입니다. 어느 단계에서 갑자기 많이 줄면 그 단계의 기준이나 입고 데이터를 의심해 봅니다.

정답표로 그린 상자 그림, 클래스별 개수 막대, 단계별 남은 그림 수 막대를 한 장에 놓은 보고 그림의 보기

보고 그림 한 장의 짜임입니다. 이 그림은 모델의 추론이 아니라 정답표(answer.csv)로 그린 보기입니다. ① 상자 색은 bolt 초록, nut 다홍, washer 보라로 정했고, ② 정답표 전체(중복과 어두운 그림 포함 22장)에 washer 30개, nut 20개, bolt 14개가 있습니다. 실습 4와 프로젝트에서는 같은 짜임을 추론 결과로 채웁니다.

보고에 넣는 것

결과만드는 코드읽는 법
상자 그림 격자cv2.rectangle, cv2.putText, cv2.hconcat/vconcat상자가 부품에 꼭 맞는지, 빠진 부품이 없는지
클래스별 개수table["class"].value_counts()들어온 부품의 구성
단계별 통과 수단계마다 남은 수를 목록으로어디서 많이 빠졌는지
그림별 기록 csvto_csv(..., encoding="utf-8-sig")엑셀로 열어 그림마다 확인

짧은 예시

Python
import cv2
import pandas as pd

ans = pd.read_csv("ch19_incoming/answer.csv")
print(ans["class"].value_counts().to_dict())              # 결과: {'washer': 30, 'nut': 20, 'bolt': 14}

# 상자 색(BGR): bolt 초록, nut 다홍, washer 보라
COLORS = {"bolt": (76, 139, 95), "nut": (38, 93, 254), "washer": (190, 125, 142)}
img = cv2.imread("ch19_incoming/belt_018.png")
for r in ans[ans["filename"] == "belt_018.png"].itertuples():
    color = COLORS[r[2]]                                   # r[2]: 둘째 열(class). 열 이름 class 는 파이썬 낱말이라 번호로 꺼낸다
    cv2.rectangle(img, (r.x1, r.y1), (r.x2, r.y2), color, 2)
    cv2.putText(img, r[2], (r.x1, r.y1 - 4), cv2.FONT_HERSHEY_SIMPLEX, 0.45, color, 1, cv2.LINE_AA)
print("상자를 그린 그림:", img.shape)                      # 결과: 상자를 그린 그림: (240, 320, 3)

# 단계별 남은 수를 표로: 들어옴, 빠짐, 남음
counts = [("들어온 그림", 24), ("읽기", 22), ("밝기", 21), ("흐림", 20), ("중복", 18), ("다듬기", 18)]
funnel = pd.DataFrame(counts, columns=["단계", "남음"])
funnel["빠짐"] = (funnel["남음"].shift(1) - funnel["남음"]).fillna(0).astype(int)
print(funnel.to_string(index=False))

itertuples() 는 표의 행을 하나씩 꺼내 r.x1 처럼 열 이름으로 읽게 해 줍니다. 그런데 class 는 파이썬이 쓰는 낱말이라 r.class 로 쓸 수 없어서 r[2](0 은 행 번호, 1 은 filename, 2 는 class)로 꺼냅니다. shift(1) 은 한 칸 아래로 민 열이라, "앞 단계의 남음 − 이 단계의 남음"이 이 단계에서 빠진 수입니다.

주의할 점

  • 상자 그림은 결과가 좋은 그림만 고르지 않습니다. 씨앗을 고정해 무작위로 고르거나, 틀린 그림을 따로 모아 함께 봅니다.
  • 그림 격자에 상자를 그릴 때는 원래 그림을 복사(img.copy())해서 그립니다. 그린 그림을 다시 추론에 넣으면 상자 선까지 물체로 볼 수 있습니다.
← 이전 절19장 · 추론 단계다음 절 →19장 · 설정을 한곳에