Search

#028 #OPENAI OCR 요금 분석

 주요 OCR서비스 / 라이브러리 비교

네이버 클로바 요금
구분
서비스
기본요금
기본 제공 구간
추가 과금기준
추가요금
영수증
Standard 플랜
월 180,000원
3,000건
추가 호출 횟수당
건당 80원
영수증
Advanced 플랜
월 580,000원
15,000건
추가 호출 횟수당
건당 50원
사업자등록증
Standard 플랜
월 180,000원
3,000건
추가 호출 횟수당
건당 80원
사업자등록증
Advanced 플랜
월 580,000원
15,000건
추가 호출 횟수당
건당 50원
 높은 정확도
️ 커스터 마이징 안됨
️ 비싼거 같음
️ 절차가 복잡함
openai 요금
구분
요금 체계
토큰 사용량
한달 사용량
건당 요금
gpt-4o-mini
- 요청 토큰 요금 $0.150 / 1M input tokens - 반환 토큰 요금 $0.600 / 1M output tokens
- 사업자 등록증 $토큰 사용량 : 대략 37,000 tokens - 영수증 $토큰 사용량 : 대략 17,000 tokens
건당 과금이지만 네이버 기본 요금제 사용량과 비교시 : 3,000 건 사업자등록증 : 월 24,000원 영수증 : 월 12,000원
사업자등록증 : 건당 8원 영수증 : 건당 4원
gpt-4o
- 요청 토큰 요금 $2.50 / 1M input tokens - 반환 토큰 요금 $10.00 / 1M output tokens
- 사업자 등록증 $토큰 사용량 : 대략 1,450 tokens - 영수증 $토큰 사용량 : 대략 1,040 tokens
건당 과금이지만 네이버 기본 요금제 사용량과 비교시 : 3,000 건 사업자등록증 : 월 15,750원 영수증 : 월 10,920원
사업자등록증 : 건당 5원 영수증 : 건당 4원
  4o 모델은 정확도 좋음 - 4o모델은 요청 최적화 작업이란게 있어서 요청 토큰 갯수가 되게 적음
  프롬프트 커스터마이징이 되어서 정확도 발전 시킬수 있음, fine tuning 옵션도 제공함
  절차가 간편함
  반환 데이터 형태도 커스터 마이징 가능 (기본주소 + 상세주소 형태 커스터마이징 가능)
  서버에 먼저 업로드하고 링크로 요청하면 요청 토큰이 1400개에서 1070개로 줄어듬, 3분의 1가량 요금을 줄일 수 있다는 뜻
tesseract OCR
  무료
  지속적인 업데이트 확인됨
️ 정확도 다소 떨어짐
️ 커스터마이징 어려움
️ 학습 모델이 아님
paddle OCR
  무료
  정확도는 tesseract보다 약간 좋음
️ 최신 라이브러리 업데이트가 3년전
️ 커스터마이징 많이 어려움
️ 학습 모델이 아님

 OPENAI api 실제 사용 비교

 사전 설정

1. 사업자등록증에서 원하는 데이터를 추출하는 기능이 필요하다! 2. 이미지 또는 pdf 파일을 base64변환해서 api 요청 3. 전체 토큰 계산 (캐시가 작동하지 않게 요청)
JavaScript
복사

 OpenAIService 구현

