python에서 opencv를 사용하여 image crop하기
/*
* http://sosal.kr/
* made by so_Sal
*/
이미지에서 원하는 도형에 해당되는 부분을 찾아, crop을 해보고자 한다.
opencv 라이브러리를 사용할 것이다.
이 포스팅에서는 위의 사진에서 네모난 사진들을 crop해볼것이다.
네모난 박스를 체크하고 해당 부분을 잘라 저장할 것이다.
1. python opencv library 설치 및 기타라이브러리 설치
python 버전을 확인한다. (3.6 버전)
http://www.lfd.uci.edu/~gohlke/pythonlibs/ 사이트에 접속하여 버전에 맞는 opencv whl 파일을 다운받는다.
윈도우즈 64비트와 python 버전 3.6을 사용하고 있기 때문에
opencv_python-3.2.0-cp36-cp36m-win_amd64.whl 파일을 다운로드 받았다.
cmd 창을 실행하여 (시작키 + R을 눌러 실행창을 띄운 후 cmd 입력)
해당 whl 파일이 저장된 위치로 이동하여
pip install "opencv_whl 파일명" 을 통해 설치해준다.
numpy, imultis 2개의 패키지도 설치가 필요합니다.
>>> pip install imutils
>>> pip install numpy
전 이미 numpy가 깔려있다고 뜨네요.
2. 이미지 thresholding
>>> import cv2
>>> import numpy as np
>>> import imutils
>>> image = cv2.imread("image.jpg")
>>> cv2.imshow("ImageShow", image)
>>> cv2.waitKey(0)
가장 좌상단부터 우측방향으로 원본이미지, 그리고 threshold 1 ~ threshold 5에 해당된다.
1, 2, 4번 어느것을 써도 충분히 잘 사각형이 detection 될 것 같다.
여기서는 threshold 2번을 사용할 것이다.
3. 사각형을 찾아 해당 부분 crop하기
# 모양을 구분해주는 ShapeDetector
class ShapeDetector:
def __init__(self):
pass
def detect(self, c):
# initialize the shape name and approximate the contour
shape = "unidentified"
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.04 * peri, True)
# if the shape is a triangle, it will have 3 vertices
if len(approx) == 3:
shape = "triangle"
# if the shape has 4 vertices, it is either a square or
# a rectangle
elif len(approx) == 4:
# compute the bounding box of the contour and use the
# bounding box to compute the aspect ratio
(x, y, w, h) = cv2.boundingRect(approx)
ar = w / float(h)
# a square will have an aspect ratio that is approximately
# equal to one, otherwise, the shape is a rectangle
shape = "square" if ar >= 0.95 and ar <= 1.05 else "rectangle"
# if the shape is a pentagon, it will have 5 vertices
elif len(approx) == 5:
shape = "pentagon"
# otherwise, we assume the shape is a circle
else:
shape = "circle"
# return the name of the shape
return shape
cv2.findContours 함수는 윤곽이 있는 부분을 찾아주는 함수이다.
>>> sd = ShapeDetector()
>>> cnts = cv2.findContours(thresh2.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
>>> cnts = cnts[0] if imutils.is_cv2() else cnts[1]
찾아진 contours 들은 cnt에 저장된다.
len(cnts)를 출력해보니 67이 뜨는데, 아마 각 네모난 사진 뿐 만 아니라 여러 사진들이 더 검출되었을 것이다.
일단 찾아진 cnts 들을 사진에 표시해보자.
for c in cnts:
if sd.detect(c) != 'rectangle': next
c = c.astype("float")
c = c.astype("int")
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(thresh2,(x,y),(x+w,y+h),(3,255,4),2)
cv2.imshow("image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
역시, 원하는 사진과 다르게 원치 않는 윤곽선도 많이 잡은 것을 확인할 수 있다.
따라서 width, height에 해당하는 w, h 변수의 길이를 출력하여 원하는 사각형의 w, h 길이를 살펴보자
2 - 3
40 - 25
18 - 18
29 - 31
29 - 31
1 - 1
22 - 27
33 - 42
72 - 74
67 - 74
67 - 74
67 - 74
29 - 41
67 - 75
67 - 75
43 - 76
68 - 76
.....
(하략)
대략 width는 60~70 사이, height는 70~80 사이 인 듯 하다.
적당히 조건을 걸어주고 다시 rectangle을 detection 해보자.
이미 image에 rectangle을 잔뜩 그려놨기 때문에, 다시 image를 로드해야 새롭게 rectangle을 그릴 수 있다.
image = cv2.imread("image.jpg")
for c in cnts:
if sd.detect(c) != 'rectangle': next
c = c.astype("float")
c = c.astype("int")
x,y,w,h = cv2.boundingRect(c)
if not (55 < w < 75 and 80 < h < 95):
continue
cv2.rectangle(image,(x,y),(x+w,y+h),(3,255,4),2)
cv2.imshow("image", image)
cv2.waitKey(0)
원하는 이미지만 crop 된 것을 확인할 수 있다.
4. 전체 소스
import imutils
import cv2
import numpy as np
image = cv2.imread("image.jpg")
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image_gray_blurred = cv2.GaussianBlur(image_gray, (5, 5), 0)
ret,thresh1 = cv2.threshold(image_gray_blurred,127,255,cv2.THRESH_BINARY)
ret,thresh2 = cv2.threshold(image_gray_blurred,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3 = cv2.threshold(image_gray_blurred,127,255,cv2.THRESH_TRUNC)
ret,thresh4 = cv2.threshold(image_gray_blurred,127,255,cv2.THRESH_TOZERO)
ret,thresh5 = cv2.threshold(image_gray_blurred,127,255,cv2.THRESH_TOZERO_INV)
images_row1 = np.hstack([image_gray_blurred, thresh1, thresh2])
images_row2 = np.hstack([thresh3, thresh4, thresh5])
images_combined = np.vstack((images_row1, images_row2))
cv2.imshow('Images', image )
cv2.waitKey(0)
cv2.destroyAllWindows()
######
class ShapeDetector:
def __init__(self):
pass
def detect(self, c):
# initialize the shape name and approximate the contour
shape = "unidentified"
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.04 * peri, True)
# if the shape is a triangle, it will have 3 vertices
if len(approx) == 3:
shape = "triangle"
# if the shape has 4 vertices, it is either a square or
# a rectangle
elif len(approx) == 4:
# compute the bounding box of the contour and use the
# bounding box to compute the aspect ratio
(x, y, w, h) = cv2.boundingRect(approx)
ar = w / float(h)
# a square will have an aspect ratio that is approximately
# equal to one, otherwise, the shape is a rectangle
shape = "square" if ar >= 0.95 and ar <= 1.05 else "rectangle"
# if the shape is a pentagon, it will have 5 vertices
elif len(approx) == 5:
shape = "pentagon"
# otherwise, we assume the shape is a circle
else:
shape = "circle"
# return the name of the shape
return shape
sd = ShapeDetector()
cnts = cv2.findContours(thresh2.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]
count = 0
for c in cnts:
if sd.detect(c) != 'rectangle': next
c = c.astype("float")
c = c.astype("int")
x,y,w,h = cv2.boundingRect(c)
if not (55 < w < 75 and 80 < h < 95):
print("skip" + str(w) + " - " + str(h))
continue
count = count+1
cv2.imwrite("output/Img"+str(count)+".jpg", image[y: y + h, x: x + w])