본문으로 건너뛰기

OMR 채점 정확도 검증

2026년 6월 20-21일, OMR 채점 작업의 초점은 속도에서 정확도로 이동했습니다. 질문은 Lumie가 자체 grade-svc를 유지할지, OMRChecker로 교체할지, 아니면 OMRChecker를 참고 구현으로만 사용할지였습니다.

결정은 Lumie 엔진을 유지하고, 반복 가능한 real-scan review loop를 만들며, 빈 행이나 애매한 행은 조용히 선택하지 않고 invalid로 남기도록 decision rule을 강화하는 것이었습니다.

배경

Lumie의 grading worker는 다음을 추출합니다.

  • 학생 전화번호 bubble
  • 45개 객관식 답안
  • 표시가 없거나 애매할 때의 invalid answer

구현은 lumie-worker/services/grading에 있습니다. Lumie의 고정 답안지 layout, registration-marker alignment, OpenCV preprocessing, density-based bubble selection을 사용합니다.

OMRChecker는 template-driven open-source engine이라 평가할 가치가 있었습니다. 하지만 교체하려면 Lumie의 실제 scanned form, 약한 펜 표시, 수정액, 인쇄된 bubble artifact, 전화번호 영역, 손상된 barcode, marker alignment issue를 모두 처리해야 했습니다.

영향

제품 위험은 점수 정확도였습니다. 빠른 recognizer라도 blank 또는 ambiguous row에서 답을 조용히 선택하면 받아들일 수 없습니다.

검증 작업은 두 가지 중요한 위험을 찾았습니다.

위험중요한 이유
선택된 답안의 false positive명확한 실패 신호 없이 점수가 바뀔 수 있습니다
실제 표시의 false negative실제 답을 invalid 또는 blank로 만들 수 있습니다
잘못된 전화번호 digitsheet가 잘못된 학생에게 붙거나 matching이 실패할 수 있습니다
손상된 formthreshold tuning 대상이 아니라 reject 또는 exclude 대상입니다

결론적인 제품 bias는 명확했습니다. 자신 없는 선택을 만들어내는 것보다 invalid를 반환하는 것이 안전합니다.

실패한 것

비교는 여러 실패 모드를 드러냈습니다.

실패 모드관찰된 동작제품 위험
약한 실제 표시OMRChecker가 보이지만 연한 표시를 빈 값으로 반환하는 경우가 있었습니다실제 답안의 false negative
수정액OMRChecker가 인쇄된 잔여 bubble을 여러 표시로 보는 경우가 많았습니다수정 후 답안이 틀리거나 invalid가 됩니다
blank answer false positive기존 Lumie rule이 blank row에서 가장 density가 높은 bubble을 선택하는 경우가 있었습니다조용한 점수 변경
phone-number false positiveblank digit column이 8 또는 9처럼 인쇄 밀도가 높은 digit 쪽으로 기울었습니다잘못된 학생 matching
손상된 barcode 또는 marker 영역일부 scan은 유효한 입력으로 신뢰할 수 없었습니다damage를 tuning으로 숨기지 말고 reject 또는 exclude해야 합니다

핵심 관찰은 blank row에도 언제나 "승자"가 있다는 점입니다. 알고리즘이 가장 density가 높은 bubble만 고르면, 인쇄 잉크, 압축 artifact, 먼지, scan shading이 학생이 표시하지 않은 행에서도 상대적 차이를 만듭니다.

진단

기존 Lumie rule은 너무 절대적이었습니다.

  1. 각 bubble의 density를 계산합니다.
  2. density가 가장 높은 후보를 선택합니다.
  3. 후보가 minimum threshold를 넘으면 accept합니다.

복구한 rule은 margin requirement를 추가합니다.

영역이전 규칙복구한 규칙
객관식 답안minimum보다 높은 최고 density를 accept최고 density가 두 번째 후보보다 충분한 margin으로 앞설 때만 accept
전화번호 digitminimum보다 높은 최고 digit를 accept더 큰 digit-specific margin이 있을 때만 accept

이 margin requirement는 실제 표시와 noise가 있는 blank row의 차이를 포착합니다. 구현은 작게 유지하면서 confidence test를 실제 제품 위험에 더 가깝게 옮겼습니다.

검증 루프

검증 workflow는 결정을 review 가능하게 만들었습니다.

  1. scan corpus를 deduplicate합니다.
  2. 모든 unique sheet에 grading engine을 실행합니다.
  3. 선택된 bubble을 보여주는 overlay를 생성합니다.
  4. local browser tool에서 sheet를 review합니다.
  5. good, bad, unsure 상태를 기록합니다.
  6. 의미 있는 threshold 또는 pipeline 변경 후 다시 실행합니다.

원본 기록의 최종 local review state는 다음입니다.

항목수량
unique reviewed images706
good705
bad1
unsure0
damaged barcode unique images removed2

남은 bad item은 완벽한 성공 주장 뒤에 숨기지 않고 그대로 남겼습니다.

결정

Lumie는 grade-svc를 OMRChecker로 교체하지 않았습니다.

OMRChecker는 참고와 비교 대상으로는 유용했지만, 직접 교체는 한 종류의 오류를 다른 종류의 오류로 바꾸는 선택이었습니다. 더 낮은 위험의 경로는 다음이었습니다.

  1. Lumie fixed-template engine을 유지합니다.
  2. non-Lumie 또는 물리적으로 손상된 form을 reject합니다.
  3. corpus-backed regression check를 추가합니다.
  4. 실제 Lumie scan을 통과하는 아이디어만 빌립니다.
  5. invalid/ambiguous answer를 강제로 선택하지 않고 보존합니다.

검증

정확도 gate는 이후 성능 작업의 전제 조건이 됐습니다.

수준점검결과
unituv run pytest tests/test_omr_density_thresholds.py원본 기록에서 통과
service suitelumie-worker/services/grading에서 uv run pytest원본 기록에서 통과
corpus reviewdeduplicated scan에 대한 local review UI705 good / 706 total
damaged-form handlingdamaged barcode image 제외 또는 rejectdamaged barcode unique image 2개 제거

앞으로 OMR recognition 변경은 golden corpus를 보존하거나 의도적인 behavior change를 명확히 문서화하지 않으면 막아야 합니다.

잘된 점

  • Synthetic confidence가 아니라 실제 Lumie scan으로 평가했습니다.
  • Visual overlay가 CSV diff보다 recognition mistake를 review하기 쉬웠습니다.
  • 남은 bad/unknown case를 명시적으로 남겼습니다.
  • 이후 성능 최적화가 의존할 수 있는 correctness gate를 만들었습니다.

아쉬웠던 점

  • 기존 highest-density rule은 blank row의 가장 강한 artifact를 선택할 수 있었습니다.
  • Phone-number 영역은 일반 답안보다 더 엄격한 margin 처리가 필요했습니다.
  • Damaged input handling은 algorithm tuning과 분리해야 했습니다.
  • 원본 기록의 모든 review artifact가 publish 가능한 위치에 보존되지는 않았습니다.

지속 불변조건

  • Confidence가 약하면 만들어낸 답보다 invalid output을 선호합니다.
  • 실제 scan distribution을 검증하지 않고 recognition engine을 교체하지 않습니다.
  • Score에 영향을 주는 변경에는 golden corpus를 유지합니다.
  • 물리적으로 손상되었거나 Lumie form이 아닌 sheet는 threshold tuning 대상이 아니라 input-validity 문제로 봅니다.

관련 문서