import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:http/http.dart' as http; import 'package:logger/logger.dart'; import 'package:image/image.dart' as img; class OpenAIService { Future<String> extractTextFromImage(Uint8List imageBytes) async { final image = img.decodeImage(imageBytes); final resizedImage = img.copyResize(image!, width: 1000); // 이미지의 크기 조절로 토큰 수를 조절 할 수 있다. final compressedImageBytes = Uint8List.fromList( img.encodeJpg(resizedImage, quality: 100)); // 이미지 품질 조절로 토큰 수를 조절 할 수 있다. final base64Image = base64Encode(compressedImageBytes); final response = await http.post( Uri.parse('https://api.openai.com/v1/chat/completions'), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ${dotenv.env['OPENAI_API_KEY']}', }, body: jsonEncode({ 'model': 'gpt-4o', // 'messages': [ // { // 'role': 'user', // 'content': [ // { // 'type': 'text', // 'text': // '너는 뛰어난 세무사야. 이 이미지는 사업자 등록증이고, 여기서 사업자 등록번호, 개업연월일, 사업장 주소, 상호명(법인명,단체명)을 추출해서 json으로만 반환해야해.\n너가 채워야 할 필드는 businessNumber, startDate, baseAddress, detailAddress, storeName 매칭해서 한글로 반환해야해.\n그리고 보통 주소는 기본주소와 상세주소로 나누어 지는데, 주소가 --서울특별시 종로구 종로 6, 5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 되어있으면 \n기본주소는 --서울특별시 종로구 종로 6--이고 상세주소는 --5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 나누어져야해 \n그리고 모든 주소는 한국에서 실존하는 지역명이어야 해' // }, // { // 'type': 'image_url', // 'image_url': { // 'url': // 'https://ims365.co.kr/file_data/ims365s/2020/09/23/584537f7305734571cbaf170666715cc.jpg' // }, // } // ], // 'detail': 'high', // }, // ], 'messages': [ { 'role': 'user', 'content': [ { 'type': 'text', 'text': '너는 뛰어난 세무사야. 이 이미지는 사업자 등록증이고, 여기서 사업자 등록번호, 개업연월일, 사업장 주소, 상호명(법인명,단체명)을 추출해서 json으로만 반환해야해.\n너가 채워야 할 필드는 bizNum, openDate, baseAddress, detailAddress, bizName에 매칭해서 한글로 반환해야해.\n그리고 보통 주소는 기본주소와 상세주소로 나누어 지는데, 주소가 --서울특별시 종로구 종로 6, 5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 되어있으면 \n기본주소는 --서울특별시 종로구 종로 6--이고 상세주소는 --5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 나누어져야해 \n그리고 모든 주소는 한국에서 실존하는 지역명이어야 해' }, { 'type': 'image_url', 'image_url': {'url': 'data:image/jpeg;base64,$base64Image'} } ], 'detail': 'high', } ], }), ); if (response.statusCode == 200) { final data = jsonDecode(utf8.decode(response.bodyBytes)); Logger().i('Full Response: ${jsonEncode(data)}'); final totalTokens = data['usage']['total_tokens']; Logger().i('Total tokens: $totalTokens'); Logger().i(data['choices'][0]['message']['content']); return data['choices'][0]['message']['content']; } else { throw Exception('Failed to extract text: ${response.body}'); } } }
JavaScript
복사
pdf파일이나 이미지를 변환해 Uint8List 타입을 매개변수로 openai-service를 요청하는 과정이다.
요청당 128000 토큰을 넘기면 에러가 발생한다.
그래서 그냥 이미지를 base64로 변환해서 요청하면 1회 최대 토큰수를 초과하는 경우가 많기 때문에, 이미지 사이즈를 줄이기로 했다.
이미지 url로 요청을 할 수도 있다 (주석처리된 부분), 이 경우 base64로 요청하는 것보다 조금 더 저렴하다. 이 부분도 확인해보자.
위와 같은 조건으로 비교를 해보았다.

 요청이 캐시되는 원리

