일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- typeORM
- nestjs
- react
- MySQL
- Dinosaur
- Queue
- flask
- dfs
- Sequelize
- TypeScript
- cookie
- Express
- mongoose
- nodejs
- game
- GIT
- Bull
- 정렬
- JavaScript
- 게임
- class
- 공룡게임
- jest
- Nest.js
- OCR
- MongoDB
- 자료구조
- Python
- AWS
- Today
- Total
포시코딩
디아블로4 경매 사이트 만들기 (5) - OpenCV.js Template Matching 본문
개요
위 글에서 이어진다.
지금까지 업로드한 사진에 대해 특정 부분을 crop하고 해당 부분에서 텍스트 인식하는 방법까지 알아봤다.
다만, 텍스트 전체를 인식하기엔 인식률이 떨어져서
전처리 작업으로 보통 OpenCV를 통해 그레이스케일 작업을 거치기 때문에
나도 마찬가지로 그레이스케일을 진행하기로 결정했다.
그레이스케일(GrayScale)이란?
8비트의 R.G.B(3채널)의 이미지를 1채널로 변화시키는 것을 의미
다시 말해, 색상 정보를 갖지 않고 0 ~ 255 밝기의 차이로 이미지를 변환시키는 것을 말하는데
3채널의 이미지가 1채널의 이미지로 변환이 되면 컴퓨터가 처리해야 할 계산이 줄어들기 때문에
이미지, 영상을 처리하는 데 있어 그레이스케일을 활용하는 것이 좋다.
이진화(Binarization)란?
그레이스케일이 3채널의 이미지를 1채널로 바꾸는 거라면
이진화는 1채널의 이미지 0 ~ 255를 0 or 1로 바꿔주는 것이다.
쉽게 말해, 특정값(임계값)을 흑 또는 백으로 구분 짓는 것
보통 이미지나 영상에 등장하는 사물의 특징을 추출하기 위한 최적의 임계값을 찾기 위해 이진화를 진행한다.
블러링(Bluring)
추가로 블러링(또는 스무딩. Smoothing)을 진행하여 노이즈를 제거할 수 있지만
나는 게임에서 찍은 스크린샷을 대상으로 진행할 것이기 때문에
제거할 노이즈가 따로 없기에 진행하지 않았다.
OpenCV란?
Open Source Computer Vision Library의 약자로
이미지, 영상 처리에 사용할 수 있는 오픈소스 라이브러리다.
이미지 처리, 객체 감지, 얼굴 인식, 동작 인식, 패턴 인식, 영상 분석 등의 작업에 활용되며
실시간 처리에 중점을 두고 설계되서 빠른 속도와 효율성을 자랑한다.
C++ 기반 언어지만 최근에는 파이썬의 대중화로 파이썬에서도 많이 사용되며
내 경우엔 OpenCV.js를 통해 javascript에서 사용할 것이다.
OpenCV.js
https://docs.opencv.org/3.4/index.html
세팅
https://docs.opencv.org/3.4/d0/d84/tutorial_js_usage.html
우선 opencv.js를 파일로 받아야 한다.
https://docs.opencv.org/3.4.0/opencv.js
위 링크로 들어가면 검은 바탕에 흰색 글씨로 수 많은 코드가 나열될텐데
우클릭 후 다른 이름으로 저장하기를 통해 내 프로젝트 폴더 내에 위치시키면 된다.
이후 같은 위치에 index.html을 만들어 예제에 나온 그대로 진행해준다.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello OpenCV.js</title>
</head>
<body>
<h2>Hello OpenCV.js</h2>
<p id="status">OpenCV.js is loading...</p>
<div style="display: flex;">
<div class="inputoutput">
<img id="imageSrc" alt="No Image" />
<div class="caption">imageSrc <input type="file" id="fileInput" name="file" /></div>
</div>
<div class="inputoutput">
<canvas id="canvasOutput"></canvas>
<div class="caption">canvasOutput</div>
</div>
</div>
<script type="text/javascript">
let imgElement = document.getElementById('imageSrc');
let inputElement = document.getElementById('fileInput');
inputElement.addEventListener('change', (e) => {
imgElement.src = URL.createObjectURL(e.target.files[0]);
}, false);
imgElement.onload = function () {
let mat = cv.imread(imgElement);
cv.imshow('canvasOutput', mat);
mat.delete();
};
var Module = {
onRuntimeInitialized() {
document.getElementById('status').innerHTML = 'OpenCV.js is ready.';
}
};
</script>
<script async src="opencv.js" type="text/javascript"></script>
</body>
</html>
위 예제를 따라적고 실행해서 아무 이미지나 업로드 해보면 위 사진처럼 그대로 캔버스에 찍혀 나오게 된다.
둘 다 같다고 생각할 수 있지만 위에는 <img> 태그, 아래는 <canvas> 태그로
opencv의 결과물은 이제부터 항상 canvas를 통해 출력될 것이다.
이렇게 기본 베이스 준비가 끝났다.
다른 처리 방법에 따라 여기서 코드를 추가하는 방식으로 진행된다.
그레이스케일
https://docs.opencv.org/3.4/df/d24/tutorial_js_image_display.html
이번엔 그레이스케일 처리를 진행해보자
마찬가지로 위 예제를 따라하면 되는데 변수명이 달라지기도 하기 때문에
바로 따라하기 좋게 수정한 코드를 공유한다.
index.html
<script type="text/javascript">
let imgElement = document.getElementById('imageSrc');
let inputElement = document.getElementById('fileInput');
inputElement.addEventListener('change', (e) => {
imgElement.src = URL.createObjectURL(e.target.files[0]);
}, false);
imgElement.onload = function () {
// let mat = cv.imread(imgElement);
// cv.imshow('canvasOutput', mat);
// mat.delete();
let src = cv.imread(imgElement);
let dst = new cv.Mat();
cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY);
cv.imshow('canvasOutput', dst);
src.delete();
dst.delete();
};
var Module = {
onRuntimeInitialized() {
document.getElementById('status').innerHTML = 'OpenCV.js is ready.';
}
};
</script>
여기서 src는 'source'로 원본 이미지 출처를,
dst는 'destination으로 목적지'를 의미한다.
cv.imread를 통해 원본 이미지를 src에 담고 cv.Mat()으로 결과를 담을 객체 dst를 만든 다음
cv.cvtColor를 통해 cv.COLOR_RGBA2GRAY 알고리즘을 적용한 결과를 dst에 담는다.
이렇게 만들어진 결과를 cv.imshow를 통해 canvas에 뿌려주게 되는 과정이다.
(여기서 imshow는 imageshow라는 뜻)
추가로, 공식 문서에서 처리를 완료한 후 과정에 쓰였던 객체들을 항상 delete() 해주는 것을 강조했다.
그렇게 나온 결과에 대해 테스트해보면 위와 같다.
여기까지 진행하고나서 나는 opencv의 다른 기능들에 대해서 궁금해졌고
모든 기능들을 확인하는 과정에서 흥미로운 기능을 찾을 수 있었다.
템플릿 매칭
https://docs.opencv.org/3.4/d8/dd1/tutorial_js_template_matching.html
템플릿 매칭은 큰 이미지에서 템플릿 이미지의 위치를 검색하여 찾는 방법이다.
나는 이 기능을 활용하면 굳이 사용자가 직접 스크린샷에서 아이템의 상세 내용 부분을 crop하지 않아도 될 것 같다는 생각을 했다.
(템플릿 매칭의 경우 적용하기에 맞는 기능인지 먼저 테스트해야해서 공식 문서상의 테스트 코드를 통해 진행했다.)
(희귀)아이템은 노란색 테두리로 이루어져 있기에 미리 위 이미지와 같이 편집한 이미지를 템플릿으로 사용
그 결과 위와 같이 원하는 위치가 빨간 테두리로 잡히는걸 확인할 수 있었다.
이제 매칭된 이미지 내용을 tesseract를 통해 텍스트 인식하면 crop할 필요도 없어질 것이다.
다만, 추가로 확인하는 과정에서 문제가 여럿 발생했는데,
첫번째, 등급이 다른 아이템일 경우 테두리 색이 달라져 제대로 위치를 잡지 못한다.
하지만 이 문제는 보통 거래 되는 아이템은 희귀 아이템일 것이기 때문에 고려하지 않아도 될듯하다.
두번째, 비교대상 이미지의 크기가 다를 경우. 즉, 비교 템플릿 이미지와 원본 이미지에서 테두리 크기가 매칭되지 않을 경우인데
이 경우도 보통 게임 내 스크린샷 기능으로 생성된 이미지 파일 원본을 그대로 사용할 것이기 때문에 고려하지 않아도 될 것으로 보인다.
만약 고려한다면 opencv의 image pyramids 기능을 사용하면 될 것으로 보임
https://docs.opencv.org/3.4/d5/d0f/tutorial_js_pyramids.html
세번째는 오류 상황이지만 사용성을 고려했을 때 문제 되지 않을 부분인 첫번째, 두번째와 다르게
실제로 문제가 되는 부분인데 템플릿 테두리 이미지의 크기에 맞춰서 매칭되기 때문에
비교 대상 아이템의 옵션 내용이 길면 나머지 옵션이 테두리를 벗어나 측정되지 않을 상황이 생긴다.
위 문제를 해결하기 위해 두가지 정도 테스트해봤다.
1. 테두리 이미지의 세로 크기를 최대한 늘리기
(진행중)
2. cv 함수 옵션에서 세로 크기 옵션 조정하기
//let point = new cv.Point(maxPoint.x + templ.cols, maxPoint.y + templ.rows);
let point = new cv.Point(maxPoint.x + templ.cols, {원본 이미지의 높이});
기존의 코드가 템플릿 이미지의 세로만큼 테두리 크기를 잡았다면,
매칭이 시작되는 위치부터 이미지 하단까지 잡아주게 세팅되게끔 수정했다.
코드는 다음과 같다.
// html
<img id="templImageSrc" src="./contours.png" style="display: none;"/>
// script
let src = cv.imread(imgElement);
let testImgElement = document.getElementById('templImageSrc');
let templ = cv.imread(testImgElement);
let dst = new cv.Mat();
let mask = new cv.Mat();
cv.matchTemplate(src, templ, dst, cv.TM_CCOEFF, mask);
let result = cv.minMaxLoc(dst, mask);
let maxPoint = result.maxLoc;
let color = new cv.Scalar(255, 0, 0, 255);
// let point = new cv.Point(maxPoint.x + templ.cols, maxPoint.y + templ.rows);
let point = new cv.Point(maxPoint.x + templ.cols, imgElement.height); // imgElement.height 대신 src.rows도 가능
cv.rectangle(src, maxPoint, point, color, 2, cv.LINE_8, 0);
cv.imshow('canvasOutput', src);
src.delete(); dst.delete(); mask.delete();
템플릿 이미지를 폴더 내 위치에서 javascript로 바로 불러와지지 않아 일단 html에 안보이게 세팅한 후 가져오는 방법을 택했다.
기존의 maxPoint.y + templ.rows 대신 원본 이미지의 높이를 뜻하는 imgElement.height (or src.rows)로 옵션을 수정하니
원하는 결과를 얻을 수 있었다.
이제 해당 테두리 내의 이미지 데이터를 따로 잘라내어 tesseract로 텍스트 인식하기만 성공하면 될듯..!
+ 추가
맨 처음 아이디어가 떠올라 느긋하게 작업하던 중에 역시 비슷한 생각으로 거래 사이트를 만든 사람이 생겼네요.
찾아보니 디아2 레저렉션 관련 사이트를 운영중인 곳에서 만든듯한데
굉장히 잘만든데다 귀찮은 작업까지 세세하게 세팅해놔서 옵션을 상세히 조절할 수가 있더군여
엄청난 노가다를 할 생각에 opencv 기능을 찾게된 저로썬 대단하단 생각이 먼저 드네요.
실제로 발견한 이후에 해당 사이트를 통해 거래를 몇번 해봤고 예전에 poe 거래 사이트 느낌이 나서 사용감이 굉장히 좋았습니다.
디아에선 따로 애드온 사용을 허용하지 않기에 부득이하게 불편한 점이 몇가지 있었지만
업데이트 주기로 봤을 때 디아4가 꾸준히 흥행한다면 편의성 개선이 계속 될 것 같습니다.
애초에 제 프로젝트는 이정도의 퀄리티를 갖춰야지 하고 시작한게 아니었지만
그래도 이정도 완성도를 따라가야지 하고 생각하면 굉장히 아득하네요.
하지만 주눅들지 않고 계속 처음 구상했던 결과물이 나올때까진 힘써볼 생각입니다.
적어도 백엔드를 건들여보긴 해야죠
다음 포스팅에선 crop 기능 대신 opencv 템플릿 매칭으로 얻게된 이미지를 통해 텍스트 인식을 하는 과정까지 진행해볼 예정입니다.
해당 기능이 완료되면 비로소 백엔드 작업에 들어갈듯!
Github
https://github.com/cchoseonghun/Practice/tree/main/opencv
참고 사이트
https://studium-anywhere.tistory.com/22
'개인프로젝트 > OCR' 카테고리의 다른 글
디아블로4 경매 사이트 만들기 (7) - OpenCV.js Thresholding (끝) (0) | 2023.07.02 |
---|---|
디아블로4 경매 사이트 만들기 (6) - OpenCV.js Image ROI (0) | 2023.06.30 |
디아블로4 경매 사이트 만들기 (4) - Vue에서 Drag&Drop, Crop, Tesseract 사용하기 (0) | 2023.06.21 |
디아블로4 경매 사이트 만들기 (3) - 이미지 자르기 crop (0) | 2023.06.15 |
디아블로4 경매 사이트 만들기 (2) - 이미지 드래그 앤 드랍 (0) | 2023.06.15 |