STEP 2 / 7
학생용 교재 · 노트북 웹캠 사물 인식률 측정 · 설치 안정화 개선판

노트북 웹캠으로 사물 인식률 측정하기

robogram.net 데이터 저장소의 이미지셋을 이용해 사물 인식 모델 파일을 만들고, 노트북 웹캠으로 실시간 인식률을 측정하는 실습입니다.

수업 시작 전 · Python 3.12.10 설치

Windows 64비트 기준입니다. 수업용 PC의 Python 버전을 동일하게 맞추면 실습 오류를 줄일 수 있습니다.

기존 버전 확인: 이미 Python이 설치되어 있다면 CMD에서 py -0p를 입력해 설치된 버전을 확인할 수 있습니다. 다른 버전은 삭제하지 않아도 되며, 수업에서는 py -3.12를 사용합니다.
1
설치 파일 다운로드python-3.12.10-amd64.exe 파일을 다운로드한 뒤 실행합니다.
2
PATH 항목 체크설치 첫 화면 아래의 Add python.exe to PATH를 반드시 체크합니다.
3
Install NowInstall Now를 눌러 설치하고, 완료 화면이 나타날 때까지 기다립니다.
4
CMD에서 확인새 CMD를 열고 py -3.12 --version을 입력해 Python 3.12.10이 표시되는지 확인합니다.
중요: 기존에 다른 Python 버전이 설치되어 있어도 먼저 삭제할 필요는 없습니다. Python 3.12.10 설치 후 기존 CMD 창을 닫고 새 CMD 창을 연 다음 py -3.12 방식으로 수업을 진행합니다. 여러 버전이 설치된 PC에서도 3.12 버전을 정확히 지정할 수 있습니다.

오늘 만들 프로그램

1. 이미지셋 준비robogram.net 데이터 저장소에서 사물 이미지셋을 다운로드합니다.
2. 모델 학습이미지셋으로 사물 인식 모델 파일을 만듭니다.
3. 인식률 측정노트북 웹캠으로 목표 사물의 인식률을 측정합니다.
중요: 이 수업에서는 아두이노 제어를 하지 않습니다. 노트북 웹캠과 Python으로만 실습합니다.

1단계. 실습 폴더 만들기

폴더 위치: 학교 PC의 바탕화면(Desktop)이 OneDrive 등으로 변경된 경우를 피하기 위해 실습 폴더를 %USERPROFILE% 아래에 만듭니다. 예: C:\Users\학생계정\object_recognition_laptop_app
Windows CMD 폴더 생성 명령어
cd %USERPROFILE%
mkdir object_recognition_laptop_app
cd object_recognition_laptop_app
mkdir obj_data
mkdir model
dir

2단계. Python과 필요한 라이브러리 설치

설치는 현재 폴더 안이 아니라 노트북의 Python 환경에 됩니다. 학교 PC에서는 pip 대신 py -3.12 -m pip 방식으로 설치하는 것이 더 안정적입니다.

권장 환경: Python 3.12.10 64-bit를 권장합니다. 설치 후 CMD를 새로 열고 아래 명령어를 순서대로 실행하세요.
Python / pip 확인
py -3.12 --version
py -3.12 -m pip --version
pip 업데이트
py -3.12 -m pip install --upgrade pip
필요한 라이브러리 설치
py -3.12 -m pip install numpy
py -3.12 -m pip install opencv-contrib-python==4.11.0.86
py -3.12 -m pip install scikit-learn joblib
참고: 이 교재의 사물 인식 코드는 OpenCV 기본 기능만 사용해 opencv-python만으로도 충분하지만, opencv-contrib-python으로 통일해서 설치합니다. 두 패키지를 같이 설치하면 서로 충돌해 cv2가 깨지는 경우가 있어, 1단계(얼굴인식)·3단계(페르소나)와 버전까지 하나로(4.11.0.86) 맞추는 것입니다. 최신 5.0.0 계열은 Haar Cascade XML 파일이 기본 포함되지 않을 수 있습니다.
설치 확인 명령어
py -3.12 -c "import cv2, numpy, sklearn, joblib; print('설치 확인 완료'); print('OpenCV:', cv2.__version__); print('NumPy:', numpy.__version__)"
설치가 모두 실패한다면: Python 버전 문제보다 학교 네트워크, 방화벽, 프록시 또는 PyPI 접속 제한 때문일 수 있습니다. Retrying, ProxyError, CERTIFICATE_VERIFY_FAILED, Could not fetch URL 같은 메시지가 나오면 네트워크 환경을 확인하세요.

