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

스캔에서, 사람이 읽는 순서 그대로의 텍스트 꺼내기

POST /ocr/text로 읽기순서 원문을 추출하고, 필요할 때 내용 전용 blocks와 path 기반 원본 좌표, 명시적인 검토 목록을 받는 방법입니다.

7 분 분량· 2026-08-31

"그냥 텍스트만 주세요"는 쉬운 OCR 요청처럼 들리지만 검색 색인을 조용히 망가뜨리기 좋은 요구이기도 합니다. 비전 OCR이 올바른 단어를 찾아도 검출 순서로 내보내면 왼쪽 단 첫 줄, 오른쪽 단 첫 줄, 다시 왼쪽 단이 섞입니다. 오류는 없는데 문서는 더 이상 사람이 읽는 문서가 아닙니다.

POST /ocr/text는 이 문제를 필드 추출이나 마크다운 변환과 분리합니다. 기본값인 useLlm: true에서는 블록을 사람이 읽는 순서로 정렬하고 줄바꿈으로 끊긴 행을 다시 잇습니다. 글자와 원본 위치는 비전 관측과 대조하므로 언어 모델의 전사를 무조건 정답으로 취급하지 않습니다.

요청을 바꾸는 두 스위치

useLlm의 기본값은 true입니다. 다단 조판, 사이드바, 기울어진 스캔, 사람이 읽을 텍스트라면 그대로 두세요. false로 설정하면 LLM 단계를 건너뛴 비전 전용 전사가 raw OCR 순서로 돌아옵니다. 이 경로에서도 같은 형식의 문서 검증 객체는 제공됩니다.

includeBlocks의 기본값은 false입니다. 전문만 필요하면 data.values.text를 쓰면 됩니다. 직접 청킹하거나 원본을 하이라이트하거나 검토 UI를 만들 때 켜세요. 그러면 내용만 담은 data.values.blocks, data.cells["blocks[0]"] 같은 메타데이터, data.review.flagged의 블록 경로가 추가됩니다.

한 번 호출하기

아래 예제는 검토 대상을 원본까지 추적할 수 있도록 includeBlocks를 켰습니다. 인증과 전체 파라미터는 API 문서에서 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
curl -X POST https://api.space-ocr.com/ocr/text \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "https://example.com/page.jpg",
    "imageType": "url",
    "useLlm": true,
    "includeBlocks": true
  }'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
{
  "status": "success",
  "data": {
    "values": {
      "text": "사쿠라상사 주식회사\n청구서\n합계 1,451원",
      "blocks": [
        {
          "text": "사쿠라상사 주식회사"
        },
        {
          "text": "청구서\n합계 1,451원"
        }
      ]
    },
    "cells": {
      "blocks[0]": {
        "box": {
          "xmin": 60,
          "ymin": 48,
          "xmax": 470,
          "ymax": 92
        },
        "quad": [
          {
            "x": 60,
            "y": 48
          },
          {
            "x": 470,
            "y": 48
          },
          {
            "x": 470,
            "y": 92
          },
          {
            "x": 60,
            "y": 92
          }
        ],
        "verified": true,
        "review": null,
        "evidence": {
          "text_match": true,
          "source": "token_id"
        }
      },
      "blocks[1]": {
        "box": {
          "xmin": 58,
          "ymin": 190,
          "xmax": 510,
          "ymax": 274
        },
        "quad": [
          {
            "x": 58,
            "y": 190
          },
          {
            "x": 510,
            "y": 190
          },
          {
            "x": 510,
            "y": 274
          },
          {
            "x": 58,
            "y": 274
          }
        ],
        "verified": false,
        "review": {
          "reasons": [
            "text_mismatch"
          ]
        },
        "evidence": {
          "text_match": false,
          "source": "char_matcher_fallback"
        }
      }
    },
    "review": {
      "unit": "block",
      "total": 2,
      "boxed": 2,
      "verified": 1,
      "flagged": [
        {
          "path": "blocks[1]",
          "reasons": [
            "text_mismatch"
          ]
        }
      ],
      "by_reason": {
        "text_mismatch": 1
      },
      "coverage": {
        "recovered_blocks": 0,
        "vision_tokens": 8,
        "tokens_claimed": 8,
        "token_coverage": 1
      }
    },
    "image": {
      "width": 1654,
      "height": 2339
    },
    "source": "llm"
  }
}

