← 전체 과정으로 돌아가기
학생용 실습 교안 · Raspberry Pi 4 · Raspbian Buster · nano 복사·붙여넣기

라즈베리파이 카메라로 얼굴인식 모델 만들기

각 단계에서 nano로 Python 파일을 바로 만들고, 웹교재의 코드를 복사해 붙여넣은 뒤 저장하고 실행합니다.

오늘 실습 흐름

1. nano로 파일 만들기터미널에서 nano 명령으로 각 Python 파일을 바로 만듭니다.
2. 코드 붙여넣기웹교재의 전체 코드를 복사해 파일에 붙여넣고 저장합니다.
3. 순서대로 실행카메라 테스트 → 얼굴 수집 → 모델 학습 → 얼굴 인식을 진행합니다.
한글 깨짐 방지: 복사하는 Python 코드는 영문·숫자 중심으로 작성되어 라즈베리파이에서 ??로 변하는 문제를 피했습니다.

개인정보 약속

1단계. 라즈베리파이 환경 확인

OS·카메라·Python 확인 명령어
cat /etc/os-release
which raspistill
vcgencmd get_camera
python3 -c "import cv2, numpy; print('OpenCV:', cv2.__version__); print('NumPy:', numpy.__version__)"
정상 기준: /usr/bin/raspistill, supported=1 detected=1, OpenCV와 NumPy 버전이 출력되면 됩니다.

2단계. 실습 폴더 만들기

실습 폴더 생성 명령어
mkdir -p ~/face_recognition_raspi_app/dataset
mkdir -p ~/face_recognition_raspi_app/trainer
cd ~/face_recognition_raspi_app
pwd

마지막 줄에 /home/pi/face_recognition_raspi_app가 출력되면 정상입니다.

3단계. 공통 카메라 기능 파일 만들기

① nano로 Python 파일 만들기

터미널에서 아래 명령을 실행하면 nano 편집기가 열리며, camera_utils.py 파일이 없으면 새로 만들어집니다.

camera_utils.py 만들기 명령어
cd ~/face_recognition_raspi_app
nano camera_utils.py

② 아래 전체 코드 복사하기

코드 복사하기를 누른 뒤 nano 화면에서 Ctrl + Shift + V로 붙여넣습니다. 터미널 환경에 따라 마우스 오른쪽 버튼 → 붙여넣기를 사용할 수도 있습니다.

camera_utils.py 전체 코드
# -*- coding: utf-8 -*-
import os
import shutil
import subprocess
import tempfile

import cv2


def find_face_cascade():
    filename = "haarcascade_frontalface_default.xml"
    candidates = []

    if hasattr(cv2, "data") and hasattr(cv2.data, "haarcascades"):
        candidates.append(os.path.join(cv2.data.haarcascades, filename))

    candidates.extend([
        os.path.join("/usr/share/opencv/haarcascades", filename),
        os.path.join("/usr/share/opencv4/haarcascades", filename),
        os.path.join("/usr/local/share/opencv4/haarcascades", filename),
    ])

    for path in candidates:
        if os.path.exists(path):
            cascade = cv2.CascadeClassifier(path)
            if not cascade.empty():
                return cascade

    raise RuntimeError(
        "Haar Cascade file was not found. "
        "Ask the instructor to check the OpenCV installation."
    )