3단계. robogram.net 데이터 저장소에서 이미지셋 다운로드

  1. robogram.net에 접속합니다.
  2. 수업에서 안내받은 데이터 저장소 메뉴 또는 링크로 이동합니다.
  3. 사물 인식 이미지셋을 다운로드합니다. 예: obj_data.zip
  4. 압축을 풀어 obj_data 폴더 안에 사물별 폴더가 보이게 정리합니다.
폴더 이름은 모델의 정답 이름입니다. 예: obj_data/keyboard, obj_data/mouse
권장 이미지 수: 사물별로 최소 20~30장 이상을 준비하고, 가능하면 30~50장 정도의 다양한 사진을 사용하는 것이 좋습니다. 이미지 수가 너무 적으면 학습 데이터와 평가 데이터가 겹쳐 실제보다 높은 정확도가 나올 수 있습니다.
4단계 캡스톤을 이어서 할 계획이라면 꼭 읽어주세요: 캡스톤에서는 물건을 손으로 들고 카메라에 보여줍니다. 그런데 책상 위에 물건만 놓고 찍은 사진으로 학습하면, 실제로 손에 들었을 때(손·전선 등이 함께 찍힘) 잘 못 맞히는 경우가 많습니다. 사진의 절반 정도는 책상 위, 나머지 절반은 손으로 들고 있는 모습으로 섞어서 찍으면 캡스톤에서 훨씬 더 정확하게 인식됩니다.
데이터 폴더 확인 명령어
cd %USERPROFILE%\object_recognition_laptop_app
dir obj_data

4단계. 웹캠 테스트 파일 만들기

파일 만드는 방법: CMD에서 notepad 0_webcam_test.py를 실행해 새 파일을 만들고, 아래 전체 코드를 붙여넣은 뒤 Ctrl+S로 저장합니다. 메모장을 닫고 CMD에서 py -3.12 0_webcam_test.py으로 실행합니다.
파일 만들기 명령어
cd %USERPROFILE%\object_recognition_laptop_app
notepad 0_webcam_test.py
0_webcam_test.py 전체 코드
import cv2

# 0단계: 노트북 웹캠 테스트
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
if not cap.isOpened():
    cap = cv2.VideoCapture(0)

if not cap.isOpened():
    print("웹캠을 열 수 없습니다.")
    print("Windows 설정 > 개인정보 및 보안 > 카메라 권한을 확인하세요.")
    print("Zoom, Teams, 카메라 앱처럼 웹캠을 사용 중인 프로그램도 닫아주세요.")
    exit()

print("웹캠 테스트를 시작합니다.")
print("웹캠 화면이 뜨면 성공입니다. 종료하려면 q 키를 누르세요.")

while True:
    ret, frame = cap.read()
    if not ret:
        print("웹캠 화면을 가져오지 못했습니다.")
        break
    cv2.imshow("Webcam Test", frame)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

cap.release()
cv2.destroyAllWindows()
실행 명령어
py -3.12 0_webcam_test.py

5단계. 모델 학습 파일 만들기

파일 만드는 방법: CMD에서 notepad 1_train_object_model.py를 실행해 새 파일을 만들고, 아래 전체 코드를 붙여넣은 뒤 Ctrl+S로 저장합니다. 메모장을 닫고 CMD에서 py -3.12 1_train_object_model.py으로 실행합니다.
파일 만들기 명령어
cd %USERPROFILE%\object_recognition_laptop_app
notepad 1_train_object_model.py
1_train_object_model.py 전체 코드
import os
import cv2
import joblib
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

# 1단계: robogram.net 이미지셋으로 사물 인식 모델 학습
DATA_DIR = "obj_data"
MODEL_DIR = "model"
MODEL_PATH = os.path.join(MODEL_DIR, "object_model.joblib")
LABEL_PATH = os.path.join(MODEL_DIR, "labels.txt")
REPORT_PATH = os.path.join(MODEL_DIR, "training_report.txt")
os.makedirs(MODEL_DIR, exist_ok=True)
IMAGE_EXTS = (".jpg", ".jpeg", ".png", ".bmp", ".webp")

