space ocr
가이드아티클요금문서
developer

검증 가능한 바운딩 박스를 반환하는 OCR API

대부분의 OCR API는 바운딩 박스를 반환하지만, 좌표계가 제각각인 데다 박스가 알려주는 것은 값의 출처뿐입니다. data.cells[path] 에 담기는 0–1000 정규화 box 와 기울기를 따라가는 quad, 그리고 검토할 값을 지목하는 verified·review 계약을 개발자 관점에서 정리했습니다.

8 분 분량· 2026-08-31

바운딩 박스는 OCR을 검증하는 단서입니다. 단순한 문자열은 모델이 무엇을 읽었다고 여기는지만 알려주지만, 박스는 그것을 페이지의 어디서 읽었는지까지 알려줍니다. 그래서 여러분(혹은 검토자, 혹은 코드)이 그 값을 무작정 믿는 대신 원본과 대조해 볼 수 있습니다. 세금계산서, 경비, KYC, 기록 관리처럼 감사 대상이 되는 곳에 OCR을 붙인다면, "모델이 total: 2,045를 반환했다"는 것만으로는 부족합니다. 그 2,045가 어느 픽셀에서 나왔는지 가리킬 수 있어야 합니다.

다행히 주요 OCR API는 대부분 바운딩 박스를 반환합니다. 문제는 막상 개발에 들어가면 중요해지는 세 가지에서 저마다 다르다는 점입니다. 바로 좌표계, (원시 텍스트뿐 아니라) 정형 필드까지 함께 주는지, 그리고 박스 옆에 붙는 값별 신호가 실제로 무엇인지입니다. 이 가이드는 이 세 가지를 차례로 짚어 보고, 좌표가 명시적인 검토 계약——값마다의 판정과, 확인할 경로 목록——과 함께 돌아오는 OCR API가 실제로 어떤 모습인지 보여 드립니다.

대부분의 OCR API는 박스를 반환합니다 — 차이는 여기에 있습니다

Google Cloud Vision, Tesseract, Amazon Textract, Azure AI Document Intelligence는 모두 텍스트와 함께 기하 정보를 반환합니다. 다만 좌표계, 정형 필드를 주는지 아니면 원시 텍스트와 레이아웃만 주는지, 그리고 값별 숫자가 무엇을 보고하는지에서 갈립니다. 아래 표는 2026년 8월 시점의 각사 공개 문서를 정리한 것입니다. 사양은 계속 바뀌므로, 통합 공수를 가늠하기 전에 최신 문서로 다시 확인하세요.

API좌표계정형 필드값별 신호
Google Cloud VisionboundingPoly 꼭짓점, 원본 이미지의 픽셀 단위 (기능에 따라 normalizedVertices 가 오기도 합니다)텍스트 + 기하 정보 (정형 키-값은 별도 제품인 Google Document AI)단어/심볼별 인식 신뢰도 (0–1)
TesseracthOCR / TSV 박스, 픽셀 단위 (호스팅 API가 아닌 로컬 라이브러리)없음 — 원시 텍스트 + 레이아웃단어별 인식 신뢰도 (0–100)
Amazon TextractBoundingBox, 페이지 너비/높이 기준 0–1 정규화 (+ 역시 0–1인 Polygon)AnalyzeDocument로 폼/표; AnalyzeExpense로 영수증블록별 인식 신뢰도 (%)
Azure Document Intelligence바운딩 폴리곤, 픽셀(이미지) 또는 인치(PDF)사전 구축/맞춤 모델단어별 인식 신뢰도
space-ocr선언한 필드 경로를 키로 하는 0–1000 정규화 box + 기울기를 따라가는 quadfields 로 직접 선언(명세행은 children), 또는 autoFieldsverified 판정 + review.flagged 검토 목록 (근거는 evidence)

두 가지를 눈여겨보세요. 첫째, 좌표 단위는 그대로 옮겨 쓸 수 없습니다. 픽셀 박스는 실제로 판독된 그 이미지에 묶여 있는 반면, 정규화 박스는 크기를 바꿔도 살아남습니다. 둘째, 값별 열이 같은 것을 재고 있지는 않습니다. 인식 신뢰도가 답하는 것은 "엔진이 자기 읽기에 얼마나 확신하는가"이고, 이는 "반환된 값이 애초에 페이지에서 발견되기는 했는가"와는 다른 질문입니다.

✓ Verified

