Major Study./Computer Science

python에서 opencv를 사용하여 image crop하기

sosal 2017. 2. 11. 21:10
반응형

/*

 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)



이미지가 잘 불러와졌는지, imshow 함수로 확인해본다.


이제는 gray scale로 바꿔준다.
컬러풀한 이미지가 edge를 찾는것에는 도움이 되지 않는 경우가 있기 때문이다.

>>> image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
>>> cv2.imshow("ImageShow", image_gray)
>>> cv2.waitKey(0)




가우시안 블러를 넣어주는 경우도 많다.
>>> image_gray_blurred = cv2.GaussianBlur(image_gray, (5, 5), 0)



이제 5가지의 threshold 방법을 통해 이미지 결과를 볼 것이다.


>>> 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)

hstack, vstack은 수평, 수직으로 array를 합쳐주는 역할을 한다.
따라서 이미지를 겹쳐주는 부분이다.

>>> 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', images_combined )
>>> cv2.waitKey(0)
>>> cv2.destroyAllWindows()



가장 좌상단부터 우측방향으로 원본이미지, 그리고 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])