def extract_features(image):
    # 색상 특징 + 모양 특징을 숫자로 변환합니다.
    image = cv2.resize(image, (96, 96))
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    hist = cv2.calcHist([hsv], [0, 1, 2], None, [8, 8, 8], [0, 180, 0, 256, 0, 256])
    hist = cv2.normalize(hist, hist).flatten()

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = cv2.resize(gray, (64, 64))
    hog = cv2.HOGDescriptor(_winSize=(64,64), _blockSize=(16,16), _blockStride=(8,8), _cellSize=(8,8), _nbins=9)
    hog_feature = hog.compute(gray).flatten()
    return np.concatenate([hist, hog_feature])

def load_dataset():
    X, y = [], []
    if not os.path.exists(DATA_DIR):
        print("obj_data 폴더가 없습니다. 이미지셋을 obj_data 폴더 안에 정리하세요.")
        exit()

    class_names = sorted([name for name in os.listdir(DATA_DIR) if os.path.isdir(os.path.join(DATA_DIR, name))])
    if len(class_names) == 0:
        print("obj_data 안에 사물별 폴더가 없습니다. 예: obj_data/bag, obj_data/keyboard")
        exit()

    print("발견한 사물 클래스:", class_names)
    for class_name in class_names:
        class_dir = os.path.join(DATA_DIR, class_name)
        count = 0
        for file_name in os.listdir(class_dir):
            if not file_name.lower().endswith(IMAGE_EXTS):
                continue
            image_path = os.path.join(class_dir, file_name)
            image = cv2.imread(image_path)
            if image is None:
                continue
            X.append(extract_features(image))
            y.append(class_name)
            count += 1
        print(f"{class_name}: {count}장")
    if len(X) == 0:
        print("학습할 이미지가 없습니다.")
        exit()
    return np.array(X), np.array(y), class_names

X, y, class_names = load_dataset()
print("\n총 이미지 수:", len(X))
print("클래스 수:", len(class_names))

try:
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)
except ValueError:
    print("클래스별 이미지 수가 적어 전체 데이터로 학습/평가합니다.")
    print("주의: 이 경우 학습에 사용한 이미지를 다시 평가하므로 실제 성능보다 높게 나올 수 있습니다.")
    print("사물별 이미지를 20~30장 이상으로 늘린 뒤 다시 실행하는 것을 권장합니다.")
    X_train, X_test, y_train, y_test = X, X, y, y

model = KNeighborsClassifier(n_neighbors=3, weights="distance")
model.fit(X_train, y_train)
pred = model.predict(X_test)
acc = accuracy_score(y_test, pred)
report = classification_report(y_test, pred, zero_division=0)
cm = confusion_matrix(y_test, pred, labels=class_names)

print("\n==============================")
print("모델 학습 완료")
print(f"테스트 인식률: {acc * 100:.2f}%")
print("==============================")
print("\n[분류 리포트]\n", report)
print("[혼동 행렬]")
print(class_names)
print(cm)

joblib.dump({"model": model, "class_names": class_names, "feature_info": "HSV color histogram + HOG"}, MODEL_PATH)
with open(LABEL_PATH, "w", encoding="utf-8") as f:
    for name in class_names:
        f.write(name + "\n")
with open(REPORT_PATH, "w", encoding="utf-8") as f:
    f.write(f"테스트 인식률: {acc * 100:.2f}%\n\n")
    f.write("[클래스 목록]\n" + str(class_names) + "\n\n")
    f.write("[분류 리포트]\n" + report + "\n")
    f.write("[혼동 행렬]\n" + str(cm) + "\n")

print("\n저장된 모델 파일:", MODEL_PATH)
print("저장된 라벨 파일:", LABEL_PATH)
print("저장된 리포트:", REPORT_PATH)
모델 학습 실행 명령어
cd %USERPROFILE%\object_recognition_laptop_app
py -3.12 1_train_object_model.py
성공하면 model/object_model.joblib, model/labels.txt, model/training_report.txt가 만들어집니다.

6단계. 실시간 사물 인식 파일 만들기

