IT/AI\ML

[python/openCV] openCV로 이미지 여백 삭제 (1)

개발자 두더지 2021. 7. 6. 23:06
728x90

 opencv로 여백을 삭제하는 두 가지 예제 코드를 살펴 보는 포스팅을 하고자 한다. 참고로 여백 삭제를 위해서는 openCV이외에 Pillow 라이브러리를 사용할 수 있지만,  두 개의 포스팅 모두 openCV를 사용할 것이다.

 첫 번째 예제 코드의 포스팅에서 사용할 이미지는 다음의 회로도이다. 

 여백을 삭제해 다음과 같이 이미지를 회로도만 보이게 할 것이다.

 

 

여백 삭제의 코드 흐름


 이미지 여백 삭제를 위해 다음과 같은 흐름으로 코드를 작성할 것이다.

1) 이미지를 읽어 들인다.

2) 색 공간을 이진화한다.

3) 윤곽을 추출한다.

4) 윤곽의 안에서 부터 좌표가 최소 그리고 최대가 되는 x, y 각각을 취득하고, 그 값을 이용해 직사각형인 이미지를 추출한다.

 1번째에서 3번째까지는 물체 인식이나 문자 인식에 잘 사용되는 방법이라고 생각된다.

 

 

여백을 삭제하는 함수


 먼저 여백을 삭제하는 함수를 다음과 같이 정의했다.  이것이 이번 코드의 핵심이 되는 부분이다.

# 이미지를 삭제하는 함수
def crop(image): # 인수는 이미지의 상대 경로
    # 이미지를 읽어들인다.
    img = cv2.imread(image)

    # 주위 부분을 강제적으로 트리밍
    h, w = img.shape[:2]
    h1, h2 = int(h * 0.05), int(h * 0.95)
    w1, w2 = int(w * 0.05), int(w * 0.95)
    img = img[h1: h2, w1: w2]
    # cv2.imshow('img', img)

    # Grayscale으로 변환 (흑백 이미지로 변환)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # cv2.imshow('gray', gray)

    # 색 공간을 이진화한다.
    img2 = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)[1]
    # cv2.imshow('img2', img2)

    # 윤곽을 추출한다.
    contours = cv2.findContours(img2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]

    # 윤곽의 좌표를 리스트에 대입한다.
    x1 = [] #x좌표의 최소값
    y1 = [] #y좌표의 최소값
    x2 = [] #x좌표의 최대값
    y2 = [] #y좌표의 최대값
    for i in range(1, len(contours)):# i = 1 는 이미지 전체의 외곽이되므로 카운트에 포함시키지 않는다.
        ret = cv2.boundingRect(contours[i])
        x1.append(ret[0])
        y1.append(ret[1])
        x2.append(ret[0] + ret[2])
        y2.append(ret[1] + ret[3])

    # 외곽의 첫 번째 외곽을 오려냄
    x1_min = min(x1)
    y1_min = min(y1)
    x2_max = max(x2)
    y2_max = max(y2)
    cv2.rectangle(img, (x1_min, y1_min), (x2_max, y2_max), (0, 255, 0), 3)

    crop_img = img2[y1_min:y2_max, x1_min:x2_max]
    # cv2.imshow('crop_img', crop_img)

    return img, crop_img

 

 

해설


 위 코드에 대해 하나 하나 어떤 의미를 갖는지 살펴보도록 하자.

- 이미지를 읽어들인다.

  img = cv2.imread(image)

- 이미지를 강제로 잘라낸다.

 아래는 원래 필요하지 않지만, 이번의 이미지는 첫 번째 외곽쪽에 좌표와 같은 것이 들어가 있으므로 외곽 추출로 그곳을 추출하지 않도록 하기 위해 강제적으로 그 부분을 삭제한다.

 shape[]는 행의 수, 열의 수, 채널 수를 반환하므로, 필요한 처음의 두 가지를 취득한다.

 4번째 행에서 슬라이스하여 이미지의 주변을 강제적으로 삭제한다.

    h, w = img.shape[:2]
    h1, h2 = int(h * 0.05), int(h * 0.95)
    w1, w2 = int(w * 0.05), int(w * 0.95)
    img = img[h1: h2, w1: w2]

