PYTHON GUIDE

BrainAI Car 프로젝트 코드로 배우는 파이썬

실제 utils/ 파일들의 코드를 읽으면서 파이썬 핵심 개념을 익혀요. 코드를 먼저 봐도 되고, 막히는 단원을 골라봐도 돼요.

기초 · 코스 1 코드 · 단원 1~10
1
import

다른 파일 가져오기

brain_ai_car.py

다른 파일에 있는 코드를 현재 파일에서 쓰고 싶을 때 import 해요. `from 파일 import 이름` 형태가 가장 많이 쓰여요.

utils/brain_ai_car.py 에서 발췌
# 노트북에서 car 모듈 불러오기
from utils.brain_ai_car import BrainAICar
# 이제 BrainAICar 클래스를 쓸 수 있어요
car = BrainAICar(mock=True)
# 여러 개를 한 번에 import 할 수도 있어요
from utils.brain_ai_car import BrainAICar, SERVO_CENTER, DEFAULT_FORWARD_SPEED
✏️ 직접 해보기import math 하고 print(math.pi) 를 실행해 보세요.
2
변수 · 타입

값에 이름 붙이기

brain_ai_car.py

변수는 값을 담는 상자예요. 파이썬은 `=` 오른쪽 값을 보고 타입을 자동으로 결정해요 — 숫자면 int, 소수면 float, 글자면 str, 참/거짓이면 bool.

utils/brain_ai_car.py 상수 및 인스턴스 변수
# brain_ai_car.py 에서 실제로 쓰는 변수들
BAUD_RATE = 115200 # int — 시리얼 통신 속도
DEFAULT_FORWARD_SPEED = 350 # int — 기본 전진 속도
SERVO_CENTER = 90 # int — 서보 중립 각도
STOP_HOLD_DURATION = 0.5 # float — 정지 유지 시간(초)
# 객체 생성 후 인스턴스 변수
self.forward_speed = 350 # int — 현재 설정된 속도
self._speed_confirmed = True # bool — 속도 명령 확인 여부
port_name = "COM3" # str — 시리얼 포트 이름
✏️ 직접 해보기speed = 350 을 만들고 speed = speed + 50 으로 바꿔서 print(speed) 해보세요.
3
클래스 · 객체

설계도와 실체

brain_ai_car.pymodeling.py

클래스는 설계도, 객체는 설계도로 만든 실제 부품이에요. `class` 로 설계도를 만들고, `클래스이름()` 으로 실체(인스턴스)를 생성해요. `__init__` 은 생성과 동시에 자동으로 실행되는 함수예요.

utils/brain_ai_car.py, utils/modeling.py
# brain_ai_car.py — 자동차 제어 클래스
class BrainAICar:
def __init__(self, mock: bool = False):
self.mock = mock
self.forward_speed = 350
car = BrainAICar(mock=True) # 인스턴스 만들기
print(car.forward_speed) # 350
# modeling.py — 훈련 설정 클래스 (dataclass 방식)
@dataclass
class TrainingConfig:
dataset_path: str = "laneD1_Dataset"
model_name: str = "laneD1"
epochs: int = 30
batch_size: int = 32
config = TrainingConfig()
print(config.model_name) # "laneD1"
✏️ 직접 해보기car = BrainAICar(mock=True) 로 만들고 car.forward_speed = 400 으로 바꾼 뒤 print(car.forward_speed) 해보세요.
4
함수 · 메서드

반복 동작을 묶기

brain_ai_car.py

`def` 로 함수를 정의해요. 클래스 안에 있으면 '메서드'라고 불러요. 첫 번째 인자 `self` 는 '이 객체 자신'을 가리켜요. `-> None` 은 반환값이 없다는 의미예요.

utils/brain_ai_car.py 핵심 제어 메서드
# brain_ai_car.py — 제어 메서드 예시
def drive_forward(self, speed: int = None) -> None:
"""전진 명령을 전송합니다."""
target = speed if speed is not None else self.forward_speed
self._issue_speed_command(target)
def steer(self, steering_value) -> None:
"""조향 명령을 전송합니다. -1.0(우) ~ +1.0(좌)"""
angle = SERVO_CENTER - int(steering_value * self.steering_range)
angle = max(SERVO_MIN, min(SERVO_MAX, angle))
self._send(angle, bypass_rate_limit=True)
# 사용
car.drive_forward() # 기본 속도로 전진
car.drive_forward(400) # 속도 400으로 전진
car.steer(0.3) # 살짝 우회전
✏️ 직접 해보기def add(a, b): return a + b 를 만들고 print(add(3, 5)) 를 실행해보세요.
5
조건문

