오늘 실습 흐름
??로 변하는 문제를 피했습니다.
개인정보 약속
- 본인 또는 촬영에 동의한 사람의 얼굴만 사용합니다.
- 얼굴 사진과 모델은 라즈베리파이 내부에만 저장합니다.
- 사람 이름은
student01처럼 영문과 숫자로 입력합니다. - 수업 종료 후 선생님 안내에 따라 얼굴 데이터를 삭제합니다.
1단계. 라즈베리파이 환경 확인
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 파일이 없으면 새로 만들어집니다.
cd ~/face_recognition_raspi_app
nano camera_utils.py
② 아래 전체 코드 복사하기
코드 복사하기를 누른 뒤 nano 화면에서 Ctrl + Shift + V로 붙여넣습니다. 터미널 환경에 따라 마우스 오른쪽 버튼 → 붙여넣기를 사용할 수도 있습니다.
# -*- 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)"
③ 저장하고 닫기
/dev/video0을 먼저 확인하고,
사용할 수 없으면 기존 Raspberry Pi 카메라 명령인 raspistill로 자동 전환합니다.
4단계. 카메라 테스트 코드 만들기
① nano로 Python 파일 만들기
터미널에서 아래 명령을 실행하면 nano 편집기가 열리며, 0_camera_test.py 파일이 없으면 새로 만들어집니다.
cd ~/face_recognition_raspi_app
nano 0_camera_test.py
② 아래 전체 코드 복사하기
코드 복사하기를 누른 뒤 nano 화면에서 Ctrl + Shift + V로 붙여넣습니다. 터미널 환경에 따라 마우스 오른쪽 버튼 → 붙여넣기를 사용할 수도 있습니다.
# -*- 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()
③ 저장하고 닫기
④ 파일 실행하기
cd ~/face_recognition_raspi_app
python3 0_camera_test.py
5단계. 얼굴 사진 수집 코드 만들기
① nano로 Python 파일 만들기
터미널에서 아래 명령을 실행하면 nano 편집기가 열리며, 1_collect_faces.py 파일이 없으면 새로 만들어집니다.
cd ~/face_recognition_raspi_app
nano 1_collect_faces.py
② 아래 전체 코드 복사하기
코드 복사하기를 누른 뒤 nano 화면에서 Ctrl + Shift + V로 붙여넣습니다. 터미널 환경에 따라 마우스 오른쪽 버튼 → 붙여넣기를 사용할 수도 있습니다.
# -*- 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()
③ 저장하고 닫기
④ 파일 실행하기
cd ~/face_recognition_raspi_app
python3 1_collect_faces.py
student01처럼 영문과 숫자로 입력합니다.6단계. 얼굴 모델 학습 코드 만들기
① nano로 Python 파일 만들기
터미널에서 아래 명령을 실행하면 nano 편집기가 열리며, 2_train_model.py 파일이 없으면 새로 만들어집니다.
cd ~/face_recognition_raspi_app
nano 2_train_model.py
② 아래 전체 코드 복사하기
코드 복사하기를 누른 뒤 nano 화면에서 Ctrl + Shift + V로 붙여넣습니다. 터미널 환경에 따라 마우스 오른쪽 버튼 → 붙여넣기를 사용할 수도 있습니다.
# -*- 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()
③ 저장하고 닫기
④ 파일 실행하기
cd ~/face_recognition_raspi_app
python3 2_train_model.py
trainer/face_model.npz 파일이 만들어집니다.
7단계. 실시간 얼굴 인식 코드 만들기
① nano로 Python 파일 만들기
터미널에서 아래 명령을 실행하면 nano 편집기가 열리며, 3_recognize_live.py 파일이 없으면 새로 만들어집니다.
cd ~/face_recognition_raspi_app
nano 3_recognize_live.py
② 아래 전체 코드 복사하기
코드 복사하기를 누른 뒤 nano 화면에서 Ctrl + Shift + V로 붙여넣습니다. 터미널 환경에 따라 마우스 오른쪽 버튼 → 붙여넣기를 사용할 수도 있습니다.
# -*- 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()
③ 저장하고 닫기
④ 파일 실행하기
cd ~/face_recognition_raspi_app
python3 3_recognize_live.py
raspistill 방식에서는 화면이 USB 웹캠보다 느리게 갱신될 수 있습니다.
잘 안 될 때
| 증상 | 확인 및 해결 |
|---|---|
Is a directory | mkdir 파일명.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