Chrome Built-in AI — LanguageModel API로 이미지에서 텍스트 추출하기
브라우저에서 이미지 속 글자를 읽는다
Tesseract.js나 Google Vision API 없이, 브라우저 자체 AI로 이미지에서 텍스트를 뽑아낼 수 있다. Chrome의 LanguageModel API는 이미지를 입력으로 받아 텍스트를 출력할 수 있는 멀티모달 API다.
외부 서버가 없다. API 키도 없다. Chrome 브라우저와 플래그 설정 하나로 동작한다.
[💡 잠깐! 이 용어는?]
LanguageModel API (Prompt API): window.LanguageModel로 접근하는 Chrome Built-in AI API. Gemini Nano를 직접 호출해 텍스트 생성, 분류, 추출 작업을 수행한다. 이미지 입력도 지원해 멀티모달 추론이 가능하다.
준비: Chrome 플래그 활성화
LanguageModel API는 아직 실험 단계다. Chrome 주소창에서 아래 플래그를 활성화해야 한다.
chrome://flags/#prompt-api-for-gemini-nano
Enabled로 설정 후 Chrome 재시작. 이미지 입력을 쓰려면 추가로:
chrome://flags/#optimization-guide-on-device-model
도 Enabled로 설정한다. 모델이 로컬에 없으면 첫 실행 시 자동 다운로드된다.
기본 세션 생성
이미지를 입력으로 사용하려면 세션 생성 시 expectedInputs에 image 타입을 명시해야 한다.
async function createOcrSession() {
const available = await LanguageModel.availability({
expectedInputs: [{ type: 'image' }],
});
if (available === 'unavailable') {
throw new Error('이미지 지원 LanguageModel을 사용할 수 없습니다.');
}
const session = await LanguageModel.create({
expectedInputs: [{ type: 'image' }],
initialInputs: [
{
role: 'system',
content:
'당신은 이미지에서 텍스트를 추출하는 OCR 전문가입니다. 이미지에 보이는 텍스트를 정확하게 읽어주세요.',
},
],
});
return session;
}initialInputs의 system 메시지가 중요하다. "OCR 전문가"라는 역할을 명확히 주면 모델이 이미지 묘사보다 텍스트 추출에 집중한다.
텍스트만 추출하는 방법
가장 단순한 OCR이다. 이미지를 주고 텍스트만 받아온다.
async function extractText(imageFile) {
const session = await createOcrSession();
const result = await session.prompt([
{
role: 'user',
content: [
{
type: 'image',
value: imageFile,
},
{
type: 'text',
value: '이 이미지에서 텍스트를 전부 추출해주세요. 텍스트만 출력하고 다른 설명은 필요 없습니다.',
},
],
},
]);
return result;
}session.prompt()의 content 배열에 이미지(type: 'image')와 텍스트 지시(type: 'text')를 함께 넣는다. 이미지 파일은 File 객체나 Blob을 그대로 넘기면 된다.
JSON 스키마로 구조화된 응답 받기
텍스트만 추출하는 것 외에, **바운딩 박스(위치 정보)**를 함께 받을 수도 있다. responseConstraint에 JSON 스키마를 넘기면 구조화된 응답이 나온다.
const ocrSchema = {
type: 'object',
properties: {
text_blocks: {
type: 'array',
items: {
type: 'object',
properties: {
text: { type: 'string' },
bounding_box: {
type: 'object',
properties: {
x: { type: 'number' },
y: { type: 'number' },
width: { type: 'number' },
height: { type: 'number' },
},
required: ['x', 'y', 'width', 'height'],
},
},
required: ['text', 'bounding_box'],
},
},
},
required: ['text_blocks'],
};
async function extractTextWithBounds(imageFile) {
const session = await createOcrSession();
const result = await session.prompt(
[
{
role: 'user',
content: [
{ type: 'image', value: imageFile },
{
type: 'text',
value: '이미지에서 텍스트와 각 텍스트의 위치(바운딩 박스)를 JSON으로 반환해주세요.',
},
],
},
],
{
responseConstraint: ocrSchema,
}
);
return JSON.parse(result);
}[💡 잠깐! 이 용어는?] responseConstraint: LanguageModel API의 출력 형식을 JSON 스키마로 제약하는 옵션. 이 옵션이 있으면 모델이 반드시 지정된 스키마에 맞는 JSON을 반환한다. 자유 텍스트 대신 파싱 가능한 구조화된 데이터를 받을 수 있다.
실제 테스트 결과
두 가지 방식을 비교한 테스트 결과다.
| 항목 | 텍스트만 추출 | JSON + 바운딩 박스 |
|---|---|---|
| 텍스트 정확도 | 높음 | 중간 |
| 위치 정보 | 없음 | 부정확 (근사값) |
| 처리 속도 | 빠름 | 느림 |
| 간판·표지판 | 잘 동작 | 동작하나 위치 오차 큼 |
| 밀집된 텍스트 | 누락 발생 | 더 많은 누락 |
바운딩 박스 테스트에서 중요한 점이 있다. 텍스트 자체는 잘 읽히는데, 좌표값이 실제 이미지 좌표와 다르다. Gemini Nano가 공간 추론에 최적화된 모델이 아니기 때문이다.
비유하면 책을 읽을 수 있는 사람에게 "이 페이지의 3번째 단어가 몇 번째 줄 몇 번째 칸에 있어?"라고 물어보는 것과 같다. 단어 자체는 읽지만 정확한 픽셀 좌표를 계산하는 건 다른 능력이다.
실용적으로는 텍스트만 추출하는 방식이 더 신뢰할 수 있다.
적합한 케이스
잘 동작하는 경우:
- 간판, 표지판, 현수막처럼 큰 글씨
- 스크린샷에 있는 UI 텍스트
- 명함, 영수증처럼 구조가 단순한 문서
- 단일 언어로 작성된 이미지
동작이 불안정한 경우:
- 손글씨
- 텍스트가 배경에 묻히는 경우 (저콘트라스트)
- 이미지 내 표·그래프 속 숫자
- 여러 언어가 섞인 이미지
브라우저 지원 현황
| 브라우저 | 지원 여부 | 비고 |
|---|---|---|
| Chrome 127+ | ✅ (플래그 필요) | 실험적 기능 |
| Chrome Stable | 🔜 | 안정화 진행 중 |
| Firefox | ❌ | 미지원 |
| Safari | ❌ | 미지원 |
| Edge | 🔜 | Chromium 기반, 향후 지원 예정 |
현재는 Chrome 개발자 도구를 쓰는 개발자나 프로토타이핑 단계에 적합하다. 프로덕션 서비스에 쓰려면 플래그 없이도 동작하는 stable 릴리즈를 기다려야 한다.
정리
- Chrome
LanguageModel.create({ expectedInputs: [{ type: 'image' }] })로 이미지 입력이 가능한 세션을 만들 수 있다. session.prompt()에 이미지 파일과 텍스트 지시를 함께 넣으면 OCR이 동작한다.responseConstraint로 JSON 스키마를 지정하면 구조화된 응답을 받을 수 있다.- 텍스트 추출 자체는 간판·표지판 수준에서 잘 동작한다. 바운딩 박스 좌표는 부정확하다.
- 현재는 Chrome 플래그 활성화가 필요한 실험 기능이다.
서버 없이 브라우저에서만 동작하는 OCR이 필요한 상황 — 내부 도구, 오프라인 앱, 프라이버시가 중요한 서비스 — 에서 충분히 실험해볼 만하다.
참고:
- Raymond Camden — Testing OCR with Chrome Built-in AI: https://www.raymondcamden.com/2026/04/11/testing-ocr-with-chrome-built-in-ai
- Chrome AI Built-in APIs: https://developer.chrome.com/docs/ai/built-in
- LanguageModel API 명세: https://github.com/webmachinelearning/writing-assistance-apis
관심 있을 만한 포스트
Chrome Built-in AI — 브라우저에서 Word·Excel·PPT·PDF를 AI로 요약하기
officeParser로 오피스 문서를 텍스트로 파싱하고, Chrome Summary API로 브라우저 내에서 AI 요약을 생성하는 방법을 다룬다.
Bun v1.3.12 — 브라우저 자동화와 인프로세스 Cron이 기본 내장됐다
Playwright 없이 브라우저를 제어하는 Bun.WebView, 그리고 서버 재시작 없이 작업을 스케줄링하는 Bun.cron의 기술적 구조.
Zustand 소프트 삭제 — enumerable:false로 컴포넌트 크래시 없이 처리하기
JavaScript property descriptor의 enumerable 플래그를 활용해 삭제된 엔티티를 투명하게 처리하는 Zustand 패턴을 소개한다.
JavaScript 이미지 프리로딩 — 5가지 방법 비교
new Image, link preload, hidden div, Cache API, fetch — 각 프리로딩 방식의 장단점과 상황별 선택 기준을 정리한다.
JavaScript 물리 엔진 만들기 — 100줄로 구현하는 2D 물리 시뮬레이션
벡터 연산, 원 충돌 감지, 충격량 기반 응답까지 순수 JavaScript로 2D 물리 엔진을 직접 만든다.
Native JSON Modules — 번들러 없이 JSON을 import하는 시대
Import Attributes와 함께 표준이 된 native JSON module. 어떻게 동작하고, 기존 번들러 방식과 뭐가 다른지 정리했다.
Babel 7.29.0 — 10년 역사의 마지막 마이너, 그리고 8 RC1
2026년 1월 31일, Babel 7의 마지막 마이너 릴리스가 공개됐다. 이 버전이 갖는 역사적 의미와 Babel 8 RC1의 핵심 변화를 정리한다.
Error.isError() — realm을 넘나드는 안전한 에러 검사 API
instanceof Error가 iframe과 worker에서 실패하는 이유, 그리고 이를 근본적으로 해결하는 Error.isError()의 동작 원리를 정리한다.