파일 만드는 방법: CMD에서 notepad 2_live_object_recognition.py를 실행해 새 파일을 만들고, 아래 전체 코드를 붙여넣은 뒤 Ctrl+S로 저장합니다. 메모장을 닫고 CMD에서 py -3.12 2_live_object_recognition.py으로 실행합니다.
파일 만들기 명령어
cd %USERPROFILE%\object_recognition_laptop_app
notepad 2_live_object_recognition.py
2_live_object_recognition.py 전체 코드
import os
import cv2
import joblib
import numpy as np

# 2단계: 학습된 모델로 노트북 웹캠 실시간 사물 인식
MODEL_PATH = "model/object_model.joblib"
if not os.path.exists(MODEL_PATH):
    print("모델 파일이 없습니다. 먼저 py -3.12 1_train_object_model.py 를 실행하세요.")
    exit()

bundle = joblib.load(MODEL_PATH)
model = bundle["model"]
class_names = bundle["class_names"]

def extract_features(image):
    image = cv2.resize(image, (96, 96))
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    hist = cv2.calcHist([hsv], [0, 1, 2], None, [8, 8, 8], [0, 180, 0, 256, 0, 256])
    hist = cv2.normalize(hist, hist).flatten()
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = cv2.resize(gray, (64, 64))
    hog = cv2.HOGDescriptor(_winSize=(64,64), _blockSize=(16,16), _blockStride=(8,8), _cellSize=(8,8), _nbins=9)
    return np.concatenate([hist, hog.compute(gray).flatten()])

def predict_object(image):
    feature = extract_features(image).reshape(1, -1)
    pred = model.predict(feature)[0]
    probs = model.predict_proba(feature)[0]
    confidence = float(np.max(probs)) * 100
    return pred, confidence

cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
if not cap.isOpened():
    cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print("웹캠을 열 수 없습니다.")
    exit()

print("실시간 사물 인식을 시작합니다. 물체를 화면 가운데 사각형 안에 보여주세요. q: 종료")
while True:
    ret, frame = cap.read()
    if not ret:
        print("웹캠 화면을 가져오지 못했습니다.")
        break

    h, w = frame.shape[:2]
    box_size = min(w, h) // 2
    x1, y1 = w//2 - box_size//2, h//2 - box_size//2
    x2, y2 = x1 + box_size, y1 + box_size
    roi = frame[y1:y2, x1:x2]
    pred, confidence = predict_object(roi)

    cv2.rectangle(frame, (x1,y1), (x2,y2), (0,255,0), 2)
    cv2.putText(frame, f"{pred} / {confidence:.1f}%", (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,0), 2)
    cv2.putText(frame, "Put object inside the box. Press q to quit.", (20,30), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (255,255,255), 2)
    cv2.imshow("Object Recognition - Laptop Webcam", frame)

    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

cap.release()
cv2.destroyAllWindows()
Confidence란? 화면에 표시되는 %는 “정답일 실제 확률”이 아니라 KNN 모델이 주변 학습 데이터와 비교해 계산한 상대적인 분류 신뢰도입니다. 값이 높을수록 현재 입력이 해당 클래스의 학습 특징과 더 비슷하다고 이해하면 됩니다.
실행 명령어
cd %USERPROFILE%\object_recognition_laptop_app
py -3.12 2_live_object_recognition.py

7단계. 목표 사물 인식률 측정 테스트

