티스토리 뷰

❖ openCV란?

  • OpenCV(Open Source Computer Vision Library) 는 컴퓨터 비전 및 이미지, 영상 처리에 특화된 오픈 소스 라이브러리 입니다.
  • 컴퓨터 비전이란 컴퓨터가 인간처럼 이미지나 비디오와 같은 시각적인 정보를 이해하고 해석할 수 있도록 하는 기술입니다.
  • 이미지나 영상 속에 담긴 정보를 분석하고 의미를 추출합니다.
  • c++, python, java등 다양한 프로그램밍 언어를 지원합니다.

❖ 활용도

필자는 로봇 관제 시스템에서 로봇이 움직일 수 있는 구역(범위)을 정의해야 합니다. 그러나 맵이 매우 넓기 때문에 손으로 하나씩 구역을 정의하는 것은 시간이 오래 걸리고 지루한 작업입니다. 그래서 테스트용 맵의 경우 대략적으로 구역을 정의하고, 해당 좌표를 출력하여 로봇이 움직일 수 있는 범위를 계산하는 데 활용하였습니다.

❖ 설치(python)

pip3 install opencv-python

❖ 이미지 분석

이미지 읽기

  • cv2.imread(filename, flags=cv2.IMREAD_COLOR)
    • filename : 읽어들일 이미지 파일의 경로
    • flags: 이미지를 읽어들일 때 사용되는 옵션 플래그
      • cv2.IMREAD_COLOR: 이미지를 BGR 색상으로 읽어들입니다. 투명한 부분은 무시됩니다. 기본값입니다.
      • cv2.IMREAD_GRAYSCALE: 이미지를 그레이스케일(흑백)로 읽어들입니다. 많은 이미지 처리 작업에서 중간 단계로 많이 사용됩니다.
      • cv2.IMREAD_UNCHANGED: 이미지를 알파 채널까지 포함하여 읽어들입니다. 투명도 정보가 필요한 경우 사용합니다.
    • 반환값
      cv2.imread는 성공적으로 이미지를 읽어오면 해당 이미지를 NumPy 배열 형태로 반환합니다. 만약 파일을 찾을 수 없거나 읽는 데 실패하면 None을 반환합니다.
  • 예제
import cv2

# 이미지 읽어오기
img = cv2.imread('image.jpg', cv2.IMREAD_COLOR)