요청 구조
OpenAI 공식 문서
캐시를 활용하기 위해서는
gpt에게 요청할 때 시작하는 첫 부분을 다른 요청과 동일하게 만들면 캐시가 작동한다.
예를들어 gpt 역할 설정같은 첫 부분을 완전 똑같이 요청하면 캐시가 작동하여 비용을 낮출 수 있다.
요청이 작동 되는 원리
OpenAI 공식 문서
요청을 받으면 요청의 앞 부분이 일치하는 요청이 있는 지 조회한다.
만약에 요청을 찾게되면, 그 요청의 응답속도를 현저히 낮출 수 있으며, 요금도 줄일 수 있다.
캐시는 보통 5~10분 보관되지만, 사용량이 낮은 시간대에서는 한 시간동안 보관할 수 있다.
캐싱 조건
OpenAI 공식문서
토큰 수가 1024 이하인 경우에는 캐시가 작동한지 않는다, 특히 cached_tokens부분이 0을 표시하게 된다.
그리고 만약 1024개를 초과하는 경우에는 캐시가 적용되는 부분은 128토큰씩 증가한다.
예를 들어 1500토큰수의 요청을 했다면 그 아래 128토큰수 증가단위인 1408토큰 까지 캐시적용된다.
캐시가 적용되는 데이터
OpenAI 공식문서
여러가지가 있지만 이 블로그에서 중요한 내용은 이미지가 캐시가 되는것인가이다.
base64로 된 이미지링크, 그리고 여러개의 파일까지 캐시가 된다

 model: gpt-4o-mini

요청 형태
'model': 'gpt-4o-mini', 'messages': [ { 'role': 'user', 'content': [ { 'type': 'text', 'text': '너는 뛰어난 세무사야. 이 이미지는 사업자 등록증이고, 여기서 사업자 등록번호, 개업연월일, 사업장 주소, 상호명(법인명,단체명)을 추출해서 json으로만 반환해야해.\n너가 채워야 할 필드는 businessNumber, startDate, baseAddress, detailAddress, businessName에 매칭해서 한글로 반환해야해.\n그리고 보통 주소는 기본주소와 상세주소로 나누어 지는데, 주소가 --서울특별시 종로구 종로 6, 5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 되어있으면 \n baseAddress는 --서울특별시 종로구 종로 6--이고 detailAddress는 --5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 나누어져야해 \n그리고 모든 주소는 한국에서 실존하는 지역명이어야 해' }, { 'type': 'image_url', 'image_url': {'url': 'data:image/jpeg;base64,$base64Image'} } ], 'detail': 'high', } ],
JavaScript
복사
텍스트 추출의 정확도를 높이기 위해서 ‘detail’: ‘high’ 로 high-resolution 적용
pdf 파일을 base64로 변환해서 요청
이미지 사이즈 width: 1000 설정
이미지 품질 quality: 100 설정
응답 형태
총 사용한 토큰 : 37,153개
정확도 : 오탈자 3개 발견
캐시된 토큰 수 : 0개
비용 계산
건당 요청 비용 : 0.0056 USD
적용 환율 : 1400 원 (약간 비싸게)
건당 비용 : 0.00562USD×1,400KRW/USD=7.861KRW, 대략 8원
네이버 월요금제 3000건 이랑 비교시 : 월 24,000원

 model: gpt-4o

요청 형태
'model': 'gpt-4o', 'messages': [ { 'role': 'user', 'content': [ { 'type': 'text', 'text': '너는 뛰어난 세무사야. 이 이미지는 사업자 등록증이고, 여기서 사업자 등록번호, 개업연월일, 사업장 주소, 상호명(법인명,단체명)을 추출해서 json으로만 반환해야해.\n너가 채워야 할 필드는 bizNum, openDate, baseAddress, detailAddress, bizName에 매칭해서 한글로 반환해야해.\n그리고 보통 주소는 기본주소와 상세주소로 나누어 지는데, 주소가 --서울특별시 종로구 종로 6, 5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 되어있으면 \n기본주소는 --서울특별시 종로구 종로 6--이고 상세주소는 --5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 나누어져야해 \n그리고 모든 주소는 한국에서 실존하는 지역명이어야 해' }, { 'type': 'image_url', 'image_url': {'url': 'data:image/jpeg;base64,$base64Image'} } ], 'detail': 'high', } ],
JavaScript
복사
이 요청은 다음날에 진행되었기 때문에 캐시는 적용이 안되었을 것이다. 캐시없이 원요금을 비교하기 위해서!
pdf 파일을 base64로 변환해서 요청
이미지 사이즈 width: 1000 설정
이미지 품질 quality: 100 설정
응답 형태
총 사용한 토큰 : 1,423개
정확도 : 오탈자 0개 발견
캐시된 토큰 수 : 0개
비용 계산
건당 요청 비용 : 0.0043 USD
적용 환율 : 1400 원 (약간 비싸게)
건당 비용 : 0.00427USD×1,400KRW/USD=5.978KRW, 대략 6원
네이버 월요금제 3000건 이랑 비교시 : 월 18,000원

 비용을 조금더 아낄 수 있는 방법?

 url로 요청

