20장. 교육 콘텐츠 설계와 종합 실습
실습 5 — 수업 자료 묶음 만들기
실습 2의 연습 그림으로 수강생에게 나눠 줄 자료 묶음 out_ch20_class_kit/ 을 코드로 만듭니다. data, code, answers 폴더와 README.md, requirements.txt 를 쓰고, 파일 목록 표를 찍고, 확인 목록을 자동으로 점검한 뒤 ZIP 한 파일로 묶습니다. 실습 2를 먼저 실행해야 합니다.
1단계. 폴더를 만들고 그림 나눠 담기
import os
import shutil
from pathlib import Path
kit = Path("out_ch20_class_kit")
# 다시 돌려도 지난번 파일이 남지 않게 통째로 지우고 새로 만든다. rmtree = remove tree
shutil.rmtree(kit, ignore_errors=True)
for sub in ["data", "code", "answers"]:
(kit / sub).mkdir(parents=True)
src = Path("out_ch20_exercise")
if not src.exists():
raise SystemExit("out_ch20_exercise 가 없습니다. 실습 2를 먼저 실행하세요")
# 문제 그림은 data/ 에, 정답 마스크와 정답표는 answers/ 에 나눠 담는다
for p in sorted(src.iterdir()):
dest = "answers" if ("mask" in p.name or p.suffix == ".csv") else "data"
shutil.copy(p, kit / dest / p.name) # copy: 파일 하나를 복사한다
for sub in ["data", "answers"]:
print(sub, sorted(x.name for x in (kit / sub).iterdir()))data ['easy.png', 'hard.png', 'medium.png']
answers ['answer.csv', 'easy_mask.png', 'hard_mask.png', 'medium_mask.png']2단계. 시작 코드, README, requirements.txt 쓰기
from importlib.metadata import version
# 수강생이 채울 시작 코드. 빈칸은 ____ 로 둔다
starter = '''import cv2
img = cv2.imread("../data/medium.png", cv2.IMREAD_GRAYSCALE)
if img is None:
raise SystemExit("그림을 읽지 못했습니다. code 폴더에서 실행했는지 보세요")
# 1단계: 노이즈를 줄인다(6차시)
blur = ____
# 2단계: Otsu 로 이진화한다(8차시)
_, binary = ____
cv2.imwrite("my_answer.png", binary)
'''
(kit / "code" / "step1_threshold.py").write_text(starter, encoding="utf-8")
readme = """# 동전 세기 이진화 실습
## 학습목표
- 노이즈 낀 그림을 흐리게 한 뒤 Otsu 로 이진화해 정답 마스크와의 IoU 0.9 이상을 얻을 수 있다.
## 준비
- Python 3.12 이상, `python -m pip install -r requirements.txt`
## 실행 순서
1. `code` 폴더로 옮긴 뒤 `step1_threshold.py` 의 빈칸(____)을 채워 실행한다.
2. 만든 `my_answer.png` 를 강사에게 내면 채점 결과와 피드백을 받는다.
## 자료와 라이선스
- `data/` 의 그림은 OpenCV 로 그린 가상의 그림이다(씨앗 2001). 실제 사진이 아니다.
- OpenCV 는 Apache 2.0 라이선스를 따른다.
"""
(kit / "README.md").write_text(readme, encoding="utf-8")
pkgs = ["opencv-python", "numpy"] # 이 실습이 쓰는 패키지만 적는다
(kit / "requirements.txt").write_text("".join(f"{p}=={version(p)}\n" for p in pkgs), encoding="utf-8")
print((kit / "requirements.txt").read_text(encoding="utf-8"))opencv-python==5.0.0.93
numpy==2.5.43단계. 파일 목록 표 만들기
import pandas as pd
rows = []
# rglob("*"): 하위 폴더까지 모든 파일과 폴더를 찾는다(recursive glob)
for p in sorted(kit.rglob("*")):
if p.is_file():
rows.append({"파일": p.relative_to(kit).as_posix(), "KB": round(p.stat().st_size / 1024, 1)})
files = pd.DataFrame(rows)
print(files.to_string(index=False))
print("파일", len(files), "개, 모두", round(float(files["KB"].sum()), 1), "KB") 파일 KB
answers/answer.csv 0.3
answers/easy_mask.png 1.2
answers/hard_mask.png 1.4
answers/medium_mask.png 1.3
code/step1_threshold.py 0.3
data/easy.png 1.4
data/hard.png 69.5
data/medium.png 61.8
README.md 0.6
requirements.txt 0.0
파일 10 개, 모두 137.8 KB4단계. 확인 목록 점검하고 ZIP 으로 묶기
checks = {
"README 가 있다": (kit / "README.md").exists(),
"requirements.txt 에 버전이 적혀 있다": "==" in (kit / "requirements.txt").read_text(encoding="utf-8"),
"문제 그림 세 장이 data 에 있다": len(list((kit / "data").glob("*.png"))) == 3,
"정답 마스크가 data 에 섞이지 않았다": not any("mask" in p.name for p in (kit / "data").iterdir()),
"README 에 라이선스가 적혀 있다": "라이선스" in readme,
"README 에 가상 그림이라고 밝혔다": "가상" in readme,
}
for name, ok in checks.items():
print(f"[{'통과' if ok else '확인'}] {name}")
# make_archive(만들 이름, 형식, 묶을 폴더): 폴더를 ZIP 한 파일로 묶는다. 이름 뒤에 .zip 이 붙는다
zip_path = shutil.make_archive("out_ch20_class_kit", "zip", "out_ch20_class_kit")
print("묶은 파일:", os.path.basename(zip_path), os.path.getsize(zip_path) // 1024, "KB")[통과] README 가 있다
[통과] requirements.txt 에 버전이 적혀 있다
[통과] 문제 그림 세 장이 data 에 있다
[통과] 정답 마스크가 data 에 섞이지 않았다
[통과] README 에 라이선스가 적혀 있다
[통과] README 에 가상 그림이라고 밝혔다
묶은 파일: out_ch20_class_kit.zip 137 KB코드 설명
shutil.rmtree(kit, ignore_errors=True)는 폴더를 통째로 지웁니다. 다시 돌렸을 때 지난번의 파일이 섞여 남지 않게 합니다.ignore_errors=True는 폴더가 없어도 오류를 내지 않습니다.- 정답 마스크와 정답표는
answers/에 따로 담습니다. 수강생에게 나눠 줄 때는answers/를 빼고 묶을 수도 있습니다. Path.write_text(글자, encoding="utf-8")은 글자를 파일로 한 번에 씁니다. README 의#은 마크다운(Markdown, 기호로 꾸미는 글쓰기 방식)의 제목 표시입니다.importlib.metadata.version(이름)은 설치된 패키지의 버전을 돌려줍니다. 손으로 적지 않고 읽어 오므로 수업 PC 의 버전과 어긋나지 않습니다.- 묶음은 파일 10개, 모두 137.8KB 이고 ZIP 은 137KB 입니다. png 는 이미 줄여 저장한 형식이라 ZIP 으로 묶어도 거의 줄지 않습니다. 노이즈가 낀
medium.png(61.8KB)와hard.png(69.5KB)가 대부분을 차지합니다.