경우에 따라 다르게

brain_ai_car.py

`if` 는 '만약 ~이면'이에요. `not` 은 '~이 아니면', `in` 은 '~안에 있으면'이에요. `elif` 로 추가 조건, `else` 로 나머지 경우를 처리해요.

utils/brain_ai_car.py 조건 분기 패턴
# brain_ai_car.py — USB 포트 찾기
def _find_microbit_port(self):
for port, desc, _ in sorted(serial.tools.list_ports.comports()):
if 'USB' in desc: # desc 에 'USB' 문자가 있으면
return port
return None # 없으면 None 반환
# 연결 확인 후 처리
port = self._find_microbit_port()
if not port:
print("[ERROR] micro:bit not found.")
return
# 속도 명령 타입 분기
if speed == 0: # 정지
self._in_forced_stop = True
elif speed == -1: # 후진
self._issue_speed_command(-1)
else: # 전진
self._issue_speed_command(speed)
✏️ 직접 해보기speed = 350; print('빠름' if speed > 300 else '느림') 을 실행해보세요.
6
반복문

같은 일 여러 번

brain_ai_car.py

`for` 는 리스트나 범위의 항목을 하나씩 꺼내 반복해요. `break` 는 반복을 즉시 멈추고, `continue` 는 이번 회차를 건너뛰어요.

utils/brain_ai_car.py 반복 패턴
# brain_ai_car.py — 직렬 포트 목록을 순회해 마이크로비트 찾기
for port, desc, hwid in sorted(serial.tools.list_ports.comports()):
print(f'포트: {port}, 설명: {desc}')
if 'USB' in desc:
print('마이크로비트 발견!')
break # 찾으면 즉시 종료
# 안전 감지 결과 순회 (check_safety_objects 내부)
for i, (class_id, conf) in enumerate(zip(classes, confidences)):
if conf < confidence_threshold:
continue # 신뢰도 낮으면 건너뜀
detected.append(class_names[class_id])
✏️ 직접 해보기for i in range(5): print(i) 를 실행해보세요.
7
try / except

오류가 나도 멈추지 않게

brain_ai_car.py

`try` 블록을 실행하다 오류가 생기면 `except` 로 넘어와요. 프로그램이 갑자기 종료되는 걸 막아줘요. `Exception as e` 로 오류 메시지를 꺼낼 수 있어요.

utils/brain_ai_car.py 예외 처리 패턴
# brain_ai_car.py — 시리얼 연결 예외 처리
try:
self._serial = serial.Serial(
port, BAUD_RATE, timeout=0.1,
parity=serial.PARITY_NONE, rtscts=False,
)
print(f'[OK] Connected on {port}')
self._serial.reset_input_buffer()
except Exception as e:
print(f'[ERROR] {e}')
self._serial = None
# 데이터 전송 예외 처리
try:
data = f'{int(cmd)}\n'.encode('utf-8')
written = self._serial.write(data)
return True
except Exception as e:
print(f'[SEND ERROR] {e}')
return False
✏️ 직접 해보기try: print(1/0) \nexcept ZeroDivisionError: print('0으로 나눌 수 없어요') 를 실행해보세요.
8
f-string

변수를 문자열 안에

modeling.py

문자열 앞에 `f` 를 붙이면 `{변수명}` 위치에 실제 값이 들어가요. `{숫자:.4f}` 처럼 소수점 자릿수, `{'ON' if 조건 else 'OFF'}` 처럼 조건식도 바로 넣을 수 있어요.

utils/modeling.py TrainingConfig.print_summary() / _print_results()
# modeling.py — TrainingConfig.print_summary() 에서
def print_summary(self) -> None:
print("=" * 55)
print("Training Configuration")
print(f' Dataset: {self.dataset_path}')
print(f' Model name: {self.model_name}')
print(f' Input size: {self.img_width}×{self.img_height}')
print(f' Epochs: {self.epochs}')
print(f' Batch size: {self.batch_size}')
print(f' Learning rate: {self.learning_rate}')
# 조건식을 중괄호 안에 바로 넣기
print(f" Augmentation: {'ON' if self.use_augmentation else 'OFF'}")
# modeling.py — _print_results() 에서 소수점 포맷팅
print(f" Final train loss: {h['loss'][-1]:.4f} MAE: {h['mae'][-1]:.4f}")
print(f" Final val loss: {h['val_loss'][-1]:.4f} MAE: {h['val_mae'][-1]:.4f}")
# → Final train loss: 0.0123 MAE: 0.0871
✏️ 직접 해보기name = 'BrainAI'; score = 0.0871; print(f'모델 {name}: MAE = {score:.4f}') 를 실행해보세요.
9
리스트 · 딕셔너리