# 이미지 출력 (창을 띄워서 보여주기)
cv2.imshow('Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

이미지의 색상 공간을 변환

  • cv2.cvtColor(src, code, dst=None, dstCn=None)
    • src : 변환할 소스 이미지 입니다.
    • code : 변환할 색상 공간을 지정하는 코드입니다.
      • cv2.COLOR_BGR2GRAY: BGR 이미지를 그레이스케일(흑백) 이미지로 변환합니다.
      • cv2.COLOR_BGR2RGB: BGR 이미지를 RGB 이미지로 변환합니다.
      • cv2.COLOR_BGR2HSV: BGR 이미지를 HSV(Hue, Saturation, Value) 색공간으로 변환합니다.
      • cv2.COLOR_BGR2YUV: BGR 이미지를 YUV 색공간으로 변환합니다.
      • cv2.COLOR_BGR2LAB: BGR 이미지를 LAB 색공간으로 변환합니다.
      • 굉장히 많은 code정보가 존재합니다.
        https://docs.opencv.org/3.4/d8/d01/group__imgproc__color__conversions.html
    • dst: 변환된 결과를 저장할 대상 이미지입니다. 생략하면 함수 내부에서 새로 생성됩니다.
    • dstCn: 출력 이미지의 채널 수입니다. 생략하면 자동으로 계산됩니다.
  • BGR 형식의 이미지를 RGB형식으로 변환하거나 컬러 이미지를 흑백이미지로 변환하는 등의 작업에 활용됩니다.
  • 색상 공간 변환을 하는 이유는?
    • 특정 색상 성분 강조: HSV 색공간은 색조, 채도, 명도를 분리하여 표현하기 때문에 특정 색상성분을 강조하거나 추출하는데 유용합니다.
    • 노이즈 제거 : 특정 색상 공간에서 노이즈가 더 잘드러나거나 제거하기 쉽습니다.
    • 객체인식 : 특정 색생을 가진 객체를 더 쉽게 인식 할 수 있습니다.
    • 이미지 처리 알고리즘 : 특정 알고리즘은 특정 색상 공간에서 더 효율적으로 작동합니다.
    • 이미지 분할, 객체 추적, 생삭 보정등에 활용될 수 있습니다.
  • 예제
import cv2

# 이미지 파일 경로
image_path = 'path/to/image.jpg'

# 이미지를 컬러로 읽어오기
image = cv2.imread(image_path)

# BGR 이미지를 그레이스케일로 변환
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# BGR 이미지를 RGB로 변환
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# 변환된 이미지 확인
cv2.imshow('Gray Image', image_gray)
cv2.imshow('RGB Image', image_rgb)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

이미지 이진화

  • cv2.threshold(src, thresh, maxval, type)
    • src : 이진화할 이미지입니다.
    • thresh
      • 픽셀 값을 구분하는 임계값으로 픽셀 값이 이 값보다 크거나 작은지에 따라 다른 값을 할당합니다.
      • 예를 들어 thresh=127인 경우 127보다 큰값은 maxval로 작은 값은 0으로 설정됩니다.
    • maxval
      • 임계값 조건에 따라 픽셀 값이 변경될 때 사용할 최대값입니다.
      • 예를 들어 이진화 모드에서 픽셀 값이 임계값을 넘는 경우 maxval 이 할당됩니다.
    • type
      • 임계처리 방식을 지정하는 옵션입니다.
      • cv2.THRESH_BINARY: 기본 이진화 방식입니다. 픽셀 값이 thresh 이상이면 maxval로, 그렇지 않으면 0으로 설정합니다.
      • cv2.THRESH_BINARY_INV: cv2.THRESH_BINARY의 반대로, thresh 이상이면 0으로, 그렇지 않으면 maxval로 설정합니다.
      • cv2.THRESH_TRUNC: 픽셀 값이 thresh를 넘으면 thresh 값으로 설정하고, 이하는 그대로 유지합니다.
      • cv2.THRESH_TOZERO: 픽셀 값이 thresh를 넘지 않으면 0으로 설정하고, 넘으면 그대로 유지합니다.
      • cv2.THRESH_TOZERO_INV: cv2.THRESH_TOZERO의 반대로, 픽셀 값이 thresh를 넘으면 0으로 설정하고, 넘지 않으면 그대로 유지합니다.
      • cv2.THRESH_OTSU: Otsu의 이진화 방식을 사용하여 임계값을 자동으로 계산합니다. thresh 값이 무시되며, 일반적으로 그레이스케일 이미지에서 객체와 배경을 구분할 때 사용됩니다.
      • cv2.THRESH_TRIANGLE: Otsu 방식과 유사하게 자동으로 임계값을 계산하지만, 이미지가 단봉 분포가 아닌 경우 유용합니다.
  • 예제
import cv2

# 이미지 불러오기 및 그레이스케일 변환
image = cv2.imread("example.jpg", cv2.IMREAD_GRAYSCALE)

# 임계처리 적용
_, binary_thresh = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
_, binary_inv_thresh = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY_INV)
_, trunc_thresh = cv2.threshold(image, 127, 255, cv2.THRESH_TRUNC)
_, tozero_thresh = cv2.threshold(image, 127, 255, cv2.THRESH_TOZERO)
_, tozero_inv_thresh = cv2.threshold(image, 127, 255, cv2.THRESH_TOZERO_INV)

# 결과 표시
cv2.imshow("Original Image", image)
cv2.imshow("Binary Threshold", binary_thresh)
cv2.imshow("Binary Inv Threshold", binary_inv_thresh)
cv2.imshow("Trunc Threshold", trunc_thresh)
cv2.imshow("ToZero Threshold", tozero_thresh)
cv2.imshow("ToZero Inv Threshold", tozero_inv_thresh)

cv2.waitKey(0)
cv2.destroyAllWindows()


---
# Otsu의 이진화 예제
_, otsu_thresh = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

print("Calculated Otsu Threshold:", _)

