
이번에 인사 관리 시스템(HR)개발을 진행하게 됐다. 그 중에서 나는 결재 시스템을 담당하게 됐다. 결재 시스템 중에서 영수증 결재 관련된 부분에서 OCR API를 이용하기로 결정했다. 그래서 이 부분과 관련해서 개발한 과정을 포스팅 해보고자 한다.
OCR (Optical Character Recognition)은 광학 문자 인식의 약자로, 이미지나 스캔 문서에 있는 텍스트를 기계가 읽을 수 있는 텍스트 데이터로 변환하는 기술이다. 이러한 OCR 기술을 제공하는 api는 꽤 많은 종류가 있었다.
나는 그 많은 기술 중에서 Naver Clova Api를 사용했다. 내가 우선순위로 둔 것은 한글 인식률이 좋은가? 5초 이내로 영수증을 처리할 수 있는? 공식 문서가 잘 정리되어 있는가? 사용이 쉬운가? 이렇게 4가지다.
이 부분에 대해서는 https://devocean.sk.com/blog/techBoardDetail.do?ID=165524&boardType=techBlog 이 블로그를 참고 했다.
📢 Naver OCR API
OCR 기술을 사용할 수 있는 설명이 있는 링크이다. 여기를 클릭하면 밑에 사진과 같은 페이지로 넘어가게 되고, 여기에서 이용 신청을 클릭하면 된다.

위의 링크에서 이용 신청을 클릭하면 밑에 그림과 같은 페이지로 이동하게 된다.
특화 모델 생성을 클릭하면 밑에와 같은 페이지로 이동하는데, 여기에서 나는 영수증 사진을 스캔하는 ocr을 사용하려고 했기 때문에 특화 모델 생성에서 영수증을 신청해서 사용했다.

신청을 하면 승인대기중으로 떠 있다가 승인이 되면 승인으로 바뀐다. 승인 받는데 까지 1시간 정도 걸린 것 같다.

이제 위의 사진에서 오른쪽 끝에 옵션에 있는 API Gateway 연동을 클릭하면 밑에와 같은 페이지가 뜰 것이다.
그리고 이 페이지에 Secret Key와 APIGW Invoke URL(나의 전용 API 주소) 이 나오게 된다. 이제 이 2가지를 이용해 OCR API를 사용해 볼 것이다.
(참고로 그냥 자동으로 연결할 수 있으니... 자동으로 연결하는 것을 사용하면 된다. 수동으로 연동하는 경우 설정이 조금 더 복잡하게 되어 있으니 이 부분은 API 문서를 확인하면 된다.)
우선 코드를 작성하기 전에 이 API가 잘 불러와지는지 Postman으로 확인해봤다. 이 때, headers와 body에 값을 실어서 보내야 하는데 이와 관련된 부분은 네이버 ocr 사용 공식 문서에서 볼 수 있다.
(1) url에는 APIGW Invoke URL(나의 전용 API 주소)를 넣어서 post 메서드를 이용해 요청을 보내야 한다.
(2) 헤더에는 어떤 타입으로 정보를 보낼지에 관한 정보와 위에서 발급 받았던 secret key를 넣어주면 된다.

이렇게 postman 사진을 보면 key와 value 부분에 값을 넣어서 보내면 되고, Content-Type에는 어떤 형식을 사용할지 여부를 넣어서 보내주면 된다.
json 형식인 경우 : aplication/jsonmultipart 형식인 경우 : multipart/form-data
json인 경우 이렇게 실어서 보내주면 된다. 이때, requestId는 내가 임의로 정한 것이다. 그리고 images에서 data 부분에는 Base 64로 변환된 이미지로 보내야 한다!! (이 부분을 간과하고 요청을 보냈다가....자꾸 에러가 발생했다....!ㅎ)
mutipart인 경우에는 message와 file을 위에 처럼 보내면 되고, message 부분에는
version, requestId, timestamp, images(format, name) 정보를 넣어서 보내면 된다.
naver:
service:
url: ${NAVER_URL}
secret-key: ${NAVER_SECRET_KEY}
이렇게 yml 파일에 설정한 다음에, 발급받았던 url과 secret_key 정보를 넣어주면 된다.
@Component
public class NaverOcrApi {
@Value("${naver.service.url}")
private String url;
@Value("${naver.service.secret-key}")
private String naverSecretKey;
/* ocr api와 통신 후 결과 값을 반환하는 메서드 */
public String requestOcrApi(File file, String fileName, String format) {
try {
// HTTP 통신을 위해 외부로 데이터를 전송할 때 사용되는 클래스
RestTemplate restTemplate = new RestTemplate();
/* message 구성 정보
* (1) version : 버전 정보
* (2) requestId : API 호출 UUID
* (3) timestamp : API 호출 timestamp
* (4) images
* (4-1) format : 파일 형식
* (4-2) name : 파일 이름
* */
String jsonMessage = new ObjectMapper().writeValueAsString(Map.of(
"version", "V2",
"requestId", UUID.randomUUID().toString(),
"timestamp", System.currentTimeMillis(),
"images", List.of(Map.of("format", format, "name", fileName))
));
// 헤더에 실어서 보내줘야 하는 정보 : content-type, secret-key
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.set("X-OCR-SECRET", naverSecretKey);
// body에 실어서 보내줘야 하는 정보 : message, file
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("message", new HttpEntity<>(jsonMessage, createJsonHeader()));
body.add("file", new FileSystemResource(file));
// 요청 보내기
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
// 응답을 받아오는 객체
ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
return response.getBody();
} catch (OcrRequestFailedException | JsonProcessingException e) {
throw new OcrRequestFailedException(ErrorCode.FAILED_OCR_CALL);
}
}
// message는 json 형식으로 들어가기 때문에 만든 부분
private HttpHeaders createJsonHeader() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
}
이렇게 api와 통신하는 부분을 작성했다. 그리고 서비스 계층에서는 이렇게 api와 통신해서 가져오는 값을 가공하고, controller와 소통하는 코드를 작성했다.
나는 가게명, 가게주소, 비용, 승인날짜 이렇게 4가지의 정보가 필요했기 때문에 4가지 정보를 가져오는 코드를 작성했다. 이 부분은 각자가 원하는 정보에 맞춰서 데이터를 가공해서 작성하면 된다. 다음 포스팅에서는 이렇게 가져온 api를 바탕으로 어떤식으로 영수증 정보를 가져왔는지 작성해볼 예정이다.
오늘은 이렇게
Naver OCR API를 Spring Boot와 연결하는 과정을 작성했다. 네이버 클로바에는 다양한 유형의 문서를 스캔할 수 있는 api를 제공하고 있으니 ocr을 사용하는 경우 각자의 상황에 맞게 api를 사용하면 좋을 것 같다.