class CameraSource:
    """
    Camera source for Raspberry Pi OS Buster.

    Priority:
    1. /dev/video0 through OpenCV VideoCapture
    2. Legacy Raspberry Pi camera through raspistill snapshots
    """

    def __init__(self, width=640, height=480):
        self.width = int(width)
        self.height = int(height)
        self.cap = None
        self.mode = None
        self.temp_path = os.path.join(
            tempfile.gettempdir(),
            "raspi_face_frame.jpg"
        )

        cap = cv2.VideoCapture(0)

        if cap.isOpened():
            cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
            cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)

            ok, frame = cap.read()
            if ok and frame is not None:
                self.cap = cap
                self.mode = "video"
                return

        cap.release()

        if shutil.which("raspistill"):
            self.mode = "raspistill"
            return

        raise RuntimeError(
            "No camera source was found. "
            "Check /dev/video0 or the raspistill command."
        )

    def read(self):
        if self.mode == "video":
            return self.cap.read()

        command = [
            "raspistill",
            "-n",
            "-t", "180",
            "-w", str(self.width),
            "-h", str(self.height),
            "-q", "75",
            "-o", self.temp_path,
        ]

        try:
            subprocess.run(
                command,
                check=True,
                timeout=6,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            )
        except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
            return False, None

        frame = cv2.imread(self.temp_path)
        return frame is not None, frame

    def release(self):
        if self.cap is not None:
            self.cap.release()

        if os.path.exists(self.temp_path):
            try:
                os.remove(self.temp_path)
            except OSError:
                pass

    def mode_name(self):
        if self.mode == "video":
            return "OpenCV VideoCapture (/dev/video0)"
        return "Legacy Raspberry Pi camera (raspistill)"

③ 저장하고 닫기

1Ctrl + O를 눌러 저장을 시작합니다.
2화면 아래 파일명이 맞는지 확인하고 Enter를 누릅니다.
3Ctrl + X를 눌러 nano를 종료합니다.
이 파일은 다른 Python 파일에서 불러오는 공통 기능 파일이므로 단독으로 실행하지 않습니다.
이 파일은 USB 카메라의 /dev/video0을 먼저 확인하고, 사용할 수 없으면 기존 Raspberry Pi 카메라 명령인 raspistill로 자동 전환합니다.

4단계. 카메라 테스트 코드 만들기

① nano로 Python 파일 만들기

터미널에서 아래 명령을 실행하면 nano 편집기가 열리며, 0_camera_test.py 파일이 없으면 새로 만들어집니다.

0_camera_test.py 만들기 명령어
cd ~/face_recognition_raspi_app
nano 0_camera_test.py

② 아래 전체 코드 복사하기

코드 복사하기를 누른 뒤 nano 화면에서 Ctrl + Shift + V로 붙여넣습니다. 터미널 환경에 따라 마우스 오른쪽 버튼 → 붙여넣기를 사용할 수도 있습니다.

0_camera_test.py 전체 코드
# -*- coding: utf-8 -*-
import cv2

from camera_utils import CameraSource


def main():
    camera = CameraSource(640, 480)

    print("Camera test started.")
    print("Camera mode:", camera.mode_name())
    print("Click the camera window and press q to stop.")

    try:
        while True:
            ok, frame = camera.read()

            if not ok or frame is None:
                print("A camera frame could not be captured.")
                break

            frame = cv2.flip(frame, 1)

            cv2.putText(
                frame,
                camera.mode_name(),
                (12, 28),
                cv2.FONT_HERSHEY_SIMPLEX,
                0.55,
                (0, 255, 0),
                2,
            )

            cv2.imshow("Raspberry Pi Camera Test", frame)

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


if __name__ == "__main__":
    main()

③ 저장하고 닫기

1Ctrl + O를 눌러 저장을 시작합니다.
2화면 아래 파일명이 맞는지 확인하고 Enter를 누릅니다.
3Ctrl + X를 눌러 nano를 종료합니다.

④ 파일 실행하기

실행 명령어
cd ~/face_recognition_raspi_app
python3 0_camera_test.py
카메라 창이 열리면 정상입니다. 종료할 때는 카메라 창을 한 번 클릭한 뒤 q 키를 누릅니다.

5단계. 얼굴 사진 수집 코드 만들기

① nano로 Python 파일 만들기

터미널에서 아래 명령을 실행하면 nano 편집기가 열리며, 1_collect_faces.py 파일이 없으면 새로 만들어집니다.