여러 값 묶어서 관리

brain_ai_car.pymodeling.py

리스트(`[]`)는 순서 있는 값의 묶음이에요. 딕셔너리(`{}`)는 키-값 쌍의 묶음이에요. 리스트는 인덱스(0부터)로, 딕셔너리는 키로 접근해요.

utils/brain_ai_car.py 리스트, utils/modeling.py 딕셔너리
# brain_ai_car.py — 리스트: 감지 대상 클래스 목록
self.safety_classes = ['person', 'car', 'dog', 'stop_sign', 'crosswalk']
print(self.safety_classes[0]) # 'person'
self.safety_classes.append('bicycle') # 항목 추가
# modeling.py — 딕셔너리: 증강 옵션 ON/OFF 설정
augmentation_options: Dict[str, bool] = {
"COLOR_JITTER": False, # 밝기·채도 랜덤 변환
"HORIZONTAL_FLIP": False, # 좌우 반전
"GAUSSIAN_NOISE": False, # 노이즈 추가
}
# 딕셔너리 키로 접근해서 분기
opts = self.config.augmentation_options
if opts.get("COLOR_JITTER"):
img_arr = ImageAugmentation.color_jitter(img_arr)
if opts.get("GAUSSIAN_NOISE"):
img_arr = ImageAugmentation.gaussian_noise(img_arr)
✏️ 직접 해보기my_list = [1, 2, 3]; my_list.append(4); print(my_list) 를 실행해보세요.
10
threading

두 가지 일 동시에

brain_ai_car.py

스레드는 '여러 일을 동시에' 처리하는 방법이에요. 자동차가 주행하는 동안 마이크로비트 응답을 별도 스레드로 계속 기다려요. `daemon=True` 는 메인 프로그램이 끝나면 이 스레드도 같이 종료한다는 뜻이에요.

utils/brain_ai_car.py 백그라운드 읽기 스레드
# brain_ai_car.py — 백그라운드 스레드로 시리얼 수신 처리
import threading
def _start_read_thread(self):
self._read_thread = threading.Thread(
target=self._read_loop, # 별도 스레드로 실행할 함수
daemon=True, # 메인 종료 시 같이 종료
)
self._read_thread.start()
def _read_loop(self):
while not self._stop_reading:
if self._serial.in_waiting > 0:
line = self._serial.readline().decode('utf-8').strip()
self._handle_echo(line) # 마이크로비트 응답 처리
time.sleep(0.001)
✏️ 직접 해보기import threading; t = threading.Thread(target=lambda: print('별도 스레드!')); t.start() 를 실행해보세요.
심화 · 코스 2 코드 · 단원 11~20

코스 2 object_detection.py 와 라벨링 툴 코드에서 뽑은 심화 개념이에요. 기초 단원을 끝냈거나, 개념이 궁금할 때 골라 읽어요.

11
@property

데코레이터 — 괄호 없는 속성

object_detection.py

`@property` 를 메서드 위에 붙이면 `obj.latency()` 대신 `obj.latency` 처럼 괄호 없이 호출할 수 있어요. 읽기 전용 계산값을 속성처럼 노출할 때 씁니다.

utils/object_detection.py ObjectDetector.latency
# object_detection.py — 최근 추론 지연(ms) 계산
class ObjectDetector:
def __init__(self):
self.latency_history = []
@property
def latency(self) -> float:
"""최근 10회 평균 추론 시간(ms)."""
n = min(10, len(self.latency_history))
return np.mean(self.latency_history[:n]) if n else 0.0
detector = ObjectDetector()
# 괄호 없이 속성처럼 읽음
print(detector.latency) # 예: 23.4
✏️ 직접 해보기class Circle:\n def __init__(self, r): self.r = r\n @property\n def area(self): return 3.14 * self.r ** 2\nc = Circle(5); print(c.area)
12
yield (제너레이터)

결과를 하나씩 내놓기

object_detection.py

`yield` 가 있는 함수는 제너레이터예요. 결과를 한꺼번에 리스트로 만들지 않고 요청할 때마다 하나씩 계산해요. 이미지 수천 장을 처리할 때 메모리를 크게 아낄 수 있어요.