'model': 'gpt-4o', 'messages': [ { 'role': 'user', 'content': [ { 'type': 'text', 'text': '?너는 뛰어난 세무사야. 이 이미지는 사업자 등록증이고, 여기서 사업자 등록번호, 개업연월일, 사업장 주소, 상호명(법인명,단체명)을 추출해서 json으로만 반환해야해.\n너가 채워야 할 필드는 bizNum, openDate, baseAddress, detailAddress, bizName에 매칭해서 한글로 반환해야해.\n그리고 보통 주소는 기본주소와 상세주소로 나누어 지는데, 주소가 --서울특별시 종로구 종로 6, 5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 되어있으면 \n기본주소는 --서울특별시 종로구 종로 6--이고 상세주소는 --5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 나누어져야해 \n그리고 모든 주소는 한국에서 실존하는 지역명이어야 해' }, { 'type': 'image_url', 'image_url': { 'url': 'https://ims365.co.kr/file_data/ims365s/2020/09/23/584537f7305734571cbaf170666715cc.jpg' }, } ], 'detail': 'high', }, ],
JavaScript
복사
요청 원문이 비슷하기 때문에, 제일 앞에 ? 를 붙여 변경하였다. 이렇게 하면 캐시 적용이 안된다.
인터넷에 공개된 사업자 등록증 사진 링크로 테스트 해보자.
응답형태
총 사용한 토큰 : 1,073개
정확도 : 오탈자 0개 발견
캐시된 토큰 수 : 0개
비용계산
건당 요청 비용 : 0.0033 USD
적용 환율 : 1400 원 (약간 비싸게)
건당 비용 : 0.00332USD×1,400KRW/USD=4.648KRW, 대략 5원
네이버 월요금제 3000건 이랑 비교시 : 월 15,000원

 결론

정확하게 왜 그런지는 알 수 없지만, 4o모델로 요청을 하면 토큰 수가 작게 계산이 되었다. 4o모델이 요청을 최적화한다고 gpt가 이야기 했지만, 다른 tokenizer 방식을 쓰는 건가 싶기도 하다. 캐시토큰도 0으로 찍히니 캐시때문은 아닌 것 같다. 그래서 정확도 높은 최고 효율을 내기 위해서는, 요청전 사업자 등록증을 스토리지 서버에 업로드를 먼저 진행하고 링크로 요청하면 1400개 정도로 요청되던 것이 1000개정도로 요청이 된다. 총 비용을 거의 3분의 1을 줄일 수 있는 셈이다.
그리고 요청 링크는 변경하되 본문을 변경하지 않고 그대로 사용하며, 5~10분 이내에 재요청이 들어온다면 매 요청당 1024 토큰을 더 아낄수 있게 된다. ( 내 생각에는 완벽히 일치하는 구간이 1024토큰이 되어야 되지 않을 까 생각한다. 그렇게 따지면 현재 링크 방식은 응답토큰이 85개라서 정확히 일치하는 본문 구간이 1024토큰이 되지 않아 캐시가 적용이 되지 않을 것이라 생각한다. )
결론을 이렇게 내보겠다.
gpt-4o 모델 사용
요청 본문을 정확히 일치 시켜야된다. (최소 1024 토큰 구간까지는)
파일 변환 방식도 괜찮고, 링크로 요청한다면 요청 본문을 좀 늘리는 것도 괜찮을 것 같다.