박스가 어떤 형식인지만큼이나, 그 박스를 어떻게 끌어내는지가 중요합니다. space-ocr에서 언어 모델이 반환하는 것은 각 필드의 텍스트와, 어떤 단어 토큰을 사용했는지에 대한 힌트뿐입니다. 박스 자체는 절대 만들어내지 않습니다. 엔진은 그 텍스트를 비전 OCR이 페이지에서 실제로 검출한 심볼과 글자 단위로 대조하고, 그래서 박스는 그 글자들이 발견된 실제 픽셀 위에 놓입니다. 대조가 돌아간 값이라면 그 셀의 evidence에 얼마나 발견되었는지 나타내는 match_ratio가 실리고, 대조할 상대가 없었다면 그 키는 아예 붙지 않습니다. 토큰 힌트는 노이즈가 섞일 수 있어(반복되는 행끼리 토큰이 뒤바뀌기도 합니다) 무작정 믿지 않고 열·행 일관성 검사로 확인합니다. 이것이 모델이 주장하기만 하는 좌표와, 페이지에 비추어 다시 검증한 좌표의 차이입니다.

space-ocr가 값마다 반환하는 것

업무 데이터는 요청한 스키마 그대로 data.values에 담깁니다. 그 값이 어디서 왔고 검사를 통과했는지는 같은 경로를 키로 쓰는 별도 맵 data.cells에 모입니다 — total, 명세행이라면 items[0].price 같은 경로입니다. 셀마다 다음이 들어 있습니다.

  • box — 이미지의 픽셀 크기와 무관하게 0–1000 정규화 격자(0,0 = 좌상단, 1000,1000 = 우하단) 위에 놓인 정수 값의 축 정렬 사각형 { xmin, ymin, xmax, ymax }.
  • quad — 문서의 기울기를 따라가는 방향이 있는 박스를 이루는 순서가 정해진 네 점(좌상단, 우상단, 우하단, 좌하단). 덕분에 비뚤어진 휴대폰 사진도 깔끔하게 박스가 잡힙니다. box와 항상 함께 돌아옵니다.
  • verified — 판정이자 review의 거울입니다. 무언가 섰다면 false, 아무것도 안 섰고 대조가 실제로 돌았다면 true, 아무것도 안 섰지만 대조할 것 자체가 없었다면(행 유니언 등) null입니다.
  • reviewnull 이거나 { reasons } 입니다. 사유는 랭킹 순 배열이고 첫 번째가 대표입니다. 코드에는 text_mismatch, low_ratio, nobox, missing 등이 있습니다.
  • evidence — 판정의 원자료입니다. text_match(문자 대조 그 자체), source(vision_symbol_match, token_id), match_ratio, 그리고 그 좌표에서 OCR이 읽은 글자인 printed_text가 들어갑니다.

같은 경로는 data.review.flagged에도 다시 나옵니다. 이것이 검토 목록으로, 들여다볼 값 하나당 한 건씩 사유와 함께 실립니다. data.image는 실제로 판독된 페이지의 너비와 높이이고, 모든 좌표는 이 면을 기준으로 표현됩니다. (스칼라 type이나 string 필드의 pattern·enum을 선언하면 data.normalized 층도 함께 생기지만, 아래의 string 전용 예시에서는 나타나지 않습니다.)

값 하나: values 와 cells
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "data": {
    "values": { "total": "2,045" },
    "cells": {
      "total": {
        "box": { "xmin": 381, "ymin": 803, "xmax": 500, "ymax": 825 },
        "quad": [
          { "x": 380, "y": 804 }, { "x": 500, "y": 801 },
          { "x": 500, "y": 823 }, { "x": 381, "y": 826 }
        ],
        "verified": true,
        "review": null,
        "evidence": {
          "text_match": true,
          "source": "vision_symbol_match",
          "match_ratio": 1.0,
          "printed_text": "2,045"
        }
      }
    },
    "image": { "width": 1654, "height": 2339 }
  }
}

픽셀이냐 정규화냐? 한 번만 변환해 두면 크기를 바꿔도 깨지지 않습니다

OCR 통합에서 자주 되풀이되는 버그가, 픽셀 좌표가 여러분이 업로드한 바로 그 이미지에 묶여 있다는 점입니다. 저장하려고 크기를 바꾸거나 다시 압축하거나, EXIF 회전 플래그를 놓치면, 겹쳐 그린 박스가 어긋나거나 잘리거나 엉뚱한 텍스트 위에 떨어집니다. 정규화 좌표는 이런 부류의 버그를 통째로 피해 갑니다. 0–1000 박스는 같은 페이지를 어떻게 렌더링하든 그 위에 그대로 들어맞기 때문입니다.

먼저 짚어 둘 것이 하나 있습니다. 기준 면은 보낸 파일이 아니라 data.image입니다. 실제로 판독된 페이지를 가리키며, EXIF 방향은 이미 픽셀에 반영되어 있고 큰 사진은 판독 전에 축소됩니다. 그래서 widthheight가 업로드한 이미지와 뒤바뀌어 돌아올 수 있습니다(4000×3000으로 보내고 3000×4000을 받는 식입니다). data.image를 기준으로 변환하면 계산이 맞아떨어집니다.