- grayscale화 (흑백 이미지화)

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

- 색 공간의 이진화

 색 공간을 이진화한다. threshold는 임계값의 의미로 threshold()함수는 첫 번째 인수에 흑백 이미지를, 두 번째 이미지를 임계 값을, 세 번째 인수에는 임계값 이상의 값을 가진 값에 할당할 값을 지정하고, 네 번째 인수에는 임계값에 어떠한 처리를 할 것인가를 지정한다.

 두 번째의 리턴 값이 임계 값 처리된 이진화 이미지이므로 [1]에서는 2번 째의 리턴값만 취득한다.

    img2 = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)[1]

- 윤곽 추출

 윤곽을 추출한다. contour은 "윤곽"이라는 의미이다. findContours() 함수는 첫 번째 인수에 이미지를, 두 번째에 인수에는 추출 모드를, 세 번째 인수에는 근사 방법을 지정한다.

 추출 모드는 RETR_LIST, RETR_EXTERNAL, RETR_CCOMP, RETR_TREE 중 선택할 수 있다. 반환 값에 대해서는 각각 주의할 필요가 있다.

 findContours()함수는 OpenCV의 버전에 따라 반환 값이 달라진다. OpenCV3까지는 반환값이 3개였지만, OpenCV4에서는 2개로 줄어 들었다. 

 OpenCV4에서는 첫 번째의 반환값으로 추출된 운곽 리스트가, 두 번째의 반환값에는 계층 구조의 리스트가 저장되어 있다. 따라서 인덱스[0]으로 하면, 윤곽의 리스트를 얻어낼 수 있다. 

    contours = cv2.findContours(img2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]

- 윤곽의 좌표를 리스트에 대입

 윤곽의 좌표를 각각의 리스트에 추가한다. 여기서 i=1는 이미지 전체의 외곽이 되므로 제외한다.

 boundingRect는 윤곽의 직사각형의, 왼쪽 위의 x,y 좌표를 첫 번째, 두 번째 반환값으로, 그리고 폭과 높이를 세 번째, 네 번째 인수에 반환하므로, 이것을 ret 리스트에 대입한다. ret은 return의 단축한 단어이다.

 물체 인식이므로, 여기서 너무 작은 윤곽이나 너무 큰 윤곽을 제거하는 것도 있다.

    x1 = [] #x좌표의 최소값
    y1 = [] #y좌표의 최소값
    x2 = [] #x좌표의 최대값
    y2 = [] #y좌표의 최대값
    for i in range(1, len(contours)):
        ret = cv2.boundingRect(contours[i])
        x1.append(ret[0])
        y1.append(ret[1])
        x2.append(ret[0] + ret[2])
        y2.append(ret[1] + ret[3])

- 제일 바깥쪽의 직사각형으로 잘라낸다. 아까 전, 작성하고 싶은 리스트로부터 각각의 최소값, 최대값을 얻어낸다. 이러한 값을 두 점으로 하는 직사각형으로 이미지를 잘라내면, 여백을 삭제한 전체 부분을 얻어 낼 수 있다. 

 rectangle() 함수는 첫 번째 인수에 이미지를, 두 번째 그리고 세 번째 인수에는 얻어낸 직사각형의 왼쪽 위와 오른 쪽 아래의 좌표를 지정하고, 네 번째 인수에는 직사각형의 선의 색을 RGB의 8비트로 지정하고, 마지막으로 다섯 번째 인수에는 선의 굵기를 지정한다. 

    x1_min = min(x1)
    y1_min = min(y1)
    x2_max = max(x2)
    y2_max = max(y2)
    cv2.rectangle(img, (x1_min, y1_min), (x2_max, y2_max), (0, 255, 0), 3)

    crop_img = img2[y1_min:y2_max, x1_min:x2_max]

 


참고자료

https://qiita.com/trami/items/e25eb70a59a51ae4f7ba

728x90