역할에 따라 응답 읽기

  • data.values.text는 문서 전문입니다. blocks를 켜면 같은 내용이 { text } 단위의 data.values.blocks에도 담기고, 내용에 좌표가 섞이지 않습니다.
  • data.cells[path]는 원본 근거를 담은 사이드카입니다. boxquad는 0~1000 페이지 좌표이며 data.image.widthheight로 처리된 이미지 픽셀에 투영합니다. verified는 그 위치의 비전 텍스트와 정규화 후 일치했는지를 나타내는 판정이지 정확도 퍼센트가 아닙니다.
  • data.review.flagged는 실제 검토 작업 목록입니다. 각 pathdata.cells[path]를 바로 찾고 review.reasons에서 검토 이유를 읽습니다.
  • data.source는 읽기순서 패스가 전사했으면 llm, 비전 경로면 vision입니다.

색인기는 values.text를, 검토 화면은 reviewcells를 소비하도록 책임을 나눌 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { data } = await response.json();

indexDocument(data.values.text);

for (const flag of data.review.flagged) {
  const cell = data.cells?.[flag.path];
  queueForReview({
    path: flag.path,
    reasons: flag.reasons,
    box: cell?.box,
    quad: cell?.quad,
    image: data.image,
  });
}
✓ Verified

폴백은 숨겨지지 않습니다. LLM 읽기순서 패스가 실패하면 OCR 오류로 끝내지 않고 비전 전사를 돌려줍니다. 이때 data.source"vision"이고 이유는 data.warning에 실립니다. 어떤 블록도 가져가지 않은 비전 token은 cells[path].evidence.source: "unclaimed_tokens"인 회수 블록으로 추가될 수 있어, 누락된 문단이 조용히 버려지지 않습니다.

원문 텍스트인가, 마크다운인가

제목과 표를 별도 타입으로 보존할 필요가 없는 전문 검색, 임베딩, 문서 비교, 접근성 피드라면 원문 텍스트가 맞습니다. 문서 구조가 필요하면 POST /ocr/markdown을 쓰고 이미지를 마크다운으로 바꾸는 가이드를 참고하세요. 좌표와 검토 메타데이터는 OCR 원본 좌표 가이드에서 더 자세히 다룹니다.

  1. 읽기순서 경로 선택하기
    이미지를 POST /ocr/text로 보냅니다. 읽기순서가 중요하면 기본 useLlm:true를 유지하고, raw 비전 순서로 충분할 때만 false를 씁니다.
  2. 원본 근거가 필요할 때 blocks 요청하기
    includeBlocks:true로 values.blocks, path 기반 cells, 블록 단위 review 메타데이터를 받습니다. 전문만 필요하면 기본값을 유지합니다.
  3. 명시적인 검토 목록 처리하기
    data.review.flagged를 순회하고 data.cells[flag.path]의 box 또는 quad를 data.image가 설명하는 프레임 위에 그립니다.
  4. 어떤 전사 경로가 실행됐는지 기록하기
    텍스트와 data.source를 함께 저장하고, 자동 폴백으로 vision이 된 경우 data.warning을 표시합니다.
/ocr/text는 기본으로 읽기순서를 바로잡나요?
네. useLlm 기본값은 true이며 블록을 재정렬하고 줄바꿈으로 끊긴 행을 다시 잇습니다. raw OCR 순서가 더 나을 때만 false로 설정하세요.
blocks를 꼭 요청해야 하나요?
아니요. includeBlocks 기본값은 false라 단순 색인에는 data.values.text만 쓰면 됩니다. blocks[n] 좌표와 블록별 검토가 필요할 때 켜세요.
verified:true면 블록이 반드시 정확한가요?
아닙니다. 연결된 위치의 비전 텍스트와 정규화 후 일치했다는 뜻이며 정확도 점수가 아닙니다. 두 판독이 같은 오독에 동의할 가능성도 남습니다.
읽기순서 모델이 실패하면 어떻게 되나요?
비전 전사로 폴백하고 data.source를 vision으로, 이유를 data.warning으로 알립니다. 문서 단위 data.review도 계속 반환됩니다.
관련 글