Clova์์ ์ ๊ณตํ๋ ์์์ฆ ๋ถ์ api๋ฅผ ์ฌ์ฉํ์ฌ ์์์ฆ์ ๋ํ ์ ๋ณด๋ฅผ ๋ฝ์๋ด๋ณด๋ ค๊ณ ํ๋ค.
์๋์ ๋์์๋ NAVER CLOUD PLATFORM ํ์ด์ง์ ์ ์ํ๋ฉด ํด๋น ๊ธฐ๋ฅ์ ๋ํด ์์ธํ๊ฒ ์ดํด๋ณผ ์ ์๋ค.
๊ทธ ์ค์์ ์์์ฆ์ ์ฌ์ฉํ๋ ค๊ณ ํ๋ฉด ์ด๋ค ์ข ๋ฅ๋ฅผ ์ ์ฒญํด์ผ ํ๋์ง๋ฅผ ํฌ์คํ ํด๋ณด๋ ค๊ณ ํ๋ค.
https://www.ncloud.com/product/aiService/ocr#overview
NAVER CLOUD PLATFORM
cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification
www.ncloud.com
1. Clova OCR์ด๋?
CLOVA OCR(Optical Character Recognition)์ ์ ์กํ ๋ฌธ์๋ ์ด๋ฏธ์ง๋ฅผ ์ธ์ํ์ฌ ์ฌ์ฉ์๊ฐ ์ง์ ํ ์์ญ์ ํ ์คํธ์ ๋ฐ์ดํฐ๋ฅผ ์ ํํ๊ฒ ์ถ์ถํ๋ ๋ค์ด๋ฒ ํด๋ผ์ฐ๋ ํ๋ซํผ์ ์๋น์ค์ ๋๋ค. ์ด๋ฏธ์ง์ ๋ชจ๋ ํ ์คํธ ์ธ์, ๋๋ฉ์ธ์ ๋ฐฐํฌ๋ ํ ํ๋ฆฟ์ด ํฌํจ๋ ์ด๋ฏธ์ง์ ํ ์คํธ ์ธ์, ํ ํ๋ฆฟ ๋ด์์ ํ์ํ ๊ธ์๋ง ๋น ๋ฅด๊ฒ ์ถ์ถํ๋ ๊ธฐ๋ฅ์ ๋ํ API๋ฅผ RESTful ํํ๋ก ์ ๊ณตํฉ๋๋ค.
1-1. OCR ์ข ๋ฅ
Clova์์ ์ ๊ณตํ๋ OCR์ ํฌ๊ฒ General OCR, Template OCR, Document OCR๋ก ์ธ ๊ฐ์ง๋ก ๋๋์ด์ ธ ์๋ค.
General OCR | ์ผ๋ฐ ํ ์คํธ ๋ฐ ํ ์ถ์ถ OCR |
Template OCR | Template OCR |
Document OCR > ๋ช ํจ | ๋ช ํจ ์ ๋ณด OCR |
Document OCR > ์ฌ์ ์๋ฑ๋ก์ฆ | ์ฌ์ ์๋ฑ๋ก์ฆ ์ ๋ณด OCR |
Document OCR > ์ ์ฉ์นด๋ | ์ ์ฉ์นด๋ ์ ๋ณด OCR |
Document OCR > ์์์ฆ | ์์์ฆ ์ ๋ณด OCR |
Document OCR > ์ ๋ถ์ฆ > ์ฌ๊ถ | ์ฌ๊ถ ์ ๋ณด OCR |
Document OCR > ์ ๋ถ์ฆ > ์ธ๊ตญ์ธ๋ฑ๋ก์ฆ | ์ธ๊ตญ์ธ๋ฑ๋ก์ฆ ์ ๋ณด OCR |
Document OCR > ์ ๋ถ์ฆ > ์ด์ ๋ฉดํ์ฆ | ์ด์ ๋ฉดํ์ฆ ์ ๋ณด OCR |
Document OCR > ์ ๋ถ์ฆ > ์ฃผ๋ฏผ๋ฑ๋ก์ฆ | ์ฃผ๋ฏผ๋ฑ๋ก์ฆ ์ ๋ณด OCR |
General OCR์ ์ด๋ฏธ์ง์์ ๋จ์ํ ํ ์คํธ๋ง ์ถ์ถํ๋ ๊ธฐ๋ฅ์ด๊ณ ,
Document OCR์ ์์์ฆ์ฒ๋ผ ๊ตฌ์กฐํ๋ ๋ฌธ์์ ํนํ๋์ด, ๋ ์ง, ์ํธ๋ช , ๊ธ์ก ๊ฐ์ ์๋ฏธ ์๋ ์ ๋ณด๋ฅผ ํญ๋ชฉ๋ณ๋ก ์ถ์ถํ๋ ๊ธฐ๋ฅ์ด๋ค.
2.OCR API ์ ์ฒญํ๊ธฐ
2-1. ๋ค์ด๋ฒ ํด๋ผ์ฐ๋ ํ๋ซํผ ๊ฐ์
๋ค์ด๋ฒ ๋ก๊ทธ์ธํ๋ฉด ๋ฐ๋ก ์ฌ์ฉํ ์ ์์ ์ค ์์๋๋ฐ, ๋ณ๋๋ก ํ์๊ฐ์ ์ ์งํํด์ผ ํ๋ค.
2-2. ๊ฒฐ์ ์๋จ ๋ฑ๋ก
๋ค์ด๋ฒ ํด๋ผ์ฐ๋ ํ๋ซํผ์ ๊ฒฐ์ ์๋จ์ ๋ฑ๋กํด์ผ api๋ค์ ์ด์ฉํ ์ ์๋ ๊ฒ ๊ฐ๋ค.
๊ทธ๋์ ๊ฒฐ์ ์๋จ์ ๋ฏธ๋ฆฌ ๋ฑ๋กํ์๋ค.
2-3. ์ฝ์ ์๋น์ค > CLOVA OCR Service ์ ์
CLOUD PLATFORM์์ ํน์ ์๋น์ค์ ์ ์ํ๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค.
1. Dashboard > Services์ CLOVA OCR์ ๊ฒ์ํ๋ฉด ํด๋น ์๋น์ค์ ๋ค์ด์ฌ์์๋ค.
https://console.ncloud.com/dashboard
2.๋ฐ ๋งํฌ ํด๋ฆญํ๋ฉด ๋ฐ๋ก OCR service ๋ฉ์ธ ํ์ด์ง๋ก ์ด๋ํ๋ค.
https://console.ncloud.com/ocr/domain
์ ์ํ๋ฉด ์์ ๊ฐ์ด CLOVA OCR Domain ํ์ด์ง๊ฐ ๋์จ๋ค.
2-4. ํนํ ๋ชจ๋ธ ์ค์ > ํนํ ๋ชจ๋ธ ์ ์ฒญ
Document OCR์ ํนํ ๋ชจ๋ธ๋ก ์ด์ฉํ ์ ์๋ค.
ํนํ ๋ชจ๋ธ ์ค์ > ํนํ ๋ชจ๋ธ ์ ์ฒญ์ ํด๋ฆญํ๋ค.
ํนํ ๋ชจ๋ธ ์ ์ฒญ์ ํ๋ฉด ์์ ๊ฐ์ ํ์ ์ฐฝ์ด ํ์๋๋ค.
๋ชจ๋ ์นธ์ด ํ์์นธ์ด๋ผ ์ ๋ ฅํ๊ณ ํ์ธ ๋ฒํผ์ ๋๋ฅธ๋ค
(์บก์ณ ๋ถ๋ถ์ ์ด๋ฏธ ์ ์ฒญ์ ํ ์ํ๋ผ ํ์ธ ๋ฒํผ์ด ํ์ฑํ๋์ง ์์๋๋ฐ, ์ฒ์ ์ ์ฒญํ๋ ์ํ๋ผ๋ฉด ํ์ฑํ ์ํ๋ก ๋ณด์ผ ๊ฒ์ด๋ค.)
์ ์ฒญ์ ํ๋ฉด ์น์ธ๊น์ง ์๊ฐ์ด ์กฐ๊ธ ๊ฑธ๋ฆฐ๋ค.
11์ 30๋ถ์ ์ ์ฒญ์ ํ์๊ณ , ์ ์ฌ์ ๋จน๊ณ 1์ 40๋ถ์ ๋ค์ด์์ ํ์ธ์ ํด๋ณด๋ ์น์ธ์ด ์๋ฃ๋์ด ์์๋ค.
2-5. ๋๋ฉ์ธ ์์ฑ
์น์ธ์ ๋ฐ์๋ค๋ฉด ์ด์ ํนํ ๋ชจ๋ธ ๋๋ฉ์ธ ์์ฑ์ ํตํด์ ๋๋ฉ์ธ์ ์์ฑํ ์ ์๋ค.
๋๋ฉ์ธ ์์ฑ > ํนํ ๋ชจ๋ธ ๋๋ฉ์ธ ์์ฑ
๋๋ฉ์ธ๋ช ๊ณผ ๋๋ฉ์ธ ์ฝ๋๋ฅผ ์ ์ด์ค๋ค.
- ๋๋ฉ์ธ ๋ช , ๋๋ฉ์ธ ์ฝ๋ : ์ค๋ณต๋ง ๋์ง ์๊ฒ ํ๋ค๋ฉด ์๋ฌด ์ด๋ฆ์ ์ ๋ ฅํด๋ ๊ด์ฐฎ์ ๊ฒ ๊ฐ๋ค.
- ์๋น์คํ์ : Document
- ์ธ์๋ชจ๋ธ : ์์์ฆ
- ์๋น์ค ํ๋ : Basic (์ ๋ฃ์)
ํนํ ๋ชจ๋ธ ์์ฑ์ ํด๋ฆญํ๋ฉด ๋๋ฉ์ธ์ด ์์ฑ๋๋ค.
2-6. API Gateway ์ฐ๋ ํด๋ฆญ
๋๋ฉ์ธ์์ API Gateway ์ฐ๋์ ํด๋ฆญํ๋ค.
Document OCR ํค ์์ฑํ๋ ํ์ ์ฐฝ์ด ํ์๋๋ค.
์ด๋ฏธ ๋ค ์ฐ๊ฒฐ์ ํ ๋ค๋ผ key๊ฐ ๋ค ์์ฑ์ด ๋์ด์์ง๋ง, ๋ง์ฝ secret key๊ฐ ์์ฑ๋์ด์์ง ์๋ค๋ฉด ์์ฑ์ ๋๋ฌ์ ์์ฑ์ ํด์ค๋ค.
๊ทธ๋ฆฌ๊ณ API ์๋ ์์ฑ์ ํด๋ฆญํ๋ฉด ์๋์ผ๋ก API๋ฅผ ์์ฑํด์ฃผ๋ฉฐ Invoke URL์ ์ก์์ค๋ค.
API ์๋ ์์ฑ์ ์๊ฐ์ด ์กฐ๊ธ ๊ฑธ๋ฆฌ๋ ๊ฒ ๊ฐ๋ค.
2-7 API ์์ฑ ํ์ธ
์กฐ๊ธ ๊ธฐ๋ค๋ ธ๋ค๊ฐ API Gateway Services์ ๋ค์ด์ค๋ฉด API๊ฐ ์์ฑ๋ ๊ฑธ ํ์ธํ ์ ์๋ค.
์ด์ Document ์์์ฆ API๋ฅผ ์ฌ์ฉํ ๋ชจ๋ ์ค๋น๊ฐ ๋๋ฌ๋ค!
3. ์ํ ์ฝ๋ ์์ฑ
https://api.ncloud-docs.com/docs/ai-application-service-ocr-example02
Document OCR
api.ncloud-docs.com
clova์์ ์ ๊ณตํด์ฃผ๋ ์์ ์ฝ๋๊ฐ ์๋ค.
์ฌ์ฉํ๊ณ ์ ํ๋ ์ธ์ด๋ฅผ ์ด์ฉํ์ฌ ์ฌ์ฉํ๋ฉด ๋๋ค.
Content-Type: multipart/form-data์ธ ์๋ฐ ์์ ์ฝ๋๋ฅผ ์ด์ฉํ์๋ค.
์์ ์ฝ๋์์ apiURL, secretKey, imageFile ๋ฅผ ์์ ํ ๋ค ์ฌ์ฉํ ์ ์๋ค.
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONObject;
public class OCRAPIDemo {
public static void main(String[] args) {
String apiURL = "YOUR_API_URL";
String secretKey = "YOUR_SECRET_KEY";
String imageFile = "YOUR_IMAGE_FILE";
try {
URL url = new URL(apiURL);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setUseCaches(false);
con.setDoInput(true);
con.setDoOutput(true);
con.setReadTimeout(30000);
con.setRequestMethod("POST");
String boundary = "----" + UUID.randomUUID().toString().replaceAll("-", "");
con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
con.setRequestProperty("X-OCR-SECRET", secretKey);
JSONObject json = new JSONObject();
json.put("version", "V2");
json.put("requestId", UUID.randomUUID().toString());
json.put("timestamp", System.currentTimeMillis());
JSONObject image = new JSONObject();
image.put("format", "jpg");
image.put("name", "demo");
JSONArray images = new JSONArray();
images.put(image);
json.put("images", images);
String postParams = json.toString();
con.connect();
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
long start = System.currentTimeMillis();
File file = new File(imageFile);
writeMultiPart(wr, postParams, file, boundary);
wr.close();
int responseCode = con.getResponseCode();
BufferedReader br;
if (responseCode == 200) {
br = new BufferedReader(new InputStreamReader(con.getInputStream()));
} else {
br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
}
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = br.readLine()) != null) {
response.append(inputLine);
}
br.close();
System.out.println(response);
} catch (Exception e) {
System.out.println(e);
}
}
private static void writeMultiPart(OutputStream out, String jsonMessage, File file, String boundary) throws
IOException {
StringBuilder sb = new StringBuilder();
sb.append("--").append(boundary).append("\r\n");
sb.append("Content-Disposition:form-data; name=\"message\"\r\n\r\n");
sb.append(jsonMessage);
sb.append("\r\n");
out.write(sb.toString().getBytes("UTF-8"));
out.flush();
if (file != null && file.isFile()) {
out.write(("--" + boundary + "\r\n").getBytes("UTF-8"));
StringBuilder fileString = new StringBuilder();
fileString
.append("Content-Disposition:form-data; name=\"file\"; filename=");
fileString.append("\"" + file.getName() + "\"\r\n");
fileString.append("Content-Type: application/octet-stream\r\n\r\n");
out.write(fileString.toString().getBytes("UTF-8"));
out.flush();
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int count;
while ((count = fis.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
out.write("\r\n".getBytes());
}
out.write(("--" + boundary + "--\r\n").getBytes("UTF-8"));
}
out.flush();
}
}
3-1. ์ํ์ฝ๋ Sysout์ผ๋ก ์ถ๋ ฅํด๋ณด๊ธฐ
package com.receipt.clova;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONObject;
public class ClovaOcrApplication {
public static void main(String[] args) {
String apiURL = "https://<api-gateway>/custom/v1/<project-id>/<custom-id>/document/receipt";
String secretKey = "<your-secret-key>";
String imageFile = "C:/Users/username/Desktop/ocr/images/input";
try{
// API ์์ฒญ์ ์ํ URL ๊ฐ์ฒด ์์ฑ
URL url = new URL(apiURL);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
// ์ฐ๊ฒฐ ์ค์
con.setUseCaches(false); // ์บ์ ์ฌ์ฉ ์ ํจ
con.setDoInput(true); // ์
๋ ฅ ์คํธ๋ฆผ ์ฌ์ฉ
con.setDoOutput(true); // ์ถ๋ ฅ ์คํธ๋ฆผ ์ฌ์ฉ
con.setReadTimeout(30000); // ์๋ต ๋๊ธฐ ์๊ฐ ์ค์ (30์ด)
con.setRequestMethod("POST"); // POST ๋ฐฉ์
// multipart/form-data๋ฅผ ์ํ boundary ์ค์
String boundary = "----" + UUID.randomUUID().toString().replaceAll("-", "");
con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
con.setRequestProperty("X-OCR-SECRET", secretKey); // ์ธ์ฆ ํค ํค๋
JSONObject json = new JSONObject();
json.put("version", "V2"); // OCR API ๋ฒ์
json.put("requestId", UUID.randomUUID().toString()); // ์์ฒญ ์๋ณ์ฉ ID
json.put("timestamp", System.currentTimeMillis()); // ํ์ฌ ์๊ฐ
// ์ ์กํ ์ด๋ฏธ์ง ์ ๋ณด ์ค์
JSONObject image = new JSONObject();
image.put("format", "jpg"); // ํ์ผ ํฌ๋งท
image.put("name", "demo"); // ํ์ผ ์ด๋ฆ(์๋ณ์ฉ)
JSONArray images = new JSONArray();
images.put(image); // ์ด๋ฏธ์ง ์ ๋ณด ๋ฐฐ์ด์ ์ถ๊ฐ
json.put("images", images); // JSON ๊ฐ์ฒด์ ์ด๋ฏธ์ง ์ ๋ณด ํฌํจ
String postParams = json.toString(); // ์ต์ข
JSON ํ๋ผ๋ฏธํฐ ๋ฌธ์์ด
// ์ฐ๊ฒฐ ์์
con.connect();
// ์๋ฒ๋ก ๋ฐ์ดํฐ ์ ์ก์ฉ ์คํธ๋ฆผ ์์ฑ
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
long start = System.currentTimeMillis();
// ์ด๋ฏธ์ง ํ์ผ ๊ฐ์ฒด ์์ฑ
File file = new File(imageFile);
// multipart/form-data ํ์์ผ๋ก ๋ฐ์ดํฐ ์ ์ก
writeMultiPart(wr, postParams, file, boundary);
wr.close(); // ์ ์ก ๋
// ์๋ต ์ฝ๋ ํ์ธ
int responseCode = con.getResponseCode();
BufferedReader br;
if (responseCode == 200) {
// ์ฑ๊ณต ์๋ต์ด๋ฉด input stream ์ฝ๊ธฐ
br = new BufferedReader(new InputStreamReader(con.getInputStream()));
} else {
// ์คํจ ์๋ต์ด๋ฉด error stream ์ฝ๊ธฐ
br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
}
// ์๋ต ๊ฒฐ๊ณผ ์ฝ์ด์ ์ถ๋ ฅ
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = br.readLine()) != null) {
response.append(inputLine);
}
br.close();
System.out.println(response); // ๊ฒฐ๊ณผ ์ถ๋ ฅ
} catch (Exception e) {
System.out.println(e);
}
}
/**
* multipart/form-data ํ์์ผ๋ก JSON ํ๋ผ๋ฏธํฐ์ ํ์ผ ์ ์ก
* @param out
* @param jsonMessage
* @param file
* @param boundary
* @throws IOException
*/
private static void writeMultiPart(OutputStream out, String jsonMessage, File file, String boundary) throws
IOException {
StringBuilder sb = new StringBuilder();
// message ํํธ ์์ฑ (JSON ๋ณธ๋ฌธ)
sb.append("--").append(boundary).append("\r\n");
sb.append("Content-Disposition:form-data; name=\"message\"\r\n\r\n");
sb.append(jsonMessage);
sb.append("\r\n");
out.write(sb.toString().getBytes("UTF-8"));
out.flush();
// ํ์ผ์ด ์กด์ฌํ๋ฉด ํ์ผ ๋ฐ์ดํฐ๋ multipart ํ์์ผ๋ก ์ ์ก
if (file != null && file.isFile()) {
out.write(("--" + boundary + "\r\n").getBytes("UTF-8"));
// ํ์ผ ์ ์ก์ ์ํ ํค๋ ์ค์
StringBuilder fileString = new StringBuilder();
fileString
.append("Content-Disposition:form-data; name=\"file\"; filename=");
fileString.append("\"" + file.getName() + "\"\r\n");
fileString.append("Content-Type: application/octet-stream\r\n\r\n");
out.write(fileString.toString().getBytes("UTF-8"));
out.flush();
// ์ค์ ํ์ผ ๋ด์ฉ ์ ์ก
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192]; // 8KB ๋ฒํผ
int count;
while ((count = fis.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
out.write("\r\n".getBytes()); // ํ์ผ ์ ์ก ์ข
๋ฃ ๋งํฌ
}
out.write(("--" + boundary + "--\r\n").getBytes("UTF-8"));
}
out.flush();
}
}
๊ฐ ์ค์ ์ค๋ช ์ ์์ฑํ๊ณ , response๋ฅผ Sysout์ผ๋ก ์ฐ์ด๋ด์ด ๊ฒฐ๊ณผ๋ฅผ ๋ณผ ์ ์๊ฒ ํ๋ค.
๋ค์ ๊ธ์์ ํด๋น response๋ก ์์์ฆ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ํฌ์คํ ํด๋ณด๊ฒ ๋ค.