5단계 학습 결과와 비교해보세요: 1_train_object_model.py를 실행했을 때 model/training_report.txt에 "테스트 인식률"이 저장됐습니다. 지금 이 7단계는 그 숫자가 실제 webcam 환경에서도 그대로 나오는지 직접 확인하는 과정입니다. 두 숫자가 비슷하면 모델이 일반화를 잘한 것이고, 지금 숫자가 훨씬 낮다면 학습 때 사진과 지금 조명·각도·배경이 달라서 생기는 "일반화 문제"를 직접 목격하고 있는 것입니다.
왜 0.5초마다 측정하나요? 매 프레임(1초에 수십 번)을 다 세면 사실상 똑같은 순간이 중복으로 과도하게 반영됩니다. 0.5초 간격으로 "정답/오답" 한 번씩만 기록해야 측정 횟수가 사람이 보기에도 의미 있는 숫자가 됩니다.
이번 버전에서 추가된 것: ① 전체 누적 인식률뿐 아니라 최근 20회 인식률도 함께 표시해 "지금 이 순간"의 성능을 더 빠르게 보여줍니다 ② 인식률·confidence를 색깔 막대그래프로 표시합니다 ③ r 키를 누르면 바로 초기화되지 않고, 지금까지의 구간 리포트(평균 confidence, 가장 많이 헷갈린 사물 등)를 CMD에 먼저 출력한 뒤 초기화됩니다.
파일 만드는 방법: CMD에서 notepad 3_measure_recognition_rate.py를 실행해 새 파일을 만들고, 아래 전체 코드를 붙여넣은 뒤 Ctrl+S로 저장합니다. 메모장을 닫고 CMD에서 py -3.12 3_measure_recognition_rate.py으로 실행합니다.
파일 만들기 명령어
cd %USERPROFILE%\object_recognition_laptop_app
notepad 3_measure_recognition_rate.py
3_measure_recognition_rate.py 전체 코드
import os
import cv2
import joblib
import numpy as np
import time
from collections import deque, Counter

# 7단계: 목표 사물 인식률 측정 테스트 (교육용 업그레이드판)
MODEL_PATH = "model/object_model.joblib"
if not os.path.exists(MODEL_PATH):
    print("모델 파일이 없습니다. 먼저 py -3.12 1_train_object_model.py 를 실행하세요.")
    exit()

bundle = joblib.load(MODEL_PATH)
model = bundle["model"]
class_names = bundle["class_names"]
print("학습된 사물 목록:")
for name in class_names:
    print("-", name)

target = input("\n인식률을 측정할 목표 사물 이름을 입력하세요: ").strip()
if target == "":
    print("목표 사물 이름이 비어 있습니다.")
    exit()

matched_target = None
for name in class_names:
    if name.strip().lower() == target.strip().lower():
        matched_target = name
        break

if matched_target is None:
    print("입력한 사물이 학습 목록에 없습니다.")
    print("다음 중 하나를 정확히 입력하세요:")
    for name in class_names:
        print("-", name)
    exit()

target = matched_target

def extract_features(image):
    image = cv2.resize(image, (96, 96))
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    hist = cv2.calcHist([hsv], [0, 1, 2], None, [8, 8, 8], [0, 180, 0, 256, 0, 256])
    hist = cv2.normalize(hist, hist).flatten()
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = cv2.resize(gray, (64, 64))
    hog = cv2.HOGDescriptor(_winSize=(64, 64), _blockSize=(16, 16), _blockStride=(8, 8), _cellSize=(8, 8), _nbins=9)
    return np.concatenate([hist, hog.compute(gray).flatten()])

def predict_object(image):
    feature = extract_features(image).reshape(1, -1)
    pred = model.predict(feature)[0]
    probs = model.predict_proba(feature)[0]
    confidence = float(np.max(probs)) * 100
    return pred, confidence

def rate_color(rate):
    if rate >= 70:
        return (0, 200, 0)
    if rate >= 40:
        return (0, 200, 255)
    return (0, 0, 255)

def draw_bar(frame, x, y, w, h, ratio, color):
    ratio = max(0.0, min(1.0, ratio))
    cv2.rectangle(frame, (x, y), (x + w, y + h), (70, 70, 70), -1)
    cv2.rectangle(frame, (x, y), (x + int(w * ratio), y + h), color, -1)
    cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 255, 255), 1)

cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
if not cap.isOpened():
    cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print("웹캠을 열 수 없습니다.")
    exit()

WINDOW_SIZE = 20
recent = deque(maxlen=WINDOW_SIZE)
total = 0
correct = 0
confidence_sum = 0.0
wrong_guess_counter = Counter()
high_confidence_wrong = 0

last_check = 0
interval = 0.5

print("\n인식률 측정을 시작합니다.")
print("r: 지금까지 구간 리포트 출력 후 초기화 (다른 사물로 바꿔 비교할 때 사용) / q: 종료")

