이미지에서 표 데이터를 추출하여 CSV로 변환하기
표, 주문서, 배송 전표 사진을 깔끔한 CSV 파일로 변환하세요. space-ocr이 명세행을 어떻게 읽고, 행 단위 검증이 확인이 필요한 값을 어떻게 드러내는지 살펴봅니다.
스캔한 표의 데이터를 스프레드시트로 옮겨 적는 일은 번거롭기 짝이 없습니다. 배송 전표나 구매 주문서처럼 여러 항목이 나열된 선명한 이미지가 있지만, 결국은 픽셀 덩어리에 불과하죠. 보통은 각 항목, 수량, 가격을 하나씩 보면서 새로운 행에 일일이 입력하는 지루한 수작업을 거쳐야 합니다. 이 과정은 시간이 오래 걸릴 뿐만 아니라, 오타 하나만으로도 전체 데이터가 엉망이 될 수 있습니다.

더 나은 방법은 표의 구조를 스키마로 정의하는 것입니다. 텍스트 덩어리를 통째로 가져오는 대신 필요한 열을 선언합니다. 반복되는 명세행 구간은 array 타입 필드로 두고 그 하위 요소(children)에 열을 나열하며, 항상 숫자가 들어오는 열에는 타입을 선언합니다.
{
"name": "items",
"type": "array",
"children": [
{ "name": "name", "type": "string" },
{ "name": "qty", "type": "integer" },
{ "name": "price", "type": "number" },
{ "name": "amount", "type": "number" }
]
}행 수는 지정하지 않습니다. 몇 행이 돌아올지는 페이지가 정합니다. 각 행은 첨자 경로(values.items[0], values.items[1] …)로 돌아오고, 같은 경로가 값별 좌표·검증 맵의 키 cells["items[0].price"] 가 됩니다. 타입을 선언해도 추출되는 값은 달라지지 않습니다. 타입이 모델에 전달되지 않기 때문입니다. 선언한 타입이 더하는 것은 values 옆에 놓이는 결정론적 두 번째 층 data.normalized 입니다.
이 방식은 동일한 값이 반복되는 빽빽한 표에서도 효과적입니다. 시스템은 대규모 언어 모델(LLM)을 사용해 추출할 텍스트를 먼저 제안하지만, 거기서 멈추지 않습니다. 품명 "刻みたくあん" 이나 단가 "580" 처럼 각 값에 대해 교차 검증을 수행합니다. 엔진은 언어 모델의 읽기를 문서의 열 구조와 대조하고, 페이지에서 최초로 인식된 OCR 기호와 문자 하나하나를 맞춰 봅니다. 값이 옆 행으로 밀리면 그 좌표의 문자는 대개 일치하지 않게 되고, 해당 필드는 그대로 통과하는 대신 검토 대상으로 기록됩니다.
"data": {
"values": {
"items": [
{ "name": "刻みたくあん", "qty": "3",
"price": "580", "amount": "1,740" }
]
},
"cells": {
"items[0]": {
"box": { "xmin": 263, "ymin": 460,
"xmax": 738, "ymax": 523 },
"quad": [ { "x": 263, "y": 460 }, { "x": 738, "y": 460 },
{ "x": 738, "y": 523 }, { "x": 263, "y": 523 } ],
"verified": null, "review": null
},
"items[0].name": {
"box": {…}, "quad": […],
"verified": true, "review": null,
"evidence": { "text_match": true, "match_ratio": 1.0 }
},
"items[0].qty": { "box": {…}, "quad": […],
"verified": true, "review": null },
"items[0].price": {
"box": { "xmin": 693, "ymin": 460,
"xmax": 738, "ymax": 488 },
"quad": […],
"verified": false,
"review": { "reasons": ["text_mismatch"] },
"evidence": { "text_match": false, "match_ratio": 0.62 }
}
},
"review": {
"unit": "field",
"flagged": [
{ "path": "items[0].price", "reasons": ["text_mismatch"] }
]
},
"normalized": {
"items": [ { "qty": 3, "price": 580, "amount": 1740 } ]
}
}이 검토 목록이 곧 작업 대기열입니다. data.review.flagged 가 정확한 경로와 사유를 지목하고, cells["items[0].price"] 에는 대조에 사용한 좌표가 들어 있습니다. 다만 전수 포착 장치는 아닙니다. 옆 행에 같은 숫자가 인쇄돼 있으면 박스가 어느 쪽에 붙어도 문자는 일치하고, 두 엔진이 같은 오독에 합의하면 맞대어 볼 상대가 없습니다. 그래서 문자 대조로는 보이지 않는 행·열 착오는 선언 가능한 규칙으로 넘겨 두는 편이 낫습니다. 코드 열에는 pattern, 마스터가 이미 보유한 값에는 enum, 타당한 범위에는 min 과 max 를 겁니다. 위반은 모두 같은 review.flagged 에 실립니다.
마지막 층은 직접 하는 검산이고, 이는 하류의 몫입니다. qty 를 integer 로, 금액 열을 number 로 선언해 두었으므로 data.normalized 에는 해석된 수치가 놓입니다("1,740" 은 1740). 행 검산은 곱셈 하나면 충분합니다.
const { values, normalized, review } = data;
const rows = values.items.map((item, i) => {
const n = normalized.items[i];
const flagged = review.flagged.some((f) =>
f.path.startsWith(`items[${i}]`)
);
return {
name: item.name,
qty: n.qty,
price: n.price,
amount: n.amount,
// 사람이 볼 행: 검토 플래그가 섰거나 검산이 안 맞는 경우
check:
flagged || n.qty == null || n.qty * n.price !== n.amount,
};
});해석되지 않은 리프는 normalized 에서 null 이 되고, 실패 종류는 cells[path].normalized.error 에, 검토 목록에는 type_mismatch 로 실립니다. 계산에 쓰는 값은 normalized 에서 가져오고, 표시할 문자는 values 를 그대로 두십시오. 좌표와 검증이 붙어 있는 쪽은 values 입니다.
각 값은 그 값이 실려 있던 페이지와 맞대어 봅니다. 모델의 읽기를 그 자리에서 실제로 감지된 OCR 기호와 문자 단위로 대조하고, 얼마나 일치했는지가 evidence.match_ratio 에 남습니다. 0.85 이상이면 신뢰할 수 있는 일치입니다. 판정 자체는 verified 이고 이것은 review 의 거울입니다. 무엇이든 플래그가 서면 false, 대조가 돌고 아무것도 서지 않으면 true, 대조할 대상이 없으면(행 union 박스 등) null 입니다. 좌표는 일치한 기호에서 box 와 quad 로 도출되고 data.image 를 기준으로 0–1000 범위로 정규화됩니다. 이는 값이 어디서 왔는지에 대한 증거이지 원하는 값이라는 증명은 아니며, 확인되지 않은 값은 review.flagged 에 목록으로 남습니다.
이용 요금은 처리된 이미지당 100원입니다. 계정에는 매월 100건의 무료 스캔이 제공됩니다. 어떤 이유로든 추출에 실패하면 비용이 청구되지 않습니다.
- 시트 스키마 정의하기새 시트를 만들고 필요한 열을 정의합니다. 반복되는 항목의 경우 '배열(array)' 타입을 사용하고 상품명, 수량, 가격 등에 대한 하위 열을 추가하세요.
- 이미지 업로드하기표 이미지를 시트로 드래그 앤 드롭하거나 API를 통해 업로드합니다.
- 추출된 데이터 검토하기이미지는 정의한 스키마에 따라 처리됩니다. 표의 각 항목은 시트에서 구조화된 행으로 나타납니다.
- 필요시 수정하기특정 셀을 클릭하면 이미지의 해당 영역을 바로 확인할 수 있습니다. 그리드에서 직접 값을 수정하세요.
- CSV로 내보내기'내보내기' 버튼을 클릭하고 CSV를 선택하세요. 모든 항목을 포함한 표 데이터가 깔끔한 구조의 파일로 다운로드됩니다.