표시된 이미지에 박스를 그리려면 한 번만 변환하면 됩니다.

  • SVG 오버레이 — SVG에 viewBox="0 0 1000 1000"을 주고 boxquad를 그대로 그립니다.
  • 절대 위치 divleftPct = xmin / 1000 * 100, topPct = ymin / 1000 * 100, widthPct = (xmax - xmin) / 1000 * 100, heightPct = (ymax - ymin) / 1000 * 100.
  • 다시 픽셀로pixel_x = box.xmin / 1000 * data.image.width, pixel_y = box.ymin / 1000 * data.image.height.

그 페이지에는 EXIF 방향이 이미 적용되어 있으므로, 회전된 휴대폰 사진(orientation 6/8)도 여러분 쪽에서 따로 보정할 필요가 없습니다. 다만 기울기 보정(deskew)은 하지 않습니다. 비뚤어진 사진은 비뚤어진 채로 남고, 그래서 quad는 기울기를 따라가고 box는 그 주위에서 축 정렬을 유지합니다.

필드를 요청하고 좌표를 돌려받기
1
2
3
4
5
6
7
8
9
10
11
curl -s https://api.space-ocr.com/ocr/fields \
  -H "Authorization: Bearer $SPACE_OCR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "https://example.com/receipt.jpg",
    "imageType": "url",
    "fields": [
      { "name": "vendor", "type": "string" },
      { "name": "total",  "type": "string" }
    ]
  }'

신뢰도 점수, 일치 비율, 그리고 판정

꼭 몸에 익혀 둘 만한 구분입니다. 대부분의 OCR API가 문서에 적어 두는 것은 인식 신뢰도입니다. 글꼴의 선명함이나 이미지 품질 같은 요소를 바탕으로, 엔진이 자기 읽기에 얼마나 확신하는지를 나타내는 숫자죠. 쓸모는 있지만, 모델이 자기 숙제를 자기가 채점하는 격입니다. 반면 일치 비율은 바깥의 사실을 잽니다. 모델이 반환한 값의 글자 중, 페이지 단위 OCR이 검출한 심볼 사이에서 실제로 발견된 것이 몇 개인지입니다. 어떤 값은 인식 신뢰도가 넉넉해도 페이지의 무엇과도 들어맞지 않을 수 있습니다.

그렇다고 이 비율만 던져 주고 임계값을 알아서 정하라고 하지는 않습니다. match_ratio는 판정의 근거로 evidence에 들어 있습니다. 임계는 엔진이 직접 적용하고 — 0.85 이상이면 확실한 문자 일치로 봅니다 — 커버리지가 모자라면 그 셀은 review 사유에 low_ratio를 달고 돌아옵니다. 그래서 코드가 거는 게이트는 review != null, 더 낫게는 data.review.flagged를 그대로 순회하는 것입니다. 그러면 비율로는 볼 수 없는 부류까지 닿습니다. required인데 아예 돌아오지 않은 값(missing), 좌표가 붙지 않은 값(nobox), 선언한 패턴이나 범위를 어긴 값 같은 것들입니다.

의외로 여기지만 이상할 것 없는 조합이 하나 있습니다. verified: falseevidence.text_match: true가 같이 있는 경우입니다. 글자로는 페이지와 잘 맞았는데, 여러분이 선언한 규칙 쪽이 잡은 값입니다. 둘 다 이유는 달라도 검토할 값이고, 어느 쪽도 반대편을 보장하지는 않습니다. 두 엔진이 같은 오독에 합의해 버릴 수도 있기 때문입니다.

검증한 뒤, OCR을 다시 돌리지 않고 조회하기

좌표는 데이터가 남아 있을 때 가장 쓸모가 있습니다. POST /upload로 이미지를 시트에 밀어 넣은 뒤, GET /view로 서버 쪽에서 조회하세요. where, sort, select, limit, offset을 써서, 예컨대 total >= 40000인 행을 한꺼번에 가져오는 작업을 OCR 재실행도 추가 비용도 없이 할 수 있습니다. 조건이 걸리는 대상은 그 시트의 컬럼(그리고 name, ocrStatus, createdAt)이며, 각 행은 cells 맵을 그대로 지닌 채 돌아오므로 boxquad도 남아 있습니다. 더 가벼운 응답을 원하면 boxes=0으로 빼면 됩니다. 검증 워크플로를 깊이 다룬 내용은 바운딩 박스로 OCR 검증하기OCR 감사 추적을 참고하세요.

아무 값이나 클릭하면 그 출처 영역이 원본 위에서 환하게 켜집니다. API가 반환하는 바로 그 좌표를 그대로 인터랙티브하게 만든 것입니다.