def print_segment_report():
    rate = (correct / total * 100) if total > 0 else 0
    avg_conf = (confidence_sum / total) if total > 0 else 0
    print("\n----- 구간 리포트 -----")
    print(f"목표 사물: {target}")
    print(f"정답 횟수 / 전체 횟수: {correct}/{total}")
    print(f"인식률: {rate:.1f}%")
    print(f"평균 confidence: {avg_conf:.1f}%")
    if wrong_guess_counter:
        most_common, cnt = wrong_guess_counter.most_common(1)[0]
        print(f"가장 많이 헷갈린 사물: '{most_common}' ({cnt}회)")
    print(f"자신있게 틀린 횟수(확신 60% 이상인데 오답): {high_confidence_wrong}")
    print("-----------------------\n")

while True:
    ret, frame = cap.read()
    if not ret:
        break

    h, w = frame.shape[:2]
    box_size = min(w, h) // 2
    x1, y1 = w // 2 - box_size // 2, h // 2 - box_size // 2
    x2, y2 = x1 + box_size, y1 + box_size
    roi = frame[y1:y2, x1:x2]
    pred, confidence = predict_object(roi)
    is_correct = pred.strip().lower() == target.strip().lower()

    now = time.time()
    if now - last_check >= interval:
        total += 1
        confidence_sum += confidence
        recent.append(1 if is_correct else 0)

        if is_correct:
            correct += 1
        else:
            wrong_guess_counter[pred] += 1
            if confidence >= 60:
                high_confidence_wrong += 1

        last_check = now

    overall_rate = (correct / total * 100) if total > 0 else 0
    recent_rate = (sum(recent) / len(recent) * 100) if recent else 0

    box_color = (0, 255, 0) if is_correct else (0, 0, 255)
    cv2.rectangle(frame, (x1, y1), (x2, y2), box_color, 3)

    overlay = frame.copy()
    cv2.rectangle(overlay, (0, 0), (w, 175), (20, 20, 20), -1)
    frame = cv2.addWeighted(overlay, 0.55, frame, 0.45, 0)

    cv2.putText(frame, f"Target: {target}  |  Pred: {pred} ({confidence:.0f}%)", (20, 30),
                cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)

    cv2.putText(frame, f"Recent {len(recent)}/{WINDOW_SIZE}: {recent_rate:.0f}%", (20, 62),
                cv2.FONT_HERSHEY_SIMPLEX, 0.7, rate_color(recent_rate), 2)
    draw_bar(frame, 260, 48, 200, 18, recent_rate / 100, rate_color(recent_rate))

    cv2.putText(frame, f"Overall: {overall_rate:.0f}% ({correct}/{total})", (20, 94),
                cv2.FONT_HERSHEY_SIMPLEX, 0.7, rate_color(overall_rate), 2)
    draw_bar(frame, 260, 80, 200, 18, overall_rate / 100, rate_color(overall_rate))

    cv2.putText(frame, f"Confidence now: {confidence:.0f}%", (20, 126),
                cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
    draw_bar(frame, 260, 112, 200, 18, confidence / 100, (0, 255, 255))

    cv2.putText(frame, "r: segment report + reset   q: quit", (20, 158),
                cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 200, 200), 1)

    cv2.imshow("Recognition Rate Test", frame)

    key = cv2.waitKey(1) & 0xFF
    if key == ord("r"):
        print_segment_report()
        total, correct, confidence_sum = 0, 0, 0.0
        recent.clear()
        wrong_guess_counter.clear()
        high_confidence_wrong = 0
        print("측정값을 초기화했습니다. 다음 구간 측정을 시작하세요.")
    if key == ord("q"):
        break

cap.release()
cv2.destroyAllWindows()
print_segment_report()
print("측정을 종료했습니다.")
인식률 측정 실행 명령어
cd %USERPROFILE%\object_recognition_laptop_app
py -3.12 3_measure_recognition_rate.py
목표 사물 이름은 obj_data 안의 폴더 이름과 똑같이 입력하세요.

화면 읽는 법 — 색깔이 알려주는 것

화면 요소의미
중앙 사각형 테두리 색초록 = 지금 이 순간 목표 사물로 정확히 인식 / 빨강 = 다른 것으로 인식
Recent 20회 막대최근 20번 측정 중 몇 번 맞았는지. 지금 이 자세·조명에서의 실력을 가장 빠르게 보여줍니다.
Overall 막대측정 시작(또는 마지막 r)부터 지금까지 전체 누적 인식률
막대 색(초록/주황/빨강)70% 이상 초록, 40~70% 주황, 40% 미만 빨강 — 숫자를 안 읽어도 상태를 바로 알 수 있습니다.

