
먼저 답부터 적습니다.
차량 외관 사진인 photoFront.jpg를 입력값으로 넣으면 중고차 성능기록부 도식 판독은 100% 실패합니다.
법령 서식인 별지 제82호 4면도와 실제 차량 외관 사진은 특징점 자체가 아예 달라서 이미지 변형(Warp)과 템플릿 매칭이 성립하지 않기 때문입니다.
제가 개발 중인 Flutter OCR 기반 사이드프로젝트 앱에서 실제 엔카 매물 사진 10장을 파이프라인에 통과시켰을 때 유사도 점수는 0.02에서 0.06에 그쳤습니다.
중고차 성능기록부 점검 앱을 만들다 보면 텍스트 인식에 이어 외판 교체 부위 도식까지 자동으로 읽어내고 싶어집니다.
Google ML Kit(모바일 기기 내 머신러닝 라이브러리) 한국어 모델로 글자를 추출한 뒤, 외판 부위 그림까지 한 번에 파싱하려는 욕심이 생기기 마련입니다.
하지만 사용자가 올리는 사진의 정체를 먼저 분류하지 않고 무작정 투영 변환(Perspective Warp, 기울어진 사각형 문서를 반듯하게 펴는 작업) 단계로 넘기면 파이프라인 전체가 즉시 터집니다.
이번 글에서는 제가 직접 겪은 실패 로그와 함께, 왜 엔카 웹 매물 사진과 법령 양식이 충돌했는지 그 원인과 분기 처리 코드를 정리해 둡니다.
10대 테스트 결과: 템플릿 매칭 점수 0.02~0.06과 전원 실패 로그
사이드프로젝트 앱(com.kiku.car_scan) 작업의 목표는 중고차 구매 전 점검을 돕는 보조 도구를 만드는 것이었습니다.
1단계는 ML Kit를 이용한 글자 추출과 규칙 기반 파싱이었고, 2단계는 외판 상태 도식을 읽어 교체(X)나 판금(W) 표기를 찾는 것이었습니다.
테스트 데이터를 모으기 위해 엔카 웹 매물 상세 페이지에서 내려받은 photoFront.jpg 10장을 테스트 파이프라인에 입력했습니다.
결과는 처참했습니다.
10대 모두 관심 영역(ROI, Region of Interest: 이미지 내에서 분석하려는 특정 영역) 추출 단계에서 warp FAIL 에러를 뱉으며 멈췄습니다.
아래는 파이썬(Python) 프로토타입 환경에서 OpenCV 라이브러리의 matchTemplate(기준 이미지와 입력 이미지를 비교해 유사도를 측정하는 함수)을 돌렸을 때 남은 실제 로그입니다.
[TEST_RUN] 2026-W39 car_scan_diagram_pipeline
[INPUT] photoFront_01.jpg -> match_score: 0.0312 -> FAIL (warp impossible)
[INPUT] photoFront_02.jpg -> match_score: 0.0245 -> FAIL (warp impossible)
[INPUT] photoFront_03.jpg -> match_score: 0.0589 -> FAIL (warp impossible)
[INPUT] photoFront_04.jpg -> match_score: 0.0411 -> FAIL (warp impossible)
[INPUT] photoFront_05.jpg -> match_score: 0.0298 -> FAIL (warp impossible)
[INPUT] photoFront_06.jpg -> match_score: 0.0387 -> FAIL (warp impossible)
[INPUT] photoFront_07.jpg -> match_score: 0.0612 -> FAIL (warp impossible)
[INPUT] photoFront_08.jpg -> match_score: 0.0440 -> FAIL (warp impossible)
[INPUT] photoFront_09.jpg -> match_score: 0.0354 -> FAIL (warp impossible)
[INPUT] photoFront_10.jpg -> match_score: 0.0281 -> FAIL (warp impossible)
[SUMMARY] Processed: 10, Success: 0, Fail: 10, Mean Score: 0.03929
템플릿 매칭 점수는 통상 0.70 이상이어야 올바른 서식의 위치를 잡았다고 봅니다.
그런데 10장 모두 평균 0.039 수준에 머물렀습니다.
코드가 잘못 작성된 줄 알고 코너 검출(Corner Detection) 임계치만 몇 시간 동안 수정했으나 전혀 나아지지 않았습니다.
원인 분석: photoFront와 자동차관리법 별지 제82호의 구조적 괴리
원인을 역추적해 보니 아주 단순하면서도 치명적인 착각이 있었습니다.
엔카 상세 페이지에서 스크랩한 photoFront.jpg 파일은 성능점검기록부 문서 사진이 아니었습니다.
실제 전시 차량이나 점검장 조명 아래 세워둔 진짜 자동차의 정면 외관 사진이었습니다.
자동차에 대한 상태 점검 도식을 판독하려면 기준 양식이 일치해야 합니다.
대한민국 자동차관리법 시행규칙 [별지 제82호 서식] 1쪽 하단에는 '자동차 상태 표시' 도식이 명시되어 있습니다.
이 서식은 보닛, 루프, 프론트 펜더, 도어, 트렁크 리드가 펼쳐진 평면 4면도(전면·후면·좌측면·우측면을 도면화한 흑백 선화) 구조를 가집니다.
반면 제가 다운로드했던 입력 데이터와 엔카 시스템 구조는 완전히 달랐습니다.
- photoFront.jpg는 차량 전면 그릴과 헤드램프, 번호판이 찍힌 3차원 실물 사진입니다.
- 엔카 상세 페이지에서 보이는 성능점검 도식은 단일 종이 스캔본이 아니라 웹 화면용 HTML5 캔버스(Canvas) 위에 앞면과 뒷면 오버레이를 겹쳐 렌더링한 2면도 형태였습니다.
- 법령 별지 제82호의 흑백 4면도 선화를 기준 템플릿으로 삼아 3차원 입체 차량 사진을 대조했으니, 특징점이 하나도 맞지 않아 투영 변환 행렬 자체가 계산되지 않은 것입니다.
문서 처리를 수행하는 컴퓨터 비전 알고리즘 관점에서 보면, A4 용지 서식을 기대한 파이프라인에 자동차 보닛과 헤드라이트가 찍힌 풍경 사진을 던져 넣은 격이었습니다.
해결책: 투영 변환(Warp) 전에 입력 이미지 성격을 먼저 분류하기
문제를 바로잡기 위해 알고리즘 순서를 완전히 뜯어고쳤습니다.
가장 먼저 수행해야 할 작업은 도식을 찾는 일도, 사각형 테두리를 비틀어 펴는(Warp) 일도 아니었습니다.
업로드된 이미지가 '성능기록부 서식 문서'인지, 아니면 '차량 실물 사진'인지 판별하는 사전 검증 레이어를 최상단에 배치했습니다.
문서 이미지와 실물 차량 사진은 히스토그램(색상 밝기 분포)과 엣지(윤곽선) 밀도에서 뚜렷한 차이를 보입니다.
A4 규격의 성능기록부는 흰색 배경 픽셀 비율이 높고 검은색 활자 엣지가 자잘하게 밀집해 있습니다.
반면 차량 실물 사진은 배경의 바닥면, 조명 그라데이션, 금속 반사광으로 인해 밝기 분포가 완만하고 굵은 윤곽선이 분산되어 나타납니다.
Flutter 앱과 연동할 사전 처리 파이프라인을 아래와 같은 파이썬 로직으로 재설계했습니다.
import cv2
import numpy as np
def validate_input_is_document(image_path: str) -> bool:
image = cv2.imread(image_path)
if image is None:
return False
# 1. 흑백 변환 및 이진화 처리
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)
# 2. 문서 특성 확인: 흰색 배경 픽셀 비율 계산
white_pixel_ratio = np.sum(thresh == 255) / (thresh.shape[0] * thresh.shape[1])
# 실물 차량 사진(photoFront 등)은 흰색 배경 비율이 통상 0.50 미만
if white_pixel_ratio < 0.50:
return False
# 3. 엣지 밀도 측정 (Canny 윤곽선 추출)
edges = cv2.Canny(gray, 50, 150)
edge_density = np.sum(edges > 0) / (edges.shape[0] * edges.shape[1])
# 활자가 인쇄된 문서는 엣지 밀도가 특정 범위 내에 정밀하게 분포함
if edge_density < 0.015 or edge_density > 0.15:
return False
return True
def process_inspection_diagram(image_path: str):
# 전처리: 문서 여부 검증
if not validate_input_is_document(image_path):
# 차 사진이면 도식 판독을 강제하지 않고 조기 종료
return {
"status": "SKIP_DIAGRAM",
"message": "입력 이미지가 별지 제82호 서식이 아닙니다. 실물 차량 사진으로 판단되어 도식 분석을 건너뜁니다."
}
# 문서가 맞을 때만 실행되는 ROI 추출 및 Warp 파이프라인
# ... 후속 템플릿 매칭 로직 수행 ...
return {"status": "SUCCESS"}
이 분류기를 파이프라인 첫 단계에 끼워 넣은 덕분에, 실물 차량 사진이 들어왔을 때 불필요하게 10초 이상 연산을 붙잡고 있다가 메모리 오류를 일으키는 현상을 완전히 막았습니다.
1인 개발자의 원칙: 판독은 참고용이며 단정하지 않는다
이 실험을 거치며 앱의 정책과 문구 방향도 다시 정리했습니다.
사이드프로젝트 앱에서 제공하는 분석은 사용자가 직접 확인할 수 있도록 보조하는 지표일 뿐입니다.
절대로 특정 매물을 "무사고 차량"이라고 단정하거나 "사고차"라고 확정 지어선 안 된다는 점을 명확히 했습니다.
특히 "검증된 매물" 같은 과장된 표현은 앱 내 UI와 안내 문구에서 완전히 덜어냈습니다.
Flutter 클라이언트 단에서도 파싱 파이프라인 결과에 대한 카피를 엄격하게 제한했습니다.
- ML Kit를 통해 읽어낸 텍스트와 도식 체크 표시는 '참고용'으로만 띄웁니다.
- 입력된 사진이 법령 양식(별지 제82호 4면도)과 일치하지 않거나 유사도 점수가 낮으면 강제로 결과를 만들어내지 않고 "원본 성능상태점검기록부를 직접 확인하세요"라는 문구를 노출합니다.
- 실사진 파이프라인이 안정적으로 검증을 통과하기 전까지는 구글 플레이 스토어 설명란이나 스크린샷에도 해당 도식 판독 기능을 출시된 것처럼 홍보하지 않기로 결정했습니다.
기능을 억지로 출시하기보다 입력 실패를 정확히 감지하고 빠져나오는 에러 핸들링이 실제 사용자 경험을 지키는 길입니다.
입력 실패 방지를 위한 4단계 점검 체크리스트
유사한 문서 OCR이나 이미지 템플릿 매칭 기능을 개발하고 계신다면, 코드 작성 전 아래 순서로 데이터 정합성을 먼저 확인하시기 바랍니다.
- 입력 데이터의 출처 파일 규격 확인
- 크롤링하거나 전달받은 파일명이 photoFront.jpg, photoRear.jpg처럼 외관 사진 식별자인지 검사합니다.
- 실제 스캔본 PDF나 카메라 촬영본인지 확인하고, 차량 사진이면 비전 파이프라인 진입을 차단합니다.
- 서식 표준 규격의 일치 여부 대조
- 기준 양식이 대한민국 자동차관리법 [별지 제82호 서식] 4면도인지 확인합니다.
- 서비스 플랫폼(엔카 등) 전용으로 변형된 웹 2면도 오버레이 방식인지 비교합니다. 서로 다른 도식은 매칭이 불가능합니다.
- ROI 투영 변환(Warp) 전 예외 처리 분기
- 이미지 전체의 밝기 히스토그램과 윤곽선 밀도를 먼저 계산해 서식 문서인지 판별합니다.
- cv2.findContours 결과에서 뚜렷한 4개 코너(사이드 사각형)를 찾지 못하면 즉시 프로세스를 안전하게 종료시킵니다.
- 사용자 안내 문구의 안정성 확보
- 판독 결과는 언제나 보조 참고용 데이터로만 표기합니다.
- 서식 인식 신뢰도가 0.70 미만인 경우 모호한 결과를 추측해 보여주지 말고 반드시 "원본 서류 확인 필요" 알림을 출력합니다.
마무리
입력이 잘못되었는데 내부 알고리즘 튜닝에만 매달리면 귀중한 개발 시간만 버리게 됩니다.
이번 실패의 본질은 OpenCV 코드의 오류가 아니라, 법령 별지 제82호 4면도 서식과 엔카의 외관 실물 사진을 같은 선상에 두고 돌린 잘못된 설계에 있었습니다.
차량 외관 사진은 글자나 도식을 읽는 대상이 아니므로, 조기에 걸러내어 도식 분석을 건너뛰는 방어 로직이 반드시 선행되어야 합니다.
스토어 리스팅 문구를 수정하기 전에 실제 파이프라인이 예외 상황을 얼마나 깔끔하게 처리하는지 증명하는 것이 먼저입니다.
※ 본 글에서 다룬 성능기록부 도식 파싱 및 OCR 기법은 보조 확인용 기술 실험 기록이며, 실제 차량의 사고 여부 판정이나 매물 상태는 공인된 점검장의 원본 성능상태점검기록부 실물과 전문가 육안 진단을 통해 직접 확인해야 합니다.
'플러터·앱개발' 카테고리의 다른 글
| 사이드프로젝트 앱에서 Flutter 로그인을 첫 화면 대신 저장 버튼에 배치한 이유는 무엇일까요? (0) | 2026.10.12 |
|---|---|
| 구글플레이 스토어 등록할 때 스크린샷과 설명 A/B 테스트하면 유입이 정말 늘어날까요? (0) | 2026.09.23 |
| 지하철에서 멈추지 않는 사이드프로젝트 앱: Flutter 로컬 캐시로 오프라인 UX 개선하기 (0) | 2026.09.18 |
| Flutter 앱 수익화, GMA Next-Gen SDK로 애드몹 SDK 업그레이드: minSdk 24와 구글플레이 AAB 배포 전략 (0) | 2026.09.06 |
| 사이드프로젝트 앱, 애드몹(AdMob)으로 앱 수익화 가능할까요? 구글플레이 출시 후 관찰한 현실 (0) | 2026.09.04 |