API에서 검증 가능한 바운딩 박스를 얻는 방법

  1. 필드 요청하기
    imageType을 'url' 또는 'base64'로 지정해 이미지를 /ocr/fields에 POST 하고, 직접 만든 fields 배열을 넘기거나 autoFields를 true로 둡니다. 엔진이 읽는 것은 래스터 이미지입니다.
  2. 좌표 읽기
    data.cells를 필드 경로로 조회합니다. 셀마다 0–1000 격자 위의 box { xmin, ymin, xmax, ymax }, 네 점짜리 quad, 판정인 verified, review, evidence가 들어 있습니다.
  3. 겹쳐 그리거나 변환하기
    '0 0 1000 1000' SVG viewBox로 박스를 그리거나, pixel_x = box.xmin / 1000 * data.image.width로 픽셀로 바꿉니다. data.image는 실제로 판독된 페이지이며 EXIF 회전은 이미 적용되어 있습니다.
  4. 검토 목록 처리하기
    점수를 직접 임계값으로 자르는 대신 data.review.flagged를 순회합니다. 항목마다 경로와 사유가 짝지어 있고, 근거는 match_ratio를 포함해 cells[path].evidence에 들어 있습니다.
  5. 저장하고 조회하기
    /upload로 이미지를 시트에 밀어 넣고 GET /view(where, sort, select)로 조회합니다. 각 행은 box와 quad가 담긴 cells 맵을 그대로 유지하고, OCR 재실행도 추가 비용도 없습니다.
어떤 OCR API가 바운딩 박스를 반환하나요?
Google Cloud Vision, Tesseract, Amazon Textract, Azure AI Document Intelligence는 모두 텍스트와 함께 기하 정보를 반환하며, space-ocr도 그렇습니다. 2026년 8월 시점의 각사 공개 문서를 기준으로 하면 좌표계(Vision과 Tesseract는 픽셀, Azure는 이미지가 픽셀이고 PDF는 인치, Textract는 0–1 정규화, space-ocr는 0–1000 정규화), 정형 필드를 돌려주는지 아니면 원시 텍스트와 레이아웃만 주는지, 그리고 값별 숫자가 무엇을 보고하는지에서 차이가 납니다.
바운딩 박스 좌표는 픽셀인가요, 정규화된 값인가요?
space-ocr는 이미지의 픽셀 크기와 무관하게 0–1000 격자에 정규화된 box와, 페이지의 기울기를 따라가는 네 점짜리 quad를 반환합니다. 픽셀로 바꾸려면 pixel_x = box.xmin / 1000 * data.image.width(y도 동일)를 쓰면 되고, '0 0 1000 1000' SVG viewBox로 곧장 겹쳐 그릴 수도 있습니다. 기준 면은 data.image, 즉 EXIF 방향과 필요한 축소를 거쳐 실제로 판독된 페이지이므로, 그 너비와 높이는 보낸 파일과 다를 수 있습니다.
OCR 신뢰도 점수와 일치 비율의 차이는 무엇인가요?
인식 신뢰도는 엔진이 자기 읽기에 얼마나 확신하는지를 나타냅니다. 반면 match_ratio는 반환된 값의 텍스트가 페이지 단위 OCR이 검출한 심볼 사이에서 실제로 얼마나 발견되었는지를 잽니다. 자기 보고가 아니라 바깥에서 하는 검사인 셈입니다. 이 값은 근거로서 cells[path].evidence에 들어가며, 0.85 이상이면 확실한 문자 일치로 보고 커버리지가 모자라면 엔진이 그 셀의 review 사유에 low_ratio를 세웁니다. 그래서 코드는 숫자가 아니라 review로 게이트를 겁니다.
비뚤어지거나 회전된 사진에서도 방향이 있는 바운딩 박스를 얻을 수 있나요?
네. 모든 셀이 축 정렬 box와 함께, 순서가 정해진 네 점(좌상단, 우상단, 우하단, 좌하단)으로 이루어진 quad를 반환하고, quad는 문서의 기울기를 따라갑니다. 기울기 보정(deskew)은 하지 않으며, 좌표의 기준이 되는 페이지(data.image)에는 EXIF 방향이 이미 적용되어 있으므로 orientation 6 또는 8인 휴대폰 사진도 여러분 쪽에서 따로 보정할 필요가 없습니다.
이 바운딩 박스 OCR이 일본어, 한국어, 중국어에서도 작동하나요?
네. 하나의 엔진이 자동 언어 감지로 한중일(CJK)과 라틴 문자를 모두 처리합니다. 따로 설정할 언어 파라미터는 없습니다. 전각 문자를 포함해 어떤 문자든, 모든 값이 같은 계약으로 돌아옵니다. 선언한 필드 경로를 키로 하는 data.cells의 box, quad, verified, review, evidence입니다.
관련 글