confidence와 정답 여부, 함께 읽는 법

정답/오답과 confidence(모델의 확신 정도)는 서로 다른 축입니다. 네 가지 경우를 구분해서 이해하면 모델의 약점이 보입니다.

confidence 높음confidence 낮음
정답✅ 이상적인 상태 — 모델이 확실히 잘 압니다.🤔 맞혔지만 확신은 부족 — 운이 좋았거나 애매한 사례입니다.
오답⚠️ 가장 위험한 경우 — 자신 있게 틀렸습니다. 학습 데이터에 이 상황과 비슷한 "가짜 정답" 사진이 섞였을 가능성이 있습니다.😕 모델도 헷갈려합니다 — 두 사물이 색·모양이 비슷하거나 학습 사진이 부족할 수 있습니다.
프로그램이 CMD에 출력하는 "자신있게 틀린 횟수(확신 60% 이상인데 오답)"가 많다면 위 표의 ⚠️ 칸에 해당합니다 — 해당 사물의 학습 사진을 더 다양하게 보강하는 게 좋다는 신호입니다.

비교 실험 해보기

숫자 하나만 보고 끝내지 말고, 조건을 바꿔가며 비교해 보세요. r을 누르면 그 구간의 리포트가 CMD에 먼저 출력된 뒤 초기화되므로, 조건을 바꿀 때마다 눌러 기록을 남기세요.

실험방법인식률(%)평균 confidence(%)
① 목표 사물목표 사물을 중앙 사각형에 크게 보여주고 20~30초 측정 → r
② 다른 사물목표가 아닌 다른 학습된 사물로 교체 후 측정 → r
③ 조명 바꾸기①과 같은 사물, 조명만 어둡게/밝게 바꿔 측정 → r
③번에서 인식률이 크게 떨어진다면, 이 모델이 "조명 변화에 약하다"는 걸 직접 증명한 것입니다 — 5단계 학습 데이터에 다양한 조명의 사진이 부족했다는 뜻이기도 합니다.

잘 안 될 때

증상해결
웹캠이 안 열림Windows 카메라 권한을 켜고 Zoom/Teams/카메라 앱을 닫습니다.
No module named cv2py -3.12 -m pip install opencv-contrib-python==4.11.0.86을 실행한 뒤 CMD를 다시 엽니다.
AttributeError: module 'cv2' has no attribute ...다른 실습에서 opencv-python·opencv-python-headless·opencv-contrib-python을 섞어 설치했을 때 생기는 충돌입니다. 아래 명령으로 전부 지운 뒤 버전을 고정해 하나만 다시 설치하세요: py -3.12 -m pip uninstall opencv-python opencv-python-headless opencv-contrib-python -y 실행 후 py -3.12 -m pip install opencv-contrib-python==4.11.0.86
pip 명령을 찾을 수 없음pip 대신 py -3.12 -m pip을 사용합니다.
Retrying / ProxyError / 인증서 오류학교 네트워크의 방화벽·프록시·PyPI 접속 제한 가능성을 확인합니다.
obj_data 안에 이미지가 없다고 나옴obj_data/bag/사진.jpg처럼 사물별 폴더 안에 이미지가 있어야 합니다.
인식률이 낮음물체를 중앙 사각형 안에 크게 보여주고, 데이터셋의 사진을 다양하게 준비합니다. "Recent 20회" 막대가 낮다면 지금 조건(조명·각도) 문제, "Overall"만 낮고 Recent는 높다면 초반에 자세를 못 잡았던 영향이니 r로 리셋 후 다시 측정해 보세요.
화면 위쪽 정보 패널이 안 보이거나 너무 큼웹캠 해상도가 매우 작은 경우 패널(세로 175px)이 화면 많은 부분을 가릴 수 있습니다. 코드의 (0, 0), (w, 175) 숫자를 줄여서 패널 높이를 조절할 수 있습니다.

수업 끝나고 전체 삭제하기

Windows CMD 삭제 명령어
cd %USERPROFILE%
rmdir /s /q object_recognition_laptop_app
dir
주의: 삭제 명령은 되돌리기 어렵습니다.

체크리스트

← 메인으로 돌아가기