utils/object_detection.py _iter_boxes()
# object_detection.py — 감지 결과를 하나씩 yield
def _iter_boxes(self, results):
boxes = results[0].boxes
names = results[0].names
confs = boxes.conf.cpu().numpy()
clids = boxes.cls.cpu().numpy().astype(int)
xyxys = boxes.xyxy.cpu().numpy().astype(int)
for i in range(len(boxes)):
name = names.get(clids[i], '')
yield name, float(confs[i]), xyxys[i]
# ↑ 이름 ↑ 신뢰도 ↑ 좌표
# 사용 — for 루프가 하나씩 꺼냄
for name, conf, coords in detector._iter_boxes(results):
print(name, conf)
✏️ 직접 해보기def count_up(n):\n for i in range(n): yield i\nfor v in count_up(3): print(v)
13
튜플 언패킹

한 줄에서 여러 값 꺼내기

object_detection.py

튜플(또는 리스트)의 값들을 변수 여러 개에 한꺼번에 담는 걸 언패킹이라 해요. `(x1, y1, x2, y2)` 처럼 중첩 구조도 한 줄에 풀 수 있어요.

utils/object_detection.py 감지 루프 언패킹
# object_detection.py — 감지 결과에서 좌표 언패킹
for name, conf, (x1, y1, x2, y2) in self._iter_boxes(results):
# ↑이름 ↑신뢰도 ↑ 좌표 4개를 중첩 언패킹
width = x2 - x1
height = y2 - y1
print(f'{name}: 좌표({x1},{y1})~({x2},{y2})')
# 기본 언패킹 예시
point = (100, 200)
x, y = point
print(x, y) # 100 200
# swap — 언패킹으로 두 값 교환
a, b = 1, 2
a, b = b, a
print(a, b) # 2 1
✏️ 직접 해보기pair = (42, 'hello'); num, text = pair; print(num, text)
14
클래스 변수

모든 객체가 공유하는 값

object_detection.py

클래스 안에 `self.` 없이 선언한 변수는 클래스 변수예요. 모든 인스턴스가 공유하고 `클래스명.변수` 로 접근해요. 감지할 표지판 종류처럼 '변하지 않는 공통 목록'에 씁니다.

utils/object_detection.py SIGN_CLASSES
# object_detection.py — 인식할 표지판 클래스 목록
class ObjectDetector:
# self 없이 클래스 바로 아래 선언 → 클래스 변수
SIGN_CLASSES = [
"left_turn_sign",
"stop_sign",
"forward_sign",
"right_turn_sign",
"school_zone_sign",
]
def is_sign(self, name: str) -> bool:
return name in ObjectDetector.SIGN_CLASSES
# 인스턴스 없이도 접근 가능
print(ObjectDetector.SIGN_CLASSES[0]) # 'left_turn_sign'
✏️ 직접 해보기class Config:\n MAX_SPEED = 400\nprint(Config.MAX_SPEED)
15
리스트 컴프리헨션

for 반복문을 한 줄로

object_detection.py

`[표현식 for 항목 in 시퀀스]` 형태로 리스트를 한 줄에 만들어요. `if 조건` 을 붙이면 필터링도 돼요. 일반 for 루프보다 짧고 빠릅니다.

utils/object_detection.py 클래스 정규화
# object_detection.py — 감지할 클래스를 소문자로 정규화
targets = [c.lower() for c in custom_classes]
# 동일한 일반 for 루프:
# targets = []
# for c in custom_classes:
# targets.append(c.lower())
# 조건 필터링 — 신뢰도 0.5 이상만
high_conf = [name for name, conf, _ in detections if conf >= 0.5]
# 중첩 — 2D 좌표를 1D로 펼치기
flat = [v for row in matrix for v in row]
# 딕셔너리 컴프리헨션 (같은 원리)
scores = {name: conf for name, conf, _ in detections}
✏️ 직접 해보기nums = [1, 2, 3, 4, 5]; evens = [n for n in nums if n % 2 == 0]; print(evens)
16
딕셔너리 .get()

키가 없어도 안전하게

object_detection.py

`dict[key]` 는 키가 없으면 KeyError가 나요. `dict.get(key, 기본값)` 은 키가 없을 때 기본값을 돌려줘서 프로그램이 멈추지 않아요.

utils/object_detection.py _iter_boxes() names.get()
# object_detection.py — 클래스 ID → 이름 변환
# results[0].names 는 {0: 'left_turn_sign', 1: 'stop_sign', ...} 형태
names = results[0].names
# .get() — 키가 없으면 빈 문자열 반환
name = names.get(cid, '')
# [] 방식 — 키가 없으면 KeyError 발생
# name = names[cid] ← 위험
# 기본값을 다양하게 활용
count = detections.get('stop_sign', 0) # 없으면 0
label = class_map.get(idx, "unknown") # 없으면 unknown
config = settings.get('threshold', 0.5) # 없으면 0.5
✏️ 직접 해보기d = {'a': 1, 'b': 2}; print(d.get('c', 99)) # 99 출력
17
삼항 연산자