1_collect_faces.py 만들기 명령어
cd ~/face_recognition_raspi_app
nano 1_collect_faces.py

② 아래 전체 코드 복사하기

코드 복사하기를 누른 뒤 nano 화면에서 Ctrl + Shift + V로 붙여넣습니다. 터미널 환경에 따라 마우스 오른쪽 버튼 → 붙여넣기를 사용할 수도 있습니다.

1_collect_faces.py 전체 코드
# -*- coding: utf-8 -*-
import os
import re
import time

import cv2

from camera_utils import CameraSource, find_face_cascade


def safe_name(value):
    value = value.strip()
    value = re.sub(r"[^A-Za-z0-9_-]", "_", value)
    return value or "student01"


def main():
    face_cascade = find_face_cascade()
    person_name = safe_name(
        input("Enter a person name in English: ")
    )

    save_dir = os.path.join("dataset", person_name)
    os.makedirs(save_dir, exist_ok=True)

    camera = CameraSource(640, 480)
    count = 0
    max_count = 60
    last_saved = 0.0

    print("Face collection started.")
    print("Camera mode:", camera.mode_name())
    print("Look at the camera and slowly change your face angle.")
    print("Press q to stop.")

    try:
        while True:
            ok, frame = camera.read()

            if not ok or frame is None:
                print("A camera frame could not be captured.")
                break

            frame = cv2.flip(frame, 1)
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

            faces = face_cascade.detectMultiScale(
                gray,
                scaleFactor=1.1,
                minNeighbors=5,
                minSize=(70, 70),
            )

            current_time = time.time()

            for x, y, w, h in faces[:1]:
                if current_time - last_saved >= 0.12:
                    face_img = gray[y:y + h, x:x + w]
                    face_img = cv2.equalizeHist(face_img)
                    face_img = cv2.resize(face_img, (100, 100))

                    count += 1
                    file_path = os.path.join(
                        save_dir,
                        "{:03d}.jpg".format(count),
                    )
                    cv2.imwrite(file_path, face_img)
                    last_saved = current_time

                cv2.rectangle(
                    frame,
                    (x, y),
                    (x + w, y + h),
                    (0, 255, 0),
                    2,
                )

                cv2.putText(
                    frame,
                    "Collecting: {}/{}".format(count, max_count),
                    (x, max(25, y - 10)),
                    cv2.FONT_HERSHEY_SIMPLEX,
                    0.7,
                    (0, 255, 0),
                    2,
                )

            cv2.imshow("Face Collection - Raspberry Pi", frame)

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

            if count >= max_count:
                print("Face collection completed.")
                break
    finally:
        camera.release()
        cv2.destroyAllWindows()

    print("Saved folder:", save_dir)
    print("Saved images:", count)


if __name__ == "__main__":
    main()

③ 저장하고 닫기

1Ctrl + O를 눌러 저장을 시작합니다.
2화면 아래 파일명이 맞는지 확인하고 Enter를 누릅니다.
3Ctrl + X를 눌러 nano를 종료합니다.

④ 파일 실행하기

실행 명령어
cd ~/face_recognition_raspi_app
python3 1_collect_faces.py
1
이름은 student01처럼 영문과 숫자로 입력합니다.
2
얼굴을 정면에 두고 좌우·위아래 방향을 조금씩 바꿉니다.
3
사진 60장이 저장되면 자동 종료됩니다.

6단계. 얼굴 모델 학습 코드 만들기

① nano로 Python 파일 만들기

터미널에서 아래 명령을 실행하면 nano 편집기가 열리며, 2_train_model.py 파일이 없으면 새로 만들어집니다.

2_train_model.py 만들기 명령어
cd ~/face_recognition_raspi_app
nano 2_train_model.py

② 아래 전체 코드 복사하기

코드 복사하기를 누른 뒤 nano 화면에서 Ctrl + Shift + V로 붙여넣습니다. 터미널 환경에 따라 마우스 오른쪽 버튼 → 붙여넣기를 사용할 수도 있습니다.

