프로젝트 진행사항 기록 및 차후 기록을 남겨둔다.
먼저 진행하고 있는 프로젝트에서 가장 중요한것은 이미지 전처리라고 생각된다.
해당 이미지에서, 어떻게 보드판의 정보를 가져와서 장애물 위치를 자동으로 등록을 할까?
이를 해결하기 위해서, 먼저 우리는 임의의 사진들에서 저 보드판을 인식해야 했다.
그러나 사용자에 따라 캡쳐 방식이 다양하고, 또 해당 게임(메이플 스토리)의 특성상 '캐시무기아이템'이 보드영역을 넘어와 인식에 방해를 줄 수도 있다.
본문에서는 해당 이미지를 어떻게 불러왔고, 어떻게 처리하였고, 어떻게 검출을 완료했는지에 대해서 작성하고자 한다.
https://opencv-python.readthedocs.io/en/latest/index.html를 참고하였다.
OpenCV-Python Study documentation! — gramman 0.1 documentation
© Copyright 2016, gramman. Revision 20fa215b.
opencv-python.readthedocs.io
현재는 해당 미니게임 이벤트가 진행중이지 않아서 직접 이미지를 캡쳐하지 못했고, 인터넷에 있는 사진을 사용했다.
따라서 캐릭터 및 닉네임은 imwrite로 사진 저장후 편집으로 모자이크 처리하였다.
이미지 불러오기
import cv2
image = cv2.imread('images/cap2.png', flags = cv2.IMREAD_COLOR)
original = image.copy()
cv2.imshow('original',original)
의 방식으로 이미지를 불러올 수 있다.
flag는 3개가 있는데,
- cv2.IMREAD_COLOR : 이미지 파일을 Color로 읽어들입니다. 투명한 부분은 무시되며, Default값입니다.
- cv2.IMREAD_GRAYSCALE : 이미지를 Grayscale로 읽어 들입니다. 실제 이미지 처리시 중간단계로 많이 사용합니다.
- cv2.IMREAD_UNCHANGED : 이미지파일을 alpha channel까지 포함하여 읽어 들입니다.
와 같다.
cv2.imwrite로 이미지를 저장할 수 있다.
불러온 이미지는 다음과 같다.
이미지 흑백처리
이미지를 흑백으로 만들면 연산량이 적고, edge detection에 무리가 없다.
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow('gray',gray)
이미지 색상을 변환하기 위해 cv2.cvtColor 함수를 사용하였다.
이미지를 흑백, Grayscale로 변환하기 위해 cvtColor의 flag로 cv2.COLOR_BGR2GRAY를 넣어 gray로 변환해주었다.
이미지를 변환하기 위한 코드는 OpenCV Color Space Conversions 에 정리되어 있다.
이미지 Smoothing
이후 kernelsize를 설정하여, GaussianBlur처리를 해준다.
이미지의 Gaussian Noise를 제거하는데 효과적이다.
이전의 unit test에서 보드의 한칸 한칸의 색이 2가지로 이루어졌다고 생각했으나, 실제 측정해보니 동일하지 않았다.
Gaussian Blur로 노이즈를 제거하고자 하였다.
ksize=(3,3)
blur = cv2.GaussianBlur(gray, ksize=ksize, sigmaX=0)
cv2.imshow('blurIMG', blur)
그러나 어떤 커널 사이즈가 가장 적합한지 판단이 서지 않았기 때문에, 여러 커널과 비교하여 적합한 커널크기를 찾아내고자 한다.
이미지 Threshold
다음은 Blur처리한 이미지를 이진화 하기위한 단계이다. 이미지의 값이 임계값 보다 크면 백(White), 작으면 흑이 된다.
openCV에서는 이진화를 위해 cv2.threshold()함수를 제공한다.
retBi, threshBi = cv2.threshold(blur,127,255,cv2.THRESH_BINARY)
s = f'{ksize}_BinaryTHR'
cv2.imshow(s, threshBi)
cv2.threshold의 type은
- cv2.THRESH_BINARY
- cv2.THRESH_BINARY_INV
- cv2.THRESH_TRUNC
- cv2.THRESH_TOZERO
- cv2.THRESH_TOZERO_INV
과 같고, 각 type의 결과는
와 같다.
임계값을 127로 설정하였을 경우는
그러나 적당히 게임 보드를 추출해 내기 위해서는, 적당한 임계값을 찾아야 했다.
Otsu threshold
그러나 임계점을 찾기 위해서 단순히 (0+255)/2 인 127을 사용하거나, 직접 찾는것 보다는 임계값을 어느정도 계산하여 사용하는것이 더욱 적합할 것이다.
otsu threshold를 사용하기 위해서는 cv2.threshold 함수에 임계값을 0으로 전달하고, type에 cv2.THRESH_OTSU를 추가로 적용하면 된다.
이제 조금 더 명확하게 보드를 검출할 수 있게 되었다.
그러나 이후 Morphological Transformation, Image Contour를 적용하기 위해서는 보드가 흰색으로 검출되어야 하기 때문에, type을 cv2.THRESH_BINARY_INV로 전달하였다.
Morphological Transformations
Morphologicla Transformation은 이미지를 Segmentation하여 단순화, 제거, 보정을 통해서 형태를 파악하는 목적으로 사용된다. 일반적으로 Binary나 GrayScale 이미지에 사용된다.
사용방법으로는 Erosion(침식), Dilation(팽창)이 있고, 두가지 방식을 조합한 Opening, Closing 방식이 있다.
Erosion
각 Pixel에 structuring element (kernel)를 적용하여 하나라도 0이 있으면 대상 pixel을 제거하는 방법이다. 다음 그림은 대상 이미지에 십자형 structuring element를 적용한 결과이다.
cv2.erode() 함수를 사용해서 적용할 수 있다.
Dilation
Erosion과 반대로 대상을 확장한 후 작은 구멍을 채우는 방법이다. Erosion과 마찬가지로 각 Pixel에 structuring element를 적용한다. 대상 pixel에 대해서 OR 연산을 수행한다. 즉 겹치는 부분이 하나라도 있으면 이미지를 확장한다.
cv2.dilate() 함수를 사용해서 적용할 수 있다. (출처에서는 dilation으로 되어있다. 주의요망)
structuring element
structuring element는 kernel로, openCV에서 제공되는 함수나 numpy로 만들 수 있다.
cv2.getStructuringElement() 함수를 이용하여 만들 수 있고, shape으로는
- cv2.MORPH_RECT
- cv2.MORPHCROSS
- cv2.MORPH_ELLIPSE
가 있다.
Opening & Closing
Opening과 Closing은 Erosion과 Dilation의 조합이다. 어느것을 먼저 적용하느냐에 따라 구분된다.
- Opeing : Erosion적용 후 Dilation 적용. 작은 Object나 돌기 제거에 적합
- Closing : Dilation적용 후 Erosion 적용. 전체적인 윤곽 파악에 적합
테스트 결과 일반적인 Dilation과 Erosion으로는 게임 보드를 검출해내기 어려웠다. 아무래도 얇은 선들이 여럿 검출되기 때문에, 세로 kernel과 가로 kernel을 이용하여 Opening 방식을 2회 적용하고자 하였다.
vertikernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,50))
openedINV = cv2.morphologyEx(threshINV, cv2.MORPH_OPEN, vertikernel)
cv2.imshow('openingVerti', openedINV)
horikernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,1))
openedINV = cv2.morphologyEx(openedINV, cv2.MORPH_OPEN, horikernel)
cv2.imshow('openingHori', openedINV)
원하던 완벽한 네모의 형태는 아니지만, 이제 거의 게임 보드를 인식할 수 있게 되었다!
이미지 Contours
- 정확도를 높히기 위해서 Binary Image를 사용합니다. threshold나 canny edge를 선처리로 수행합니다.
- cv2.findContours() 함수는 원본 이미지를 직접 수정하기 때문에, 원본 이미지를 보존 하려면 Copy해서 사용해야 합니다.
- OpenCV에서는 contours를 찾는 것은 검은색 배경에서 하얀색 대상을 찾는 것과 비슷합니다. 그래서 대상은 흰색, 배경은 검은색으로 해야 합니다.
Contours는 윤곽선으로, 이미지에서 같은 강도를 갖는 경계선을 연결한 선이다.
- 정확도를 높이기 위해 Binary 이미지를 사용하며, Threshold나 Canny Edge를 선처리로 수행한다.
- cv2.findContours()함수는 원본 이미지를 직접 수정한다. 따라서 원본 이미지를 보존하기 위해서 Copy하여 사용한다.
- OpenCV에서 Contours를 찾는 것은 검은 배경에서 하얀 대상을 찾는것이기 대문에, 대상은 흰색, 배경은 검은색으로 해야한다.
cv2.findContours는 윤곽선을 찾는 함수이다.
cv2.findcontours(image, mode, method)
returns: image, contours, hierarchy
- mode - 윤곽선을 찾는 방법
- cv2.RETR_EXTERNAL : contours line중 가장 바같쪽 Line만 찾음
- cv2.RETR_LIST : 모든 contours line을 찾지만, hierachy 관계를 구성하지 않음
- cv2.RETR_CCOMP : 모든 contours line을 찾으며, hieracy관계는 2-level로 구성함
- cv2.RETR_TREE : 모든 contours line을 찾으며, 모든 hieracy관계를 구성함
- method - 윤곽선을 찾을 때 사용하는 근사치 방법
- cv2.CHAIN_APPROX_NONE : 모든 contours point를 저장
- cv2.CHAIN_APPROX_SIMPLE : contours line을 그릴 수 있는 point 만 저장. (ex; 사각형이면 4개 point)
- cv2.CHAIN_APPROX_TC89_L1 : contours point를 찾는 algorithm
- cv2.CHAIN_APPROX_TC89_KCOS : contours point를 찾는 algorithm
어떤 mode와 어떤 method가 도움이 될지 모르기 때문에, 모두 적용하여 비교해보고자 하였다.
cv2.drawContours는 image에 contours가 그려진 결과를 반환하는 함수이다.
cv2.drawContours(image, contours,contourIdx, color, thickness)
modes = [cv2.RETR_EXTERNAL, cv2.RETR_LIST, cv2.RETR_CCOMP, cv2.RETR_TREE]
methods = [cv2.CHAIN_APPROX_NONE, cv2.CHAIN_APPROX_SIMPLE, cv2.CHAIN_APPROX_TC89_L1,\
cv2.CHAIN_APPROX_TC89_KCOS]
for mode in modes:
for method in methods:
contours, hierarchy = cv2.findContours(openedINV.copy(), mode, method)
contours_image = cv2.drawContours(original.copy(), contours, -1, (0,255,0), 3)
name = f'blogIMG/CONTOUR_{method}_{mode}.jpg'
cv2.imshow(name,contours_image)
cv2.imwrite(name, contours_image)
cv2.waitKey()
해당 이미지에서는 cv2.CHAIN_APPROX_NONE method를 사용하는것이 가장 효과적으로 보드를 검출할 수 있었다.
따라서 현재 보유하고있는 4장의 sample에서도 동일한 결과를 얻어내는지 확인하고자 하였다.
대략적인 보드 검출은 다양한 상황에서 잘 하고있는것 같다!
다음에는 보드만을 네모 반듯하게 추출하는 방법에 대해서 알아보도록 하겠다.
'좌충우돌개발일지' 카테고리의 다른 글
HumanML3D Dataset 준비하기 (3) | 2024.11.13 |
---|---|
Welcome to Hogwarts! (3) | 2024.09.29 |
정사각형으로 외곽선 검출 (0) | 2023.01.31 |