이전 포스팅에서 Tesseract OCR의 문자 인식률을 높히기 위해 데이터를 LSTM 모델로 바꾸어봤다.
개선된 인식률을 보여줬지만 보다 인식률을 높히기위해 OpenCV를 이용하여 이미지 전처리를 해보겠다.
1. 전처리 작업
아래의 전처리 작업을 다양하게 시도해보았다.
- 불필요한 여백 잘라내기
- 테두리 자르기
- 문자 사이 여백 자르기
- 남은 이미지 확대
- 가우시안 블러 적용
- 문자 색상만 남기기
- 이미지 윤곽선 강조
- 이진화
2. 코드와 결과
코드
package com.coffebara.summaryBot.ocr;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import java.awt.image.BufferedImage;
import java.util.Arrays;
@Slf4j
public class OpenCVManager {
public static void main(String[] args) {
//OpenCV 라이브러리 로드
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// 이미지 파일 경로
String imagePath = "C:\\summaryBot\\20240922\\20240922_190038.png";
// 이미지 읽기
Mat image = Imgcodecs.imread(imagePath);
if (image.empty()) {
log.info("Image not found!");
return;
}
// 여백 자르기1 (테두리)
int outSideX = 176; // 시작 x 좌표
int outSideY = 242; // 시작 y 좌표
int outSideW = 1434 - outSideX; // 잘라낼 너비
int outSideH = 864 - outSideY; // 잘라낼 높이
// ROI가 이미지 범위를 초과하지 않는지 확인
if (outSideX + outSideW > image.cols() || outSideY + outSideH > image.rows()) {
System.out.println("ROI가 이미지 범위를 초과합니다.");
return;
}
Rect roi = new Rect(outSideX, outSideY, outSideW, outSideH);
Mat croppedImage = new Mat(image, roi);
// 잘라낸 이미지 1.5배 확대
Mat zoomedImage = new Mat();
Imgproc.resize(croppedImage, zoomedImage, new Size(croppedImage.cols() * 1.5, croppedImage.rows() * 1.5));
// 여백 자르기 2 (문자 사이를 가르고 양옆을 합침)
int middleX = 104;
int middleY = 8;
int middleW = 230 - middleX;
int middleH = 916 - middleY;
// 잘라낼 부분 생성
Rect cropRect = new Rect(middleX, middleY, middleW, middleH);
Mat croppedImage2 = new Mat(zoomedImage, cropRect);
// 나머지 이미지 부분 생성
Mat leftImage = new Mat(zoomedImage, new Rect(0, 0, middleX, zoomedImage.rows())); // 왼쪽 이미지
Mat rightImage = new Mat(zoomedImage, new Rect(middleX + middleW, 0, zoomedImage.cols() - (middleX + middleW), zoomedImage.rows())); // 오른쪽 이미지
// 양쪽 이미지 합치기
Mat mergedImage = new Mat();
Core.hconcat(Arrays.asList(leftImage, rightImage), mergedImage);
// 이미지를 HSV 색상 공간으로 변환
Mat hsvImage = new Mat();
Imgproc.cvtColor(mergedImage, hsvImage, Imgproc.COLOR_BGR2HSV);
// 흰색의 범위 설정 (HSV)
Scalar lowerWhite = new Scalar(0, 0, 200); // 흰색의 하한값
Scalar upperWhite = new Scalar(180, 50, 255); // 흰색의 상한값
// 노란색의 범위 설정 (HSV)
Scalar lowerYellow = new Scalar(20, 100, 100); // 노란색의 하한값
Scalar upperYellow = new Scalar(30, 255, 255); // 노란색의 상한값
// 흰색 마스크 생성
Mat whiteMask = new Mat();
Core.inRange(hsvImage, lowerWhite, upperWhite, whiteMask);
// 노란색 마스크 생성
Mat yellowMask = new Mat();
Core.inRange(hsvImage, lowerYellow, upperYellow, yellowMask);
// 두 마스크를 합침 (흰색과 노란색만 남기기)
Mat mask = new Mat();
Core.bitwise_or(whiteMask, yellowMask, mask);
// 원본 이미지에서 마스크 적용 (흰색과 노란색 글자만 남김)
Mat resultImg = new Mat();
Core.bitwise_and(mergedImage, mergedImage, resultImg, mask);
// // 가우시안 블러 적용
// Mat blurredImage = new Mat();
// Imgproc.GaussianBlur(resultImg, blurredImage, new Size(5, 5), 0);
//
// 이미지 윤곽선 강조
Mat edges = new Mat();
Imgproc.Canny(resultImg, edges, 100, 200);
// 윤곽선을 3채널로 변환
Mat channelsEdges = new Mat();
Imgproc.cvtColor(edges, channelsEdges, Imgproc.COLOR_GRAY2BGR);
// 윤곽선을 원본 이미지에 덧씌우기
// 윤곽선 이미지를 원본 이미지와 같은 크기로 조정
Mat resizedEdges = new Mat();
Imgproc.resize(channelsEdges, resizedEdges, new Size(resultImg.cols(), resultImg.rows()));
// 윤곽선을 원본 이미지에 덧씌우기
Mat thickEdges = new Mat();
Core.addWeighted(resizedEdges, 0.8, resizedEdges, 1.0, 0, thickEdges);
//
// // 여백 추가
// int borderSize = 10;
// Mat finalImage = new Mat();
// Core.copyMakeBorder(thickEdges, finalImage, borderSize, borderSize, borderSize, borderSize, Core.BORDER_CONSTANT, new Scalar(255, 255, 255));
//
// // 전처리: 이진화 (Thresholding)
// Mat binary = new Mat();
// Imgproc.threshold(resultImg, binary, 150, 255, Imgproc.THRESH_BINARY);
// // Mat을 BufferedImage로 변환
BufferedImage bufferedImage = matToBufferedImage(thickEdges);
// Tesseract
Tesseract tesseract = new Tesseract();
tesseract.setDatapath("C:/Program Files/Tesseract-OCR/tessdata/");
tesseract.setLanguage("kor_lstm_best+eng_lstm_best");
tesseract.setPageSegMode(4);
tesseract.setOcrEngineMode(1);
// 결과 이미지 저장 (원하는 경로에 저장)
Imgcodecs.imwrite("C:\\summaryBot\\20240922\\cut.png", thickEdges);
try {
String result = tesseract.doOCR(bufferedImage);
System.out.println("인식된 텍스트: " + result);
} catch (TesseractException e) {
e.printStackTrace();
}
}
public static BufferedImage matToBufferedImage(Mat matrix) {
int type = BufferedImage.TYPE_BYTE_GRAY;
if (matrix.channels() > 1) {
type = BufferedImage.TYPE_3BYTE_BGR;
}
int bufferSize = matrix.channels() * matrix.cols() * matrix.rows();
byte[] bytes = new byte[bufferSize];
matrix.get(0, 0, bytes); // get all the pixels
BufferedImage image = new BufferedImage(matrix.cols(), matrix.rows(), type);
image.getRaster().setDataElements(0, 0, matrix.cols(), matrix.rows(), bytes);
return image;
}
}
결과 중 하나
[DM23] PME XR}
"DEATH MARCHz
4,262,231
[D2K3] PMStewart
DEATH OF KAMIKAZE
4,198,218
[DM23] PMpsijchd
DEATH MARCHz
4,104,963
[DM23] "MBardox
DEATH MARCHz
3,809,332
[D2K3] Mo oWodKao o
DEATH OF KAMIKAZE
3,508,562
[K23L] “onDonner
Royal Lions 23
3,330,687
원본 이미지
전처리 작업한 이미지 결과물
전처리 작업에 따라 인식률이 좋아지기도 나빠지기도 했다. 하지만 어느 결과에서도 위첨자를인식하지 못해 그 주변 문자까지 모두 망가졌다.
3. ChatGPT
무료 OCR의 한계라고 생각할 무렵, 어느분에게 gpt에 넣어보세요 라는 힌트를 얻었다.
"Please scan me this document and provide output in markdown/latex format"
전처리도 안했는데 그 어떤 결과물보다 가장 높은 인식률을 보여줬다ㅠㅠ 하지만 몇백장은 못쓰니 Pass
다음 시간에는 디바이스에서 아이디를 복사하여 가져오는 방법을 시도해보겠다.
참고
'Backend > 프로젝트' 카테고리의 다른 글
갈아만든 임원 - 8 엑셀 데이터 입력 (0) | 2024.10.06 |
---|---|
갈아만든 임원 - 7 데이터 추출하기3 클립보드 접근 (1) | 2024.10.05 |
갈아만든 임원 - 5 데이터 추출하기2 OpenCV 설치 (1) | 2024.09.25 |
갈아만든 임원 - 4 데이터 추출하기 (Tesseract OCR) (0) | 2024.09.23 |
갈아만든 임원 - 3 화면 조작 및 스크린샷 저장하기 (0) | 2024.09.22 |