2_train_model.py 전체 코드
# -*- coding: utf-8 -*-
import os

import cv2
import numpy as np


IMAGE_SIZE = (100, 100)


def make_feature(image):
    image = cv2.resize(image, IMAGE_SIZE)
    image = cv2.equalizeHist(image)
    vector = image.astype(np.float32).reshape(-1)
    vector -= float(vector.mean())

    norm = float(np.linalg.norm(vector))
    if norm > 0:
        vector /= norm

    return vector


def main():
    dataset_dir = "dataset"
    trainer_dir = "trainer"
    os.makedirs(trainer_dir, exist_ok=True)

    if not os.path.isdir(dataset_dir):
        print("The dataset folder does not exist.")
        print("Run 1_collect_faces.py first.")
        return

    names = []
    prototypes = []
    sample_counts = []

    for person_name in sorted(os.listdir(dataset_dir)):
        person_path = os.path.join(dataset_dir, person_name)

        if not os.path.isdir(person_path):
            continue

        features = []

        for image_name in sorted(os.listdir(person_path)):
            image_path = os.path.join(person_path, image_name)
            image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)

            if image is None:
                continue

            features.append(make_feature(image))

        if not features:
            continue

        prototype = np.mean(np.stack(features), axis=0)
        prototype_norm = float(np.linalg.norm(prototype))

        if prototype_norm > 0:
            prototype /= prototype_norm

        names.append(person_name)
        prototypes.append(prototype.astype(np.float32))
        sample_counts.append(len(features))

    if not prototypes:
        print("No valid face images were found.")
        print("Run 1_collect_faces.py first.")
        return

    model_path = os.path.join(trainer_dir, "face_model.npz")

    np.savez_compressed(
        model_path,
        names=np.array(names, dtype="<U64"),
        prototypes=np.stack(prototypes).astype(np.float32),
        sample_counts=np.array(sample_counts, dtype=np.int32),
    )

    print("Training completed.")
    print("Model file:", model_path)

    for name, count in zip(names, sample_counts):
        print("- {}: {} images".format(name, count))


if __name__ == "__main__":
    main()

③ 저장하고 닫기

1Ctrl + O를 눌러 저장을 시작합니다.
2화면 아래 파일명이 맞는지 확인하고 Enter를 누릅니다.
3Ctrl + X를 눌러 nano를 종료합니다.

④ 파일 실행하기

실행 명령어
cd ~/face_recognition_raspi_app
python3 2_train_model.py
학습이 끝나면 trainer/face_model.npz 파일이 만들어집니다.

7단계. 실시간 얼굴 인식 코드 만들기

① nano로 Python 파일 만들기

터미널에서 아래 명령을 실행하면 nano 편집기가 열리며, 3_recognize_live.py 파일이 없으면 새로 만들어집니다.

3_recognize_live.py 만들기 명령어
cd ~/face_recognition_raspi_app
nano 3_recognize_live.py

② 아래 전체 코드 복사하기

코드 복사하기를 누른 뒤 nano 화면에서 Ctrl + Shift + V로 붙여넣습니다. 터미널 환경에 따라 마우스 오른쪽 버튼 → 붙여넣기를 사용할 수도 있습니다.

3_recognize_live.py 전체 코드
# -*- coding: utf-8 -*-
import os

import cv2
import numpy as np

from camera_utils import CameraSource, find_face_cascade


IMAGE_SIZE = (100, 100)
MODEL_PATH = os.path.join("trainer", "face_model.npz")
SIMILARITY_THRESHOLD = 72.0


def make_feature(image):
    image = cv2.resize(image, IMAGE_SIZE)
    image = cv2.equalizeHist(image)
    vector = image.astype(np.float32).reshape(-1)
    vector -= float(vector.mean())

    norm = float(np.linalg.norm(vector))
    if norm > 0:
        vector /= norm

    return vector