if/else를 한 줄로

object_detection.py

`값1 if 조건 else 값2` 형태로 if/else를 한 줄에 써요. 짧고 간단한 조건에서 코드를 깔끔하게 만들어줘요.

utils/object_detection.py detect() 임계값 처리
# object_detection.py — 임계값 인자가 없으면 기본값 사용
def detect(self, frame, confidence_threshold=None):
thr = (confidence_threshold
if confidence_threshold is not None
else self.confidence_threshold)
# 한 줄로 쓸 수도 있음
thr = confidence_threshold if confidence_threshold is not None else self.confidence_threshold
# 다른 예시들
label = 'stop' if speed == 0 else 'go'
sign = '+' if value >= 0 else '-'
status = 'found' if port else 'not found'
✏️ 직접 해보기score = 0.03; print('우수' if score < 0.05 else '보통')
18
NumPy 배열

AI가 다루는 숫자 묶음

object_detection.py

NumPy 배열은 파이썬 리스트보다 훨씬 빠른 수치 계산에 쓰여요. 이미지가 곧 숫자 배열이고, AI 모델의 입출력도 전부 NumPy 배열이에요. `[:n]` 슬라이싱과 `np.mean()` 이 자주 쓰입니다.

utils/object_detection.py latency 계산 + NumPy 기초
import numpy as np
# object_detection.py — 최근 10회 추론 시간 평균
n = min(10, len(self.latency_history))
avg = np.mean(self.latency_history[:n])
# ↑ 슬라이싱: 앞에서 n개
# 이미지 → NumPy 배열
import cv2
frame = cv2.imread('image.jpg') # shape: (H, W, 3)
print(frame.shape) # (480, 640, 3)
print(frame.dtype) # uint8
# 배열 연산 (모든 픽셀에 동시 적용)
normalized = frame / 255.0 # 0~255 → 0.0~1.0
print(np.mean(frame)) # 전체 평균 밝기
✏️ 직접 해보기import numpy as np; a = np.array([10, 20, 30, 40]); print(np.mean(a[:3]))
19
glob (파일 검색)

패턴으로 파일 한꺼번에 찾기

Step 4 - Dataset Building.ipynb

`glob.glob(패턴)` 은 와일드카드(`*`, `**`)로 폴더 안 파일을 한꺼번에 찾아요. 수백 장의 이미지를 일일이 나열할 필요 없이 경로 패턴 하나로 전부 불러와요.

Step 4 - Dataset Building.ipynb 이미지 수집
import glob
# Step 4 Dataset Building — 하위 폴더까지 모든 jpg 찾기
images = glob.glob('data/**/*.jpg', recursive=True)
print(f'총 {len(images)}장 발견') # 예: 총 1024장 발견
# 특정 폴더만
train_imgs = glob.glob('dataset/train/*.jpg')
val_imgs = glob.glob('dataset/val/*.jpg')
# pathlib 방식 (최신 파이썬 스타일)
from pathlib import Path
images = list(Path('data').rglob('*.jpg'))
labels = list(Path('data').rglob('*.txt'))
print(len(images), len(labels))
✏️ 직접 해보기import glob; files = glob.glob('*.py'); print(files)
20
JavaScript 맛보기

AI 개발은 파이썬만이 아니에요

bbox_labeler.html

우리가 만든 라벨링 툴은 파이썬이 아닌 웹(JavaScript + Canvas)으로 만들었어요. AI 프로젝트엔 데이터 처리(Python), UI(JS/HTML), 배포(서버) 등 다양한 언어가 함께 쓰여요.

tools/bbox_labeler.html drawBoxes() 함수
// bbox_labeler.html — canvas 위에 바운딩 박스 그리기 (JavaScript)
function drawBoxes() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
for (const box of current.boxes) {
ctx.strokeStyle = CLASS_COLORS[box.classIdx];
ctx.lineWidth = 2;
ctx.strokeRect(box.x, box.y, box.w, box.h);
// 클래스 이름 라벨
ctx.fillStyle = CLASS_COLORS[box.classIdx];
ctx.fillText(CLASSES[box.classIdx], box.x + 4, box.y + 14);
}
}
// Python과 비교 — 변수 선언만 달라요
// Python: boxes = [] JS: const boxes = []
// Python: for box in boxes JS: for (const box of boxes)
✏️ 직접 해보기브라우저 개발자도구(F12) → Console 탭에서 console.log('hello') 를 입력해보세요.