🎨 OpenCV
OpenCV - [ 기초 실습 ]
date
Jul 5, 2023
slug
opencv-01
author
status
Public
tags
Python
OpenCV
summary
OpenCV 라이브러리 실습
type
Post
thumbnail
category
🎨 OpenCV
updatedAt
Jul 28, 2023 07:31 AM
OpenCV 기초 실습
OpenCV(Open Source Computer Vision) 는 실시간 컴퓨터 비전을 목적으로 한 프로그래밍 라이브러리이다. OpenCV 는 TensorFlow, PyTorch 의 딥러닝 프레임워크를 지원한다.
OpenCV 기본 사용법
# pip install opencv-python import cv2 # OpenCV: 이미지, 영상 처리 라이브러리 import numpy as np # Numpy: 벡터, 행렬 연산 라이브러리 import matplotlib.pyplot as plt # Matplotlib: 시각화 라이브러리
# 이미지 파일 읽어오기 cat_image = cv2.imread('cat.jpg', cv2.IMREAD_COLOR) # IMREAD_COLOR, IMREAD_GRAYSCALE, IMREAD_UNCHANGED # 이미지 파일 출력하기 plt.imshow(cv2.cvtColor(cat_image , cv2.COLOR_BGR2RGB)) # 기본이 BGR 이기 때문에 RGB 로 바꿔주기 plt.show()
이미지 연산
이미지의 특정 픽셀 범위를 변경하고 싶을 때는 두 가지 방법이 있다.
- 반복문을 이용해서 픽셀 하나하나 변경하는 방법
- mask 를 이용해서 한 번에 바꾸는 방법
이중에서 numpy 를 지원하는 OpenCV 는 mask 를 사용해서 한 번에 바꾸는 방법이 훨씬 빠른 속도로 처리한다.
# 반복문을 사용하는 방법 for i in range(0, 100): for j in range(0, 100): image[i, j] = [255, 255, 255] # 흰색으로 변환 # => 0.005003690719604492 seconds # mask 를 사용하는 방법 image[0:100, 0:100] = [255, 255, 255] # => 0.0 seconds
뿐만 아니라 mask 를 사용하면 ROI(region of interest) 추출 및 복사가 훨씬 수월해 진다.
roi = image[100:250, 100:300] image[0:150, 0:200] = roi plt.imshow(cv2.cvtColor(cat_image, cv2.COLOR_BGR2RGB)) plt.show()
이미지 크기 조절
# cv2.resize(image, dsize, fx, fy, interpolation) dsize: Manual Size # 이미지 확대 expand = cv2.resize(cat_image, None, fx=1.2, fy=1.2, interpolation=cv2.INTER_CUBIC) # INTER_CUBIC: 사이즈를 키울 때 # 이미지 축소 shrink = cv2.resize(cat_image, None, fx=0.8, fy=0.8, interpolation=cv2.INTER_AREA) # INTER_AREA: 사이즈를 줄일 때 images = [expand, shrink] titles = ['expand', 'shrink'] fig, ax = plt.subplots(ncols=2, nrows=1, figsize=(15, 10)) ax = ax.flatten() for idx, img in enumerate(images): ax[idx].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) ax[idx].set_title(titles[idx]) plt.suptitle('resize', fontsize=25, y=0.9) plt.tight_layout() plt.show()
각각 1.2배, 0.8배가 되었다.
잠깐 보간 방법에 대한 설명 그래프를 살펴보자.
이미지 변형
# cv2.warpAffine(image, M, dsize) M: 변환 행렬, dsize: Manual Size height, width = cat_image.shape[:2] # (h, w, c) 형태이므로 # 이미지 이동 M = np.float32([[1, 0, 50], [0, 1, 10]]) # M: 변환 행렬 => 각 픽셀들 기준 (1, 1) -> (50, 10) 이동 warp = cv2.warpAffine(cat_image, M, (width, height)) plt.imshow(cv2.cvtColor(warp, cv2.COLOR_BGR2RGB)) plt.show()
참고로 Affin 이란, 유클리드 공간의 아핀 기하학적 성질들을 일반화해서 만들어지는 구조를 말한다.
이미지가 x 축으로 50, y 축으로 10만큼 이동했다.
변환 행렬이 과 같은 형태일 때, 이미지의 모든 좌표 (x, y)는
행렬곱 연산 을 통해
좌표 (x+50, y+10)로 이동한다.
# cv2.getRotationMatrix2D(center, angle, scale) height, width = cat_image.shape[:2] # 이미지 회전 M = cv2.getRotationMatrix2D((width / 2, height / 2), 90, 0.5) warp = cv2.warpAffine(cat_image, M, (width, height)) plt.imshow(cv2.cvtColor(warp, cv2.COLOR_BGR2RGB)) plt.show()
이미지의 중심을 기준으로 90도 회전하고 사이즈는 0.5배가 됐다.
무게 중심을 적용할 수 있는 회전 변환 식은 복잡하니 넘어가도록 하겠다.
참고로 이미지 회전을 위한 기본적인 변환 행렬은 이다.
이미지 합치기
# 이미지 합치기 # cv2.add() : Saturation 연산(0보다 작으면 0, 255보다 크면 255) # np.add() : Modulo(나머지를 계산하는 연산) background = cv2.imread('background.jpg') keyboard = cv2.imread('keyboard.png') saturation = cv2.add(background, keyboard) modulo = background + keyboard images = [saturation, modulo] titles = ['saturation', 'modulo'] fig, ax = plt.subplots(ncols=2, nrows=1, figsize=(15, 10)) ax = ax.flatten() for idx, img in enumerate(images): ax[idx].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) ax[idx].set_title(titles[idx]) plt.suptitle('add', fontsize=25, y=0.8) plt.tight_layout() plt.show()
임계점 처리
# 이미지 이진화 # cv2.threshold(image, thresh, max_value, type) image = cv2.imread('gray_image.jpg', cv2.IMREAD_GRAYSCALE) ret, thresh1 = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY) # THRESH_BINARY: 임계 값보다 크면 max_value, 작으면 0 ret, thresh2 = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY_INV) # THRESH_BINARY_INV: 임계 값보다 작으면 max_value, 크면 0 ret, thresh3 = cv2.threshold(image, 127, 255, cv2.THRESH_TOZERO) # THRESH_TOZERO: 임계 값보다 크면 그대로, 작으면 0 ret, thresh4 = cv2.threshold(image, 127, 255, cv2.THRESH_TOZERO_INV) # THRESH_TOZERO_INV: 임계 값보다 크면 0, 작으면 그대로 ret, thresh5 = cv2.threshold(image, 127, 255, cv2.THRESH_TRUNC) # THRESH_TRUNC: 임계 값보다 크면 임계 값, 작으면 그대로 images = [thresh1, thresh2, thresh3, thresh4, thresh5] titles = ['THRESH_BINARY', 'THRESH_BINARY_INV', 'THRESH_TOZERO', 'THRESH_TOZERO_INV', 'THRESH_TRUNC'] fig, ax = plt.subplots(ncols=2, nrows=3, figsize=(12, 12)) ax = ax.flatten() for idx, img in enumerate(images): ax[idx].imshow(cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)) ax[idx].set_title(titles[idx]) plt.tight_layout() plt.show()
이미지의 “적응” 임계점 처리
# 이미지 이진화 # cv2.adaptiveThreshold(image, max_value, adaptive_method, type, block_size, C) # adaptive_method: 임계 값을 결정하는 계산 방법 # ADAPTIVE_THRESH_MEAN_C: 주변영역의 평균값으로 결정 # ADAPTIVE_THRESH_GAUSSIAN_C # block_size: 임계 값을 적용할 영역의 크기 # C: 평균이나 가중 평균에서 차감할 값 image = cv2.imread('hand_writing_image.jpg', cv2.IMREAD_GRAYSCALE) ret, thres1 = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY) thres2 = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 21, 3) images = [image, thres1, thres2] titles = ['image', 'THRESH_BINARY', 'ADAPTIVE_THRESH_MEAN_C'] fig, ax = plt.subplots(ncols=3, nrows=1, figsize=(15, 5)) ax = ax.flatten() for idx, img in enumerate(images): ax[idx].imshow(cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)) ax[idx].set_title(titles[idx]) plt.suptitle('adaptive threshold', fontsize=25, y=0.9) plt.show()
Adaptive Threshold를 이용하면, 전체 픽셀을 기준으로 임계값을 적용하지 않는다.
두 번째 사진에서 지장이 찍힌 부분을 보면, 전체 픽셀을 기준으로 임계값이 적용되어 지문을 잘 파악할 수 없다.
하지만 세 번째 이미지에서는 임계값을 주변 영역의 평균값으로 결정했기 때문에 지문을 더 잘 파악할 수 있다.
즉, 광원이 한 개일 때 이미지 내에서 어두운 부분이 생길 수 있는데 이때 어두운 부분에도 광원이 있는 효과를 볼 수 있게 된다.
Contours
입력 이미지는 Gray Scale Threshold 전처리 과정이 필요하다.
# Contours 찾기 # cv2.findContours(image, mode, method) # mode: Contour들을 찾는 방법 # RETR_EXTERNAL: 바깥쪽 Line만 찾기 # RETR_LIST: 모든 Line을 찾지만, Hierarchy 구성 X # RETR_TREE: 모든 Line을 찾으며, 모든 Hierarchy 구성 O # method: Contour들을 찾는 근사치 방법 # CHAIN_APPROX_NONE: 모든 Contour 포인트 저장 # CHAIN_APPROX_SIMPLE: Contour Line을 그릴 수 있는 포인트만 저장
# Contours 그리기 # cv2.drawContours(image, contours, contour_index, color, thickness) # contour_index: 그리고자 하는 Contours Line (전체: -1)
# Contours 의 사각형 외곽 찾기, Convex Hull 찾기, 유사 다각형 찾기 # (1) 흑백으로 변환 (contours 는 input 으로 흑백 이미지를 받기 때문에) image = cv2.imread('digit_image.png') image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(image_gray, 230, 255, 0) thresh = cv2.bitwise_not(thresh) # 이미지 처리에서 비트별 NOT 연산은 이미지의 픽셀 값을 반전 # 이 연산은 0을 1로, 1을 0으로 바꾸어 이진 값의 반전 plt.imshow(cv2.cvtColor(thresh, cv2.COLOR_BGR2RGB)) plt.show()
# (2) contours 찾기 contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # (3) contours 그리기 image = cv2.drawContours(image, contours, -1, (0, 0, 255), 3) plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) plt.show()
# (4) 사각형 외곽 찾기 contour = contours[0] x, y, w, h = cv2.boundingRect(contour) image = cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 3) plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) plt.show()
# (5) Convex Hull 찾기 hull = cv2.convexHull(contours[0]) # contours[n]: 숫자를 바꿔가며 실행시켜 보자 image = cv2.drawContours(image, [hull], -1, (255, 0, 0), 3) plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) plt.show()
# (6) 유사 다각형 찾기 epsilon = 0.005 * cv2.arcLength(contours[0], True) # arcLength(윤곽선, 닫혀있는지 여부): 윤곽선의 길이 # 윤곽선 길이에 0.01을 곱한 값을 epsilon 변수에 할당 => 윤곽선 근사화에 사용되는 최대 거리 approx = cv2.approxPolyDP(contour, epsilon, True) image = cv2.drawContours(image, [approx], -1, (255, 0, 0), 3) plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) plt.show()
Dementia_Analysis 프로젝트 때 사용한 뇌 MRI 이미지로 contour 다시 해보기
(출처: 새싹교육 나동빈 멘토님 강의내용)