def main():
    if not os.path.exists(MODEL_PATH):
        print("The trained model was not found.")
        print("Run 2_train_model.py first.")
        return

    model = np.load(MODEL_PATH, allow_pickle=False)
    names = model["names"]
    prototypes = model["prototypes"]

    face_cascade = find_face_cascade()
    camera = CameraSource(640, 480)

    print("Live face recognition started.")
    print("Camera mode:", camera.mode_name())
    print("The percentage is a similarity score, not a probability.")
    print("Press q to stop.")

    try:
        while True:
            ok, frame = camera.read()

            if not ok or frame is None:
                print("A camera frame could not be captured.")
                break

            frame = cv2.flip(frame, 1)
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

            faces = face_cascade.detectMultiScale(
                gray,
                scaleFactor=1.1,
                minNeighbors=5,
                minSize=(70, 70),
            )

            for x, y, w, h in faces:
                face_img = gray[y:y + h, x:x + w]
                feature = make_feature(face_img)

                raw_scores = np.dot(prototypes, feature)
                score_index = int(np.argmax(raw_scores))
                cosine_score = float(raw_scores[score_index])

                similarity = max(
                    0.0,
                    min(100.0, (cosine_score + 1.0) * 50.0),
                )

                if similarity >= SIMILARITY_THRESHOLD:
                    name = str(names[score_index])
                    color = (0, 255, 0)
                else:
                    name = "Unknown"
                    color = (0, 0, 255)

                label = "{} ({:.0f}%)".format(name, similarity)

                cv2.rectangle(
                    frame,
                    (x, y),
                    (x + w, y + h),
                    color,
                    2,
                )

                cv2.putText(
                    frame,
                    label,
                    (x, max(25, y - 10)),
                    cv2.FONT_HERSHEY_SIMPLEX,
                    0.72,
                    color,
                    2,
                )

            cv2.imshow(
                "Live Face Recognition - Raspberry Pi",
                frame,
            )

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


if __name__ == "__main__":
    main()

③ 저장하고 닫기

1Ctrl + O를 눌러 저장을 시작합니다.
2화면 아래 파일명이 맞는지 확인하고 Enter를 누릅니다.
3Ctrl + X를 눌러 nano를 종료합니다.

④ 파일 실행하기

실행 명령어
cd ~/face_recognition_raspi_app
python3 3_recognize_live.py
얼굴 위에 영문 이름과 유사도 점수가 표시됩니다. 표시되는 %는 신원을 확정하는 확률이 아니라 학습 이미지와의 유사도입니다.
raspistill 방식에서는 화면이 USB 웹캠보다 느리게 갱신될 수 있습니다.

잘 안 될 때

증상확인 및 해결
Is a directorymkdir 파일명.py를 실행하면 파일이 아니라 폴더가 만들어집니다. 잘못 만든 폴더는 rmdir 파일명.py로 삭제한 뒤 nano 파일명.py를 실행합니다.
No such file or directory ls -la로 현재 폴더에 파일이 저장되었는지 확인합니다.
supported=1 detected=0 전원을 끈 뒤 카메라 리본 케이블 방향과 연결 상태를 확인합니다.
카메라 창이 열리지 않음 raspistill -n -t 1000 -o test.jpg로 사진 촬영을 먼저 시험합니다.
카메라 화면이 느림 raspistill 모드는 사진을 반복 촬영하므로 USB 웹캠보다 느릴 수 있습니다.
얼굴이 검출되지 않음 밝은 곳에서 얼굴을 화면 중앙에 크게 두고 역광을 줄입니다.

수업 종료 후 전체 삭제

실습 폴더 삭제 명령어
cd ~
rm -rf ~/face_recognition_raspi_app
ls
주의: 삭제한 얼굴 사진과 모델은 복구하기 어렵습니다. 선생님 안내에 따라 실행합니다.

최종 체크리스트