디지털 이미지의 구조
디지털 이미지는 픽셀(pixel)의 2차원 배열로 표현됩니다. 각 픽셀은 이미지의 가장 작은 단위로, 색상과 밝기 정보를 담고 있습니다.
이미지 표현 방식
- 그레이스케일 - 단일 채널, 0(검정)~255(흰색)
- RGB - 3개 채널 (빨강, 녹색, 파랑), 각 0~255
- RGBA - RGB + 알파(투명도) 채널
- HSV - 색조(Hue), 채도(Saturation), 명도(Value)
import cv2
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
# 이미지 로드 (OpenCV는 BGR 순서로 로드)
img_bgr = cv2.imread('image.jpg')
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
# 이미지 정보 출력
print(f"이미지 shape: {img_rgb.shape}") # (높이, 너비, 채널)
print(f"데이터 타입: {img_rgb.dtype}")
print(f"최소값: {img_rgb.min()}, 최대값: {img_rgb.max()}")
# 그레이스케일 변환
img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
print(f"그레이스케일 shape: {img_gray.shape}")
기본 이미지 연산
이미지 리사이징
이미지 크기를 변경하는 것은 전처리의 기본입니다. 다양한 보간 방법이 있습니다.
# OpenCV로 리사이징
# 보간 방법: INTER_LINEAR(기본), INTER_CUBIC, INTER_NEAREST
resized = cv2.resize(img_rgb, (640, 480), interpolation=cv2.INTER_LINEAR)
# 비율로 리사이징
scale_percent = 50 # 원본의 50%
width = int(img_rgb.shape[1] * scale_percent / 100)
height = int(img_rgb.shape[0] * scale_percent / 100)
resized = cv2.resize(img_rgb, (width, height))
# PIL로 리사이징
from PIL import Image
pil_img = Image.fromarray(img_rgb)
pil_resized = pil_img.resize((640, 480), Image.BICUBIC)
이미지 회전 및 변환
# 회전 (중심 기준, 각도, 스케일)
height, width = img_rgb.shape[:2]
center = (width // 2, height // 2)
# 45도 회전
rotation_matrix = cv2.getRotationMatrix2D(center, 45, 1.0)
rotated = cv2.warpAffine(img_rgb, rotation_matrix, (width, height))
# 좌우 반전
flipped_horizontal = cv2.flip(img_rgb, 1)
# 상하 반전
flipped_vertical = cv2.flip(img_rgb, 0)
# 아핀 변환 (평행사변형 변환)
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
affine_matrix = cv2.getAffineTransform(pts1, pts2)
affine_img = cv2.warpAffine(img_rgb, affine_matrix, (width, height))
색상 공간 변환
다양한 색상 공간으로 변환하여 특정 작업을 쉽게 수행할 수 있습니다.
# RGB to HSV (색상 기반 작업에 유용)
img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
# 특정 색상 범위 마스킹 (예: 빨간색)
lower_red = np.array([0, 120, 70])
upper_red = np.array([10, 255, 255])
mask = cv2.inRange(img_hsv, lower_red, upper_red)
# 마스크 적용
red_only = cv2.bitwise_and(img_rgb, img_rgb, mask=mask)
# RGB to LAB (색상 보정에 유용)
img_lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB)
# RGB to YCrCb (비디오 압축에 사용)
img_ycrcb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2YCrCb)
필터링과 블러링
가우시안 블러
노이즈 제거와 이미지 스무딩에 사용됩니다.
# 가우시안 블러 적용
blurred = cv2.GaussianBlur(img_rgb, (5, 5), 0)
# 커널 크기가 클수록 더 많이 블러됨
heavily_blurred = cv2.GaussianBlur(img_rgb, (15, 15), 0)
미디언 필터
솔트 앤 페퍼 노이즈 제거에 효과적입니다.
# 미디언 필터 (커널 크기는 홀수여야 함)
median = cv2.medianBlur(img_rgb, 5)
양방향 필터
엣지를 보존하면서 노이즈를 제거합니다.
# 양방향 필터 (직경, 색상 공간 시그마, 좌표 공간 시그마)
bilateral = cv2.bilateralFilter(img_rgb, 9, 75, 75)
엣지 검출
이미지에서 경계선을 찾는 것은 많은 비전 작업의 기초입니다.
Canny 엣지 검출
# Canny 엣지 검출 (가장 인기있는 방법)
edges = cv2.Canny(img_gray, threshold1=100, threshold2=200)
# 임계값 조정
# threshold1: 약한 엣지 기준
# threshold2: 강한 엣지 기준
edges_sensitive = cv2.Canny(img_gray, 50, 150)
edges_conservative = cv2.Canny(img_gray, 150, 250)
Sobel 연산자
# x 방향 그래디언트
sobelx = cv2.Sobel(img_gray, cv2.CV_64F, 1, 0, ksize=5)
# y 방향 그래디언트
sobely = cv2.Sobel(img_gray, cv2.CV_64F, 0, 1, ksize=5)
# 그래디언트 크기
gradient_magnitude = np.sqrt(sobelx**2 + sobely**2)
gradient_magnitude = np.uint8(gradient_magnitude)
Laplacian 연산자
# Laplacian (2차 미분)
laplacian = cv2.Laplacian(img_gray, cv2.CV_64F)
laplacian = np.uint8(np.absolute(laplacian))
이미지 임계값 처리
그레이스케일 이미지를 이진 이미지로 변환합니다.
고정 임계값
# 단순 임계값 (127보다 크면 255, 작으면 0)
ret, binary = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
# 역 임계값
ret, binary_inv = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY_INV)
# Truncate
ret, trunc = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TRUNC)
# To Zero
ret, tozero = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO)
적응형 임계값
조명 변화에 강건합니다.
# 적응형 임계값 (평균 기반)
adaptive_mean = cv2.adaptiveThreshold(
img_gray, 255,
cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY,
blockSize=11,
C=2
)
# 적응형 임계값 (가우시안 기반)
adaptive_gaussian = cv2.adaptiveThreshold(
img_gray, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,
blockSize=11,
C=2
)
# Otsu의 이진화 (자동으로 최적 임계값 찾음)
ret, otsu = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(f"Otsu 임계값: {ret}")
모폴로지 연산
이진 이미지의 형태를 변경하는 연산입니다.
# 커널 정의
kernel = np.ones((5, 5), np.uint8)
# 침식 (Erosion) - 객체 축소, 노이즈 제거
erosion = cv2.erode(binary, kernel, iterations=1)
# 팽창 (Dilation) - 객체 확대, 구멍 채우기
dilation = cv2.dilate(binary, kernel, iterations=1)
# Opening - 침식 후 팽창 (작은 노이즈 제거)
opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
# Closing - 팽창 후 침식 (작은 구멍 채우기)
closing = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
# Gradient - 팽창과 침식의 차이 (윤곽선)
gradient = cv2.morphologyEx(binary, cv2.MORPH_GRADIENT, kernel)
# Top Hat - 원본과 Opening의 차이
tophat = cv2.morphologyEx(binary, cv2.MORPH_TOPHAT, kernel)
# Black Hat - Closing과 원본의 차이
blackhat = cv2.morphologyEx(binary, cv2.MORPH_BLACKHAT, kernel)
히스토그램
이미지의 밝기 분포를 분석합니다.
히스토그램 계산 및 시각화
# 그레이스케일 히스토그램
hist = cv2.calcHist([img_gray], [0], None, [256], [0, 256])
# 히스토그램 시각화
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.imshow(img_gray, cmap='gray')
plt.title('원본 이미지')
plt.axis('off')
plt.subplot(1, 2, 2)
plt.plot(hist)
plt.title('히스토그램')
plt.xlabel('픽셀 값')
plt.ylabel('빈도')
plt.show()
# 컬러 이미지의 각 채널 히스토그램
colors = ('r', 'g', 'b')
for i, color in enumerate(colors):
hist = cv2.calcHist([img_rgb], [i], None, [256], [0, 256])
plt.plot(hist, color=color)
plt.title('RGB 히스토그램')
plt.show()
히스토그램 평활화
대비를 개선하여 이미지 품질을 향상시킵니다.
# 그레이스케일 히스토그램 평활화
equalized = cv2.equalizeHist(img_gray)
# 컬러 이미지의 히스토그램 평활화 (YCrCb 공간에서)
img_ycrcb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2YCrCb)
img_ycrcb[:, :, 0] = cv2.equalizeHist(img_ycrcb[:, :, 0])
equalized_color = cv2.cvtColor(img_ycrcb, cv2.COLOR_YCrCb2BGR)
# CLAHE (Contrast Limited Adaptive Histogram Equalization)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
clahe_img = clahe.apply(img_gray)
기하학적 변환
원근 변환
# 원본 이미지의 4개 점 정의
pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
# 변환 후 위치
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
# 원근 변환 행렬
perspective_matrix = cv2.getPerspectiveTransform(pts1, pts2)
# 변환 적용
perspective_img = cv2.warpPerspective(img_rgb, perspective_matrix, (300, 300))
컨볼루션과 커널
커스텀 필터를 만들어 다양한 효과를 적용할 수 있습니다.
# 샤프닝 커널
sharpen_kernel = np.array([
[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]
])
sharpened = cv2.filter2D(img_rgb, -1, sharpen_kernel)
# 엠보싱 커널
emboss_kernel = np.array([
[-2, -1, 0],
[-1, 1, 1],
[0, 1, 2]
])
embossed = cv2.filter2D(img_gray, -1, emboss_kernel)
# 블러 커널 (박스 필터)
box_kernel = np.ones((5, 5), np.float32) / 25
box_blur = cv2.filter2D(img_rgb, -1, box_kernel)
이미지 증강
머신러닝 학습을 위해 데이터를 확장하는 기법입니다.
import albumentations as A
# 증강 파이프라인 정의
transform = A.Compose([
A.RandomRotate90(p=0.5),
A.Flip(p=0.5),
A.Transpose(p=0.5),
A.OneOf([
A.GaussNoise(p=1),
A.GaussianBlur(p=1),
A.MotionBlur(p=1),
], p=0.5),
A.OneOf([
A.OpticalDistortion(p=1),
A.GridDistortion(p=1),
A.ElasticTransform(p=1),
], p=0.3),
A.ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.2, rotate_limit=45, p=0.5),
A.OneOf([
A.CLAHE(clip_limit=2),
A.Sharpen(),
A.Emboss(),
], p=0.3),
A.HueSaturationValue(p=0.3),
])
# 증강 적용
augmented = transform(image=img_rgb)
augmented_img = augmented['image']
요약
- 디지털 이미지는 픽셀의 배열로, RGB, 그레이스케일 등 다양한 형식이 있습니다
- 기본 연산으로 리사이징, 회전, 반전 등의 변환을 수행합니다
- 색상 공간 변환(RGB, HSV, LAB)으로 다양한 작업을 효율적으로 수행합니다
- 필터링(가우시안, 미디언, 양방향)으로 노이즈를 제거하고 이미지를 개선합니다
- 엣지 검출(Canny, Sobel)로 이미지의 경계를 찾습니다
- 임계값 처리로 그레이스케일을 이진 이미지로 변환합니다
- 모폴로지 연산으로 이진 이미지의 형태를 조정합니다
- 히스토그램 분석 및 평활화로 이미지 대비를 개선합니다
- 弘益人間 - 이미지 처리 기술로 의료 진단, 품질 검사 등 인류에 이로운 응용을 만듭니다
복습 문제
- RGB와 HSV 색상 공간의 차이점은 무엇이며, 각각 어떤 상황에서 유용한가요?
- 가우시안 블러와 미디언 필터의 차이점을 설명하고, 각각 어떤 노이즈에 효과적인가요?
- Canny 엣지 검출의 주요 단계를 설명하세요.
- 적응형 임계값이 고정 임계값보다 나은 경우는 언제인가요?
- 모폴로지 연산 중 Opening과 Closing의 차이를 설명하고 각각의 용도를 제시하세요.
- CLAHE가 일반 히스토그램 평활화보다 나은 이유는 무엇인가요?
- 이미지 증강이 머신러닝 모델 학습에 왜 중요한가요?