# 결과 표시
cv2.imshow("Otsu Threshold", otsu_thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()

---
# 가우시안 블러 적용 후 Otsu의 이진화
blurred = cv2.GaussianBlur(image, (5, 5), 0)
_, otsu_blur_thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# 결과 표시
cv2.imshow("Otsu with Gaussian Blur", otsu_blur_thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()

이미지 윤곽선 검출

  • cv2.findContours(image, mode, method)
    • image : 윤곽선을 찾을 입력이미지입니다. 일반적으로 그레이스케일 이미지여야 하며, 이진화된 이미지(픽셀값이 0 또는 255인 이미지)
    • mode: 윤곽선을 찾는 방법을 지정하는 플래그입니다.
      • cv2.RETR_EXTERNAL: 최상위 레벨 외곽선만 탐지합니다. 즉, 내부의 중첩된 외곽선은 무시하고 외곽의 가장 큰 외곽선만 반환합니다.
      • cv2.RETR_LIST: 모든 외곽선을 계층 구조 없이 단일 목록으로 반환합니다. 외곽선의 계층 관계는 중요하지 않은 경우 사용합니다.
      • cv2.RETR_CCOMP: 외곽선을 두 레벨로 분리하여 반환합니다. 최상위 외곽선이 첫 번째 레벨이고, 구멍을 포함한 내부 외곽선이 두 번째 레벨에 포함됩니다.
      • cv2.RETR_TREE: 모든 외곽선을 트리 구조로 반환하여 외곽선 간 부모-자식 관계를 제공합니다. 복잡한 계층 구조 분석이 필요한 경우에 유용합니다.
    • method: 외곽선을 근사화하여 점의 개수를 줄이는 방식으로, 저장할 외곽선의 세부 정도를 결정합니다.
      • cv2.CHAIN_APPROX_NONE: 모든 외곽선 점을 반환합니다. 경로상의 모든 점이 포함되므로 세밀한 외곽선을 유지할 수 있지만, 메모리와 성능에 부담이 될 수 있습니다.
      • cv2.CHAIN_APPROX_SIMPLE: 외곽선을 단순화하여, 수평, 수직, 대각선 방향의 점들을 하나의 끝점으로 줄입니다. 이는 메모리 사용을 줄이는 데 유용하며, 대부분의 경우 충분한 외곽선 정보를 제공합니다.
      • cv2.CHAIN_APPROX_TC89_L1cv2.CHAIN_APPROX_TC89_KCOS: Teh-Chin 연산자를 사용하여 곡선을 근사화하는 방법으로, 더 복잡한 근사화가 필요할 때 사용됩니다. 비교적 덜 사용되는 옵션입니다.
  • 반환값
    • contours: 탐지된 모든 외곽선의 목록이며, 각각의 외곽선은 점들의 배열로 나타난다.
    • hierarchy: 각 외곽선의 계층 구조를 나타내는 배열입니다. 객체 간 계층 관계(부모, 자식 관계 등)를 파악하는 데 사용됩니다.
  • 예제
import cv2
import numpy as np

# 이미지 불러오기 및 그레이스케일 변환
image = cv2.imread("example.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 이미지 이진화 (임계값 처리)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# 외곽선 검출
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 검출된 외곽선 그리기
cv2.drawContours(image, contours, -1, (0, 255, 0), 2)

# 결과 표시
cv2.imshow("Contours", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

 이미지 윤곽선 검출

  • cv2.drawContours(image, contours, contourIdx, color, thickness, lineType=cv2.LINE_8, hierarchy=None, maxLevel=0, offset=(0, 0))
    • image: 윤곽선을 그릴 대상 이미지입니다. 이 이미지는 컬러(BGR) 또는 그레이스케일일 수 있습니다. 윤곽선은 이 이미지에 직접 그려집니다.
    • contours: 그릴 윤곽선의 리스트입니다. 일반적으로 cv2.findContours 함수를 통해 얻은 윤곽선 리스트를 전달합니다.
    • contourIdx: 그릴 윤곽선의 인덱스입니다.
      • 1을 전달하면 모든 윤곽선을 그립니다.
      • 특정 인덱스(예: 0, 1, ...)를 전달하면 해당 인덱스의 윤곽선만 그립니다.
    • color: 윤곽선의 색상입니다. BGR 형식의 튜플로 지정합니다. 예: (0, 255, 0)은 초록색.
    • thickness: 윤곽선의 두께입니다. 음수 값을 전달하면 윤곽선을 채웁니다. 예: 2, 1.
    • lineType: 선의 종류입니다. 기본값은 cv2.LINE_8이며, cv2.LINE_AA (안티-에일리어싱 선) 등도 사용할 수 있습니다.
    • hierarchy: 윤곽선의 계층 구조입니다. 일반적으로 cv2.findContours의 반환값 중 하나인 hierarchy를 전달합니다.
    • maxLevel: 재귀적으로 그릴 윤곽선의 최대 레벨입니다. 0은 현재 윤곽선만, 1은 자식 윤곽선까지, 1은 모든 레벨의 윤곽선을 그립니다.
    • offset: 윤곽선을 그릴 때 적용할 오프셋입니다. 기본값은 (0, 0)입니다.
import cv2
import numpy as np

# 이미지 불러오기 및 그레이스케일 변환
image = cv2.imread("example.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 이미지 이진화 (임계값 처리)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# 외곽선 검출
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 검출된 외곽선 그리기
cv2.drawContours(image, contours, -1, (0, 255, 0), 2)

# 결과 표시
cv2.imshow("Contours", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

모폴로지 연산(노이즈 제거, 객체 분리, 이미지 크기 조절)

  • cv2.morphologyEx(src, op, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)
    • src: 입력 이미지
    • op: 수행할 모폴로지 연산 종류
      • 침식(cv2.MORPH_ERODE): 이미지의 경계선을 깎아내는 연산입니다. 작은 객체를 제거하거나, 구멍을 채우는 데 사용됩니다.
      • 팽창(cv2.MORPH_DILATE): 이미지의 경계선을 확장하는 연산입니다. 객체를 키우거나, 작은 구멍을 메우는 데 사용됩니다.
      • 열림(cv2.MORPH_OPEN): 침식 후 팽창을 수행하는 연산입니다. 작은 객체를 제거하고, 이미지를 부드럽게 만드는 데 사용됩니다.
      • 닫힘(cv2.MORPH_CLOSE): 팽창 후 침식을 수행하는 연산입니다. 작은 구멍을 메우고, 이미지의 윤곽을 명확하게 만드는 데 사용됩니다.
      • 그래디언트(cv2.MORPH_GRADIENT): 팽창 이미지에서 침식 이미지를 뺀 결과입니다. 객체의 경계선을 강조하는 데 사용됩니다.
      • 탑햇(cv2.MORPH_TOPHAT): 원본 이미지에서 열림 이미지를 뺀 결과입니다. 밝은 작은 객체를 강조하는 데 사용됩니다.
      • 블랙햇(cv2.MORPH_BLACKHAT): 닫힘 이미지에서 원본 이미지를 뺀 결과입니다. 어두운 작은 구멍을 강조하는 데 사용됩니다.
    • kernel: 구조 요소 (커널)
      • cv2.getStructuringElement(shape, ksize, anchor=None)
      • shape
        • cv2.MORPH_RECT: 직사각형 모양.
        • cv2.MORPH_ELLIPSE: 타원형 모양.
        • cv2.MORPH_CROSS: 십자가 모양.
      • ksize : 크기
      • 예제
      kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
      
    • dst: 출력 이미지 (선택 사항)
    • anchor: 커널의 기준점 (선택 사항)
    • iterations: 연산 반복 횟수 (선택 사항)
    • borderType: 경계 처리 방법 (선택 사항)
      • 이미지 경계 처리 방식
      • cv2.BORDER_CONSTANT: 외부 픽셀은 borderValue로 채움.
      • cv2.BORDER_REFLECT: 경계를 반사시킴.
      • cv2.BORDER_REPLICATE: 가장자리 픽셀 값을 복제.
    • borderValue: 경계 값 (선택 사항)
      • borderType=cv2.BORDER_CONSTANT일 때 사용할 경계 값. 기본값은 0입니다.
  • 활용 예시
    • 노이즈 제거: 작은 노이즈를 제거하기 위해 열림 연산을 사용합니다.
    • 객체 분리: 객체의 경계선을 강조하기 위해 그래디언트 연산을 사용합니다.
    • 텍스트 인식: 텍스트 영역을 강조하기 위해 탑햇 연산을 사용합니다.
    • 이미지 전처리: 다른 영상 처리 알고리즘의 성능을 향상시키기 위한 전처리 단계로 사용됩니다.

❖ 기본 예제 코드

- 기본 정보를 바탕으로 이미지를 분석하는 코드의 예제입니다.

- 이미지별로 옵션값을 수정하며 최적화가 필요합니다.

import cv2
import numpy as np

# 메인 함수에서 사용
def main():
    original_image = cv2.imread('image2.png')
    if original_image is None:
        print("이미지를 불러올 수 없습니다.")
        return
    
    # 이미지 복사
    image = original_image.copy()

    # 그레이스케일 변환
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # 이미지 이진화 (랙 영역 검출)
    # _, binary = cv2.threshold(gray, 242, 255, cv2.THRESH_BINARY)
    _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

    #모폴로지 연산
    kernel = np.ones((2,2), np.uint8)
    # binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations=1)
    # binary = cv2.morphologyEx(binary, cv2.MORPH_DILATE, kernel, iterations=2)
    binary = cv2.morphologyEx(binary, cv2.MORPH_GRADIENT, kernel, iterations=8)

    # 랙영역 찾기
    # contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

    # 랙영역 색칠
    # cv2.drawContours(image, contours, -1, (0, 255, 0), thickness=cv2.FILLED)
    for i, contour in enumerate(contours):
        if hierarchy[0][i][0] != -1:
            cv2.fillPoly(image, [contour], (0, 255, 0))  # 내부 객체 색칠

    # 색칠한 랙영역의 이진 데이터를 다시 가져온다.
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

    spacing = 8  # 점 간의 간격 (픽셀 단위)
    for y in range(0, binary.shape[0], spacing):
        for x in range(0, binary.shape[1], spacing):
            if binary[y, x] == 0:
                cv2.circle(image, (x, y), 2, (0, 0, 255), -1)  # 빨간색 점 찍기
   
    # 결과 표시
    cv2.imshow('Original', original_image)
    cv2.imshow('rackimage', binary)
    cv2.imshow('image', image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()

댓글
공지사항