|
|
[7장 연습문제]
# [ 7장 ]
# 1. 입력 영상의 화소와 마스크의 구성으로 회선 알고리즘을 상세히 설명하시오
# 회선 알고리즘은 입력 영상의 개별 화소와 특정 가중치를 가진 마스크를 결합하여 새로운 화소값을 생성하는 과정으로
한 픽셀 위에 마스크라는 특정 연산을 실행하는 틀을 올려 각각 모든 화소에 대해 한번에 찍어낸다.
# 먼저 연산의 대상이 되는 입력 영상의 화소 데이터와 필터의 성질을 결정하는 행렬인 마스크가 필요하다. 마스크는 보통
중심점을 기준으로 상하좌우가 대칭인 홀수 크기의 행렬로 구성되며 각 칸에는 연산의 강도를 결정하는 계수가 적혀 있다.
# 마스크가 배치되면 마스크의 각 계수와 그 아래 겹쳐진 입력 영상의 화소값을 일대일로 대응시켜 각각 곱하는 화소 대
화소 곱셈을 수행한 후 얻어진 모든 곱셈 결과값들을 하나로 합산해 출력 영상에서 입력 영상과 동일한 좌표 위치에 새로운
화소값으로 기록된다.
# 결과적으로 회선 알고리즘은 모든 화소 위치에서 주변 화소 정보를 마스크의 가중치에 따라 재구성하는 병렬적 연산
체계이며 마스크의 수치 구성에 따라 영상을 부드럽게 만들거나 테두리를 검출하는 등의 다양한 효과를 만들어낸다.
#
# 2. 블러링과 샤프닝을 비교 설명하시오
# 블러링은 인접한 픽셀들을 서로 섞어 화소 간의 밝기 차이를 줄이는 과정으로, 마스크 내부의 계수를 모두 양수로 구성하여 주변 화소들의 평균값을 취하는 방식을 사용한다.
# 이를 통해 영상의 거친 부분을 매끄럽게 다듬고 자잘한 노이즈를 제거할 수 있으나, 물체의 경계선이 희미해지며 영상이
전체적으로 뿌옇게 변하는 특성이 있다.
# 샤프닝은 인접한 픽셀 간의 밝기 차이를 의도적으로 키워 윤곽을 도드라지게 만드는 과정으로 마스크 중심에는 큰 양수
가중치를 두고 주변에는 음수를 배치하여, 기준 화소와 이웃 화소 간의 격차를 극대화하는 원리를 사용한다.
# 따라서 사물의 테두리가 뚜렷해지고 선명도가 높아지지만, 영상이 거칠어지거나 숨어 있던 미세한 노이즈가 강조될 수 있다.
#
# 3. 에지 검출 방법의 종류를 아는 대로 쓰시오
# 에지 검출은 영상 내에서 화소의 밝기가 급격하게 변하는 지점을 찾는 과정으로, 화소 값의 변화율을 측정하는 미분 개념을 응용한 마스크 연산을 통해 수행되며 1차 미분, 2차 미분, 그리고 여러 단계를 결합한 복합 알고리즘 방식으로 나뉜다.
# 1차 미분 기반 방식은 화소 값의 변화량인 그래디언트 크기를 이용하며 대표적으로 소벨, 프리윗, 로버츠 연산 등이
존재하고, 2차 미분 기반 방식은 밝기 변화의 변화율이 0이 되는 지점인 제로 크로싱을 포착하여 에지를 찾으며 라플라시안과 LoG가 이에 해당한다.
# 마지막으로 캐니 에지 검출과 같이 노이즈 제거와 에지 추적 공정을 결합하여 정확도를 극대화한 정교한 알고리즘이 현대
영상 처리에서 주로 사용된다.
#
# 4. 대표적인 1차 미분 마스크의 종류와 특징에 대해 설명하시오
# 1차 미분 마스크는 영상의 밝기 변화가 발생하는 지점을 그래디언트 연산을 통해 검출하는 도구로, 마스크 계수의 합은
항상 0이 된다. 로버츠 연산은 2x2 크기의 아주 작은 마스크를 사용하여 대각선 방향의 밝기 차이를 계산하며, 연산 속도가
매우 빠르지만 주변 화소 정보를 적게 반영하므로 노이즈에 극히 취약하다.
# 프리윗 연산은 3x3 크기의 마스크를 사용하여 수평 및 수직 방향의 차이를 계산하며, 마스크 계수에 별도의 가중치를 두지
않고 주변 화소와의 차이를 균일하게 반영하여 소벨에 비해 에지가 거칠게 나타나는 경향이 있다.
# 소벨 연산은 현재 가장 널리 쓰이는 1차 미분 마스크로, 중심 화소 주변에 더 큰 가중치를 부여함으로써 인접한 화소와의
차이를 보다 효과적으로 계산하며 프리윗보다 노이즈의 영향을 적게 받으면서 안정적인 에지 검출 결과를 제공한다.
#
# 5. 2차 미분 마스크의 종류와 특징에 대해 설명하시오
# 2차 미분 마스크는 밝기 변화의 변화율을 측정하여 에지를 검출하며, 1차 미분보다 밝기 변화에 더욱 민감하게 반응하여
세밀한 에지를 찾는 데 유리하다.
# 라플라시안 연산은 모든 방향의 에지를 한 번에 검출할 수 있는 등방성 필터로, 밝기 변화가 일어나는 지점에서 결과값의
부호가 바뀌는 제로 크로싱 현상을 이용해 에지 위치를 특정한다. 그러나 미분을 두 번 수행하는 특성상 영상의 미세한
노이즈까지 크게 증폭시킬 수 있어 단독으로 사용되는 경우는 드물다.
# 이러한 문제를 보완하기 위해 고안된 LoG는 가우시안 블러링을 선행하여 노이즈를 사전에 제거한 뒤 라플라시안 연산을
수행함으로써, 라플라시안의 정밀함은 유지하면서도 노이즈에 대한 안정성을 확보한 2차 미분 방식이다.
#
# 6. 캐니 에지 알고리즘의 과정을 설명하시오
# 캐니 에지 알고리즘은 노이즈에 강하면서도 단일 화소 두께의 정교한 에지를 검출하기 위해 고안된 다단계 공정으로,
첫 번째 단계인 가우시안 필터링에서는 영상에 포함된 노이즈를 제거하여 이후 연산의 오차를 줄이고, 두 번째 단계에서는
소벨 연산자 등을 이용해 각 화소의 그래디언트 크기와 방향을 계산한다.
# 세 번째 단계인 비최대 억제 과정에서는 그래디언트 방향을 따라 주변 화소와 값을 비교하여 국부적 최댓값이 아닌 화소를 제거함으로써 에지를 가늘게 만든다. 마지막으로 이중 임계값 처리를 통해 높은 임계값보다 큰 값을 강한 에지로, 두 임계값
사이의 값을 약한 에지로 분류한 뒤 강한 에지와 연결된 약한 에지만 최종 결과에 포함시켜 끊기지 않는 최적의 윤곽선을
도출한다.
#
# 7. 최댓값, 최솟값 필터링에 대해서 설명하시오
# 최댓값 필터링과 최솟값 필터링은 마스크 영역 내 화소들 중 가장 큰 값이나 가장 작은 값을 선택하여 중심 화소를 교체하는 비선형 공간 필터링 기법으로, 최댓값 필터링은 마스크 범위 내에서 가장 밝은 화소값을 선택하기 때문에 영상 내의 어두운
노이즈를 제거하는 데 중점을 두며 결과 영상이 전체적으로 밝아지고 밝은 영역이 확장되는 특성을 보인다.
# 반대로 최솟값 필터링은 마스크 범위 내에서 가장 어두운 화소값을 선택하여 중심 화소에 할당하므로 밝은 지점의 노이즈를 억제하는 데 유리하며 영상이 전체적으로 어두워지고 어두운 영역이 넓어진다.
#
# 8. 평균값 필터링과 미디언 필터링을 비교설명하시오
# 평균값 필터링과 미디언 필터링은 영상의 노이즈를 제거하기 위해 사용되는 대표적인 공간 필터링 기법이지만 연산 방식과 결과가 다르게 나온다. 평균값 필터링은 마스크 영역 내 모든 화소값의 산술 평균을 계산하여 중심 화소에 할당하는
선형 연산으로, 영상의 전체적인 밝기 변화를 부드럽게 만들어 가우시안 노이즈 등을 억제하는 데 효과적이지만 모든 화소값을 고르게 섞는 특성상 에지 성분까지 함께 뭉개져 영상이 흐릿해지는 블러링 현상이 발생한다.
# 반면 미디언 필터링은 마스크 영역 내 화소값들을 크기순으로 정렬한 뒤 그중 중앙값을 선택하는 비선형 연산으로,
픽셀값이 주변과 무관하게 튀는 솔트 앤 페퍼 노이즈 제거에 효과적이고 주변 값들과 섞이지 않고 존재하는 값 중 하나를
선택하기 때문에 노이즈는 효과적으로 제거하면서도 에지의 선명도를 상대적으로 잘 유지한다는 장점이 있다.
#
# 9. 모폴로지의 연산 종류에 대해서 적고, 각각 설명하시오
# 모폴로지는 영상 내 객체의 형태를 분석하고 수정하는 기법으로 주로 이진 영상에서 잡음 제거, 구멍 메우기,
객체 분리 등을 목적으로 사용된다. 먼저 침식 연산은 마스크 영역 내의 최솟값을 선택하여 객체의 크기를 축소시키며,
이를 통해 작은 돌출부나 가느다란 노이즈가 제거된다.
# 반대로 팽창 연산은 마스크 영역 내의 최댓값을 선택하여 객체의 크기를 확장시키고 내부의 작은 구멍이나 끊어진 부분을
메우는 효과가 있다. 이 두 기초 연산을 조합한 열기 연산은 침식 후 팽창을 적용하는 방식으로 객체의 크기는 유지하면서
돌출된 노이즈를 제거하는 데 사용되며, 닫기 연산은 팽창 후 침식을 적용하여 객체 내부의 빈틈을 채우고 분리된 부분을
연결하는 데 사용된다.
#
# 10. filter() 함수를 이용해서 컬러 영상에서 블러링과 샤프닝을 구현해 보자
import cv2
import numpy as np
def optimized_separable_blur(img, ksize, sigma):
kernel_1d = cv2.getGaussianKernel(ksize, sigma)
result = np.zeros_like(img)
for i in range(3):
result[:, :, i] = cv2.filter2D(img[:, :, i], -1, kernel_1d.T)
result[:, :, i] = cv2.filter2D(result[:, :, i], -1, kernel_1d)
return result
def optimized_sharpening(img, ksize, sigma, alpha=0.5):
blurred = optimized_separable_blur(img, ksize, sigma)
sharpened = cv2.addWeighted(img, 1.0 + alpha, blurred, -alpha, 0)
return sharpened
img = cv2.imread('best_frame.jpg')
if img is None:
print("이미지 파일을 읽을 수 없습니다. 경로를 확인하세요.")
else:
result = optimized_separable_blur(img, 3, 1.0)
cv2.imshow('optimized separable Blur Result', result)
result = optimized_sharpening(img, 3, 1.0)
cv2.imshow('optimized sharpening Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 11. 1차 미분 연산을 수행하도록 마스크를 생성하여 직접 회선을 수행하시오(3가지 연산 마스크를 적용)
import cv2
import numpy as np
def optimized_separable_blur(img, ksize, sigma):
kernel_1d = cv2.getGaussianKernel(ksize, sigma)
result = np.zeros_like(img)
for i in range(3):
result[:, :, i] = cv2.filter2D(img[:, :, i], -1, kernel_1d.T)
result[:, :, i] = cv2.filter2D(result[:, :, i], -1, kernel_1d)
return result
def apply_derivative(img, mx, my):
gx = cv2.filter2D(img, cv2.CV_32F, mx)
gy = cv2.filter2D(img, cv2.CV_32F, my)
dst = cv2.magnitude(gx, gy)
return np.clip(dst, 0, 255).astype(np.uint8)
img = cv2.imread('best_frame.jpg')
if img is None:
print("이미지를 찾을 수 없습니다.")
else:
blurred_img = optimized_separable_blur(img, 3, 1.0)
# Roberts
roberts_x = np.array([[1, 0], [0, -1]], dtype=np.float32)
roberts_y = np.array([[0, 1], [-1, 0]], dtype=np.float32)
# Prewitt
prewitt_x = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=np.float32)
prewitt_y = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]], dtype=np.float32)
# Sobel
sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=np.float32)
sobel_y = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]], dtype=np.float32)
res_roberts = apply_derivative(blurred_img, roberts_x, roberts_y)
res_prewitt = apply_derivative(blurred_img, prewitt_x, prewitt_y)
res_sobel = apply_derivative(blurred_img, sobel_x, sobel_y)
cv2.imshow('Roberts Edge', res_roberts)
cv2.imshow('Prewitt Edge', res_prewitt)
cv2.imshow('Sobel Edge', res_sobel)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 12. 2차 미분 연산을 수행하는 함수로 에지 검출을 수행하시오(2가지 함수 이상)
import cv2
import numpy as np
src = cv2.imread('best_frame.jpg', cv2.IMREAD_GRAYSCALE)
if src is None: exit()
blur = cv2.GaussianBlur(src, (3, 3), 0)
# 라플라시안
kernel = np.array([[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1]], dtype=np.float32)
custom_lap = cv2.filter2D(blur, cv2.CV_32F, kernel)
lap_result = cv2.convertScaleAbs(custom_lap)
# 캐니
gx = cv2.Sobel(blur, cv2.CV_32F, 1, 0)
gy = cv2.Sobel(blur, cv2.CV_32F, 0, 1)
mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)
nms = np.zeros_like(mag)
h, w = mag.shape
for y in range(1, h - 1):
for x in range(1, w - 1):
deg = angle[y, x] % 180
if (0 <= deg < 22.5) or (157.5 <= deg <= 180):
p1, p2 = mag[y, x+1], mag[y, x-1]
elif (22.5 <= deg < 67.5):
p1, p2 = mag[y-1, x+1], mag[y+1, x-1]
elif (67.5 <= deg < 112.5):
p1, p2 = mag[y-1, x], mag[y+1, x]
else:
p1, p2 = mag[y-1, x-1], mag[y+1, x+1]
if mag[y, x] >= p1 and mag[y, x] >= p2:
nms[y, x] = mag[y, x]
custom_canny_logic = cv2.convertScaleAbs(nms)
# 결과 출력
cv2.imshow('Custom Laplacian', lap_result)
cv2.imshow('Custom Canny Logic (NMS)', custom_canny_logic)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 13. 각기 다른 함수로 블러링을 수행하도록 프로그램을 작성하시오(3가지 함수 이상)
import cv2
import numpy as np
# 평균 블러
def manual_average_blur(img, ksize):
kernel = np.ones((ksize, ksize), np.float32) / (ksize**2)
result = np.zeros_like(img)
for i in range(3):
result[:, :, i] = cv2.filter2D(img[:, :, i], -1, kernel)
return result
# 가우시안 블러
def optimized_gaussian_blur(img, ksize, sigma):
kernel_1d = cv2.getGaussianKernel(ksize, sigma)
result = np.zeros_like(img)
for i in range(3):
tmp = cv2.filter2D(img[:, :, i], -1, kernel_1d.T)
result[:, :, i] = cv2.filter2D(tmp, -1, kernel_1d)
return result
# 미디언 블러
'''
def manual_median_blur(img, ksize):
pad = ksize // 2
h, w, c = img.shape
padded = np.pad(img, ((pad, pad), (pad, pad), (0, 0)), mode='edge')
result = np.zeros_like(img)
# 이 부분은 연산량이 너무 많아 속도가 느려 기존 함수로 대체
for y in range(h):
for x in range(w):
roi = padded[y:y+ksize, x:x+ksize, :]
for i in range(3):
result[y, x, i] = np.median(roi[:, :, i])
return result
'''
src = cv2.imread('best_frame.jpg')
if src is None:
print("이미지를 찾을 수 없습니다.")
else:
k_size = 5
sigma = 1.0
dst_avg = manual_average_blur(src, k_size)
dst_gau = optimized_gaussian_blur(src, k_size, sigma)
# 기본 제공 함수로 대체
dst_med = cv2.medianBlur(src, k_size)
# 결과 출력
cv2.imshow('Original', src)
cv2.imshow('Custom Average Blur', dst_avg)
cv2.imshow('Custom Gaussian Blur', dst_gau)
cv2.imshow('Custom Median Blur', dst_med)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 14. 캐니에지 알고리즘의 이중 임계값을 트랙바로 만들어서 두 개의 임계값을 조절하여 에지를 검출하도록 프로그램을 작성하시오
import cv2
import numpy as np
src = cv2.imread('best_frame.jpg', cv2.IMREAD_GRAYSCALE)
if src is None: exit()
blur = cv2.GaussianBlur(src, (3, 3), 0)
win_name = 'Custom Canny Control'
cv2.namedWindow(win_name)
cv2.createTrackbar('Low', win_name, 50, 255, lambda x: None)
cv2.createTrackbar('High', win_name, 150, 255, lambda x: None)
gx = cv2.Sobel(blur, cv2.CV_32F, 1, 0)
gy = cv2.Sobel(blur, cv2.CV_32F, 0, 1)
mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)
h, w = mag.shape
nms = np.zeros_like(mag)
for y in range(1, h - 1):
for x in range(1, w - 1):
deg = angle[y, x] % 180
if (0 <= deg < 22.5) or (157.5 <= deg <= 180):
p1, p2 = mag[y, x+1], mag[y, x-1]
elif (22.5 <= deg < 67.5):
p1, p2 = mag[y-1, x+1], mag[y+1, x-1]
elif (67.5 <= deg < 112.5):
p1, p2 = mag[y-1, x], mag[y+1, x]
else:
p1, p2 = mag[y-1, x-1], mag[y+1, x+1]
if mag[y, x] >= p1 and mag[y, x] >= p2:
nms[y, x] = mag[y, x]
while True:
low = cv2.getTrackbarPos('Low', win_name)
high = cv2.getTrackbarPos('High', win_name)
strong_edge = (nms >= high).astype(np.uint8) * 255
weak_edge = ((nms >= low) & (nms < high)).astype(np.uint8) * 255
combined_edge = cv2.add(strong_edge, weak_edge)
cv2.imshow(win_name, combined_edge)
if cv2.waitKey(10) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
#
# 15. 미디언 필터링을 수행하는 함수를 직접 작성하고, 수행 결과를 윈도우에 표시하시오
import cv2
import numpy as np
def manual_median_blur(img, ksize):
pad = ksize // 2
h, w, c = img.shape
padded = np.pad(img, ((pad, pad), (pad, pad), (0, 0)), mode='edge')
result = np.zeros_like(img)
for y in range(h):
for x in range(w):
roi = padded[y:y+ksize, x:x+ksize, :]
for i in range(3):
result[y, x, i] = np.median(roi[:, :, i])
return result
src = cv2.imread('best_frame.jpg')
if src is None:
print("이미지를 읽을 수 없습니다.")
else:
dst_med = manual_median_blur(src, 5)
cv2.imshow('Original', src)
cv2.imshow('Manual Median Blur Result', dst_med)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 16. 침식 연산과 팽창 연산함수를 참고하여 하나의 함수로 통일해 구현하시오
import cv2
import numpy as np
def manual_morphology_integrated(img, ksize, is_erosion=True):
pad = ksize // 2
h, w = img.shape
padded = np.pad(img, ((pad, pad), (pad, pad)), mode='constant', constant_values=0)
result = np.zeros_like(img)
op = np.min if is_erosion else np.max
for y in range(h):
for x in range(w):
roi = padded[y:y+ksize, x:x+ksize]
result[y, x] = op(roi)
return result
src = cv2.imread('best_frame.jpg', cv2.IMREAD_GRAYSCALE)
if src is None:
print("이미지를 읽을 수 없습니다.")
else:
dst_erosion = manual_morphology_integrated(src, 3, is_erosion=True)
dst_dilation = manual_morphology_integrated(src, 3, is_erosion=False)
cv2.imshow('Original', src)
cv2.imshow('Integrated Erosion', dst_erosion)
cv2.imshow('Integrated Dilation', dst_dilation)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 17. 번호판 후보 객체 검출 영상을 출력할 때 화살표 키를 입력받아 앞, 뒤 영상으로 넘어가고, esc키를 누르면 종료되는 프로그램을 작성하시오
import cv2
import os
import numpy as np
image_path = './plate_candidates/'
if not os.path.exists(image_path):
print(f"경로를 찾을 수 없습니다: {image_path}")
exit()
file_list = [f for f in os.listdir(image_path) if f.endswith(('.jpg', '.png', '.jpeg'))]
if not file_list:
print("표시할 이미지가 없습니다. 경로와 파일 확장자를 확인하세요.")
exit()
kernel = np.ones((3, 3), np.uint8)
idx = 0
while True:
current_path = os.path.join(image_path, file_list[idx])
src_gray = cv2.imread(current_path, cv2.IMREAD_GRAYSCALE)
if src_gray is None:
print(f"파일을 읽을 수 없습니다: {file_list[idx]}")
idx = (idx + 1) % len(file_list)
if len(file_list) == 1: break
continue
opening_res = cv2.morphologyEx(src_gray, cv2.MORPH_OPEN, kernel)
display_img = opening_res.copy()
info_text = f"[{idx + 1}/{len(file_list)}] {file_list[idx]} (Opening Applied)"
cv2.putText(display_img, info_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255), 2)
cv2.imshow('Plate Candidates Browser (Opening)', display_img)
key = cv2.waitKeyEx(1)
if key == 27:
break
elif key == 0x270000:
idx = (idx + 1) % len(file_list)
elif key == 0x250000:
idx = (idx - 1) % len(file_list)
cv2.destroyAllWindows()
#
[8장 연습문제]
# 1. 순방향 사상과 역방향 사상에 대해 설명하고, 장단점을 비교하시오
# 순방향 사상은 원본 이미지의 픽셀을 기준으로 변환 행렬을 적용하여 결과 이미지의 좌표를 결정하는 방식이다.
이 방식은 변환 과정이 직관적이라는 장점이 있지만 이미지를 확대하거나 비틀 때 결과 이미지의 특정 좌표에 원본 픽셀이
전달되지 않는 홀 현상이 발생하여 결과물에 빈 구멍이 생기고 여러 원본 픽셀이 하나의 결과 좌표에 겹치는 오버랩 현상이
발생하여 이미지가 왜곡될 가능성이 높다.
# 반면 역방향 사상은 결과 이미지의 모든 좌표를 기준으로 역행렬을 사용하여 원본 이미지의 어느 위치에서 색상을 가져와야 하는지 역추적하는 방식으로, 결과 이미지의 모든 픽셀을 하나씩 전수조사하여 채우기 때문에 홀 현상이 전혀 발생하지 않아 매끄러운 결과물을 얻을 수 있고 역추적한 원본 좌표가 정수로 딱 떨어지지 않고 소수점으로 계산될 때 주변 픽셀들을
조합하여 최적의 색상을 결정하는 보간법을 적용하기에 매우 유리한 구조가 된다.
# 다만 모든 픽셀에 대해 역행렬 연산과 역추적 과정을 거쳐야 하므로 순방향 사상에 비해 상대적으로 연산량이 많아진다.
그러나 결과물의 품질이 압도적으로 우수해 대부분의 영상 처리 라이브러리에서는 역방향 사상을 표준 방식으로 사용하고
있다.
#
# 2. 홀 과 오버랩 에 대해 설명하시오
# 홀 현상은 이미지를 변환하거나 확대할 때 결과 영상의 특정 픽셀 좌표에 대응되는 원본 픽셀이 존재하지 않아 발생하는
빈 공간을 의미한다. 주로 순방향 사상 방식을 사용할 때 나타나며 원본의 픽셀들을 결과 좌표로 하나씩 던져주는 과정에서
픽셀과 픽셀 사이의 간격이 벌어지며 생긴다. 이로 인해 결과물에는 마치 바늘로 찌른 듯한 작은 구멍이나 검은 점들이
불규칙하게 나타나 전체적인 영상의 품질이 크게 저하된다.
# 오버랩 현상은 변환 과정에서 원본 이미지의 서로 다른 여러 픽셀이 결과 이미지의 동일한 한 좌표에 겹쳐서 배치되는
현상을 말한다. 이미지를 축소하거나 복잡하게 비틀 때 주로 발생하며 특정 좌표에 여러 색상 정보가 한꺼번에 몰리면서
원래의 이미지가 가진 세밀한 정보가 소실되거나 뭉개지는 왜곡이 일어난다. 홀 현상과 마찬가지로 순방향 사상에서 빈번하게 발생하며 이를 해결하기 위해서는 결과 영상의 모든 픽셀을 기준으로 원본을 찾아가는 역방향 사상 방식을 사용해야 한다.
#
# 3. 보간법이 필요한 이유를 설명하고 opencv에서 보간 방법을 가리키는 옵션 상수를 설명하시오
# 보간법은 역방향 사상을 통해 결과 영상의 픽셀 좌표를 원본 영상의 좌표로 역추적했을 때 그 좌표가 정수로 딱 떨어지지
않고 소수점 단위로 계산되는 경우에 사용한다. 컴퓨터 이미지는 격자 구조의 정수 좌표에만 픽셀 값이 존재하기 때문에
소수점 위치에 해당하는 정확한 색상 값을 바로 가져올 수 없기 때문에 주변 정수 좌표에 위치한 실제 픽셀 값들을 수학적으로 계산하여 가상의 픽셀 값을 만들어내는 과정이 보간법이며 이를 통해 이미지를 확대하거나 변환하더라도 깨짐 없이 부드러운 결과물을 얻을 수 있게 된다.
# OpenCV에서 보간 방법을 지정할 때 사용하는 대표적인 옵션 상수는 다음과 같다. cv2.INTER_NEAREST는 최근접 이웃
보간법으로 가장 가까운 위치의 픽셀 값을 그대로 가져오며 계산 속도가 매우 빠르지만 결과물에 계단 현상이 발생할 수 있고 cv2.INTER_LINEAR는 양선형 보간법으로 주변 4개의 픽셀 값을 거리 비율에 따라 평균 내어 계산하며 속도와 품질의 균형이 좋아 기본값으로 가장 많이 사용된다.
# cv2.INTER_CUBIC은 양입방 보간법으로 주변 16개의 픽셀을 사용하여 3차 함수로 계산하며 양선형 보간법보다 훨씬
선명하고 고품질의 영상을 얻을 수 있지만 연산량이 많다. cv2.INTER_AREA는 영역 보간법으로 이미지를 축소할 때 픽셀
정보가 손실되는 것을 방지하기 위해 주변 픽셀을 무너뜨려 계산하며 축소 작업에 특히 최적화되어 있다.
#
# 4. 최근접 이웃 보간법에 대해 설명하시오
# 최근접 이웃 보간법은 역방향 사상으로 계산된 원본 영상의 실수 좌표에서 가장 가까운 거리에 있는 정수 좌표의 픽셀 값을 그대로 가져오는 방식으로 예를 들어 역추적한 좌표가 10.2로 계산되었다면 소수점을 반올림하여 10번 위치에 있는 픽셀 값을 결과 영상의 색상으로 채택한다. 이 방법은 단순히 가장 인접한 하나의 픽셀 정보만 복사하면 되기 때문에 연산 속도가 모든
보간법 중에서 가장 빠르지만 이미지를 크게 확대할 경우 픽셀 값이 계단 모양으로 도드라져 보이는 에일리어싱 현상이
발생하여 결과 영상의 경계선이 매끄럽지 못하고 거칠게 나타나는 단점이 있다.
#
# 5. 양선형 보간법의 과정을 상세히 설명하시오
# 양선형 보간법은 실수 좌표 주변에 위치한 4개의 정수 좌표 픽셀 값을 거리 비율에 따라 두 번의 선형 보간을 거쳐 최종
색상을 결정하는 방식이다. 먼저 역방향 사상으로 구한 실수 좌표를 기준으로 인접한 상단 두 픽셀 사이에서 가로 방향으로
선형 보간을 수행하여 중간 값을 구하고 동시에 하단 두 픽셀 사이에서도 같은 방식으로 가로 방향 중간 값을 구한다.
그 다음 이렇게 얻어진 상단과 하단의 두 중간 값 사이에서 세로 방향으로 다시 한번 선형 보간을 수행하여 최종적인 픽셀
값을 산출하는데, 이 과정은 단순히 가장 가까운 값을 가져오는 방식보다 계산량이 많지만 주변 픽셀들의 변화량을 부드럽게 반영하기 때문에 이미지를 확대하거나 변환했을 때 경계선이 끊기지 않고 매끄러운 결과물을 얻을 수 있다.
#
# 6. 중심점을 기준으로 회전 변환을 실행하려면 어떠한 과정을 거쳐야 하는지 변환 행렬의 곱으로 나타내고 설명하시오
# 중심점을 기준으로 회전 변환을 수행하기 위해서는 세 단계의 변환 과정을 순차적으로 거쳐야 한다. 기본적으로 회전 변환 행렬은 원점 $(0, 0)$을 중심으로 회전하도록 설계되어 있기 때문에 중심점이 원점이 아니라면 이미지가 원점을 기준으로 크게 회전하며 화면 밖으로 벗어나는 현상이 발생한다. 첫 번째 단계는 회전시키려는 중심점 "(x_{c}, y_{c})"을 원점 "(0, 0)"으로
옮기는 평행 이동 과정으로, 이를 위해 모든 좌표에서 중심점 좌표를 빼주는 이동 행렬 "T(-x_{c}, -y_{c})"를 곱한다.
# 두 번째 단계는 원점으로 옮겨진 이미지에 회전 행렬 "R(\theta)"를 곱하여 원하는 각도만큼 회전시키고,
마지막 세 번째 단계는 원점에서 회전된 이미지를 다시 원래의 중심점 위치로 되돌리기 위해 중심점 좌표만큼 더해주는
역평행 이동 행렬 "T(x_{c}, y_{c})"를 곱한다.
이 전체 과정을 행렬의 곱으로 나타내면 "M = T(x_{c}, y_{c}) \times R(\theta) \times T(-x_{c}, -y_{c})"가 되고,
실제 연산 시에는 행렬 곱의 결합 법칙에 따라 이 세 행렬을 미리 하나로 합쳐진 최종 변환 행렬 "M"을 만든 뒤 이미지의
모든 픽셀 좌표에 한꺼번에 적용한다.
#
# 7. 어파인 변환을 수행하는 opencv함수들을 예시하고 인수들에 대해 설명하시오
# OpenCV에서 어파인 변환을 수행하기 위해 가장 핵심적으로 사용하는 함수는 변환 행렬을 생성하는 함수들과 이를 실제로 이미지에 적용하는 함수로 나뉘는데, 가장 먼저 사용하는 함수는 cv2.getRotationMatrix2D으로, 이미지의 중심점을 기준으로 회전과 크기 변환을 동시에 수행할 수 있는 $2 \times 3$ 어파인 변환 행렬을 생성한다. 인수는 총 세 가지로 회전의 중심이
될 좌표를 의미하는 center, 회전하고자 하는 각도인 angle, 그리고 이미지의 확대 또는 축소 비율을 결정하는 scale이 있어
복잡한 삼각함수 연산 없이도 회전에 필요한 어파인 행렬을 쉽게 얻을 수 있다.
# 또 다른 행렬 생성 함수는 cv2.getAffineTransform이 있다. 이 함수는 평행성이 유지되는 어파인 변환의 특성을 이용하여
원본 영상의 점 세 개가 결과 영상의 점 세 개로 어떻게 이동하는지를 계산해 변환 행렬을 만들고 인수는 변환 전의 점 세 개
좌표를 담은 배열인 src와 변환 후의 대응되는 점 세 개 좌표를 담은 배열인 dst이다. 이 함수는 이미지의 전단 변환이나
기울어짐 등을 보정할 때 주로 사용된다.
# 마지막으로 위 함수들을 통해 얻은 변환 행렬을 실제 이미지 데이터에 적용하는 함수가 cv2.warpAffine이다.
이 함수는 역방향 사상 방식을 사용하여 이미지를 실제로 비틀거나 이동시킨다.
인수는 입력 이미지인 src, 앞서 구한 "2 \times 3" 어파인 변환 행렬인 M, 그리고 결과 이미지의 크기를 결정하는 dsize가
필수적으로 들어간다. 또한 추가적인 선택 인수로 flags를 사용하여 앞서 설명한 양선형 보간법이나 최근접 이웃 보간법 등의 보간 방식을 지정할 수 있으며 borderMode와 borderValue를 통해 변환 후 생기는 빈 공간의 색상을 결정할 수 있다.
#
# 8. 원근 변환에 대해서 설명하시오
# 원근 변환은 영상의 평면성을 유지하면서도 보는 시점에 따른 원근감을 표현하거나 제거할 수 있는 기하학적 변환 방식으로, 2차원 영상을 3차원 공간상에서 자유롭게 회전시키거나 기울였을 때 나타나는 왜곡을 수학적으로 모델링한 것으로 어파인
변환이 직선의 평행성을 유지하는 것과 달리 원근 변환은 직선성은 유지하되 평행성은 유지하지 않는다. 이 때문에 직사각형 모양의 이미지를 사다리꼴이나 임의의 사각형 모양으로 비틀 수 있으며 반대로 비스듬하게 촬영된 사다리꼴 형태의 피사체를 정면에서 본 듯한 직사각형으로 펴는 것도 가능하다.
# 수학적으로 원근 변환은 "3 \times 3" 크기의 행렬을 사용하여 계산하며 변환 과정에서 각 픽셀의 좌표 "(x, y)"는 행렬의
원소들과 결합하여 새로운 좌표 "(x', y')"로 이동하게 되는데 이때 분모에 해당하는 항이 픽셀의 위치에 따라 선형적으로
변화하는 구조를 가진다. 이 분모 값이 위치마다 다르게 적용됨으로써 픽셀 단위로 각기 다른 배율의 확장이나 축소가
일어나게 되고 결과적으로 영상 내에 깊이감과 원근감이 형성된다. 이러한 특성 때문에 원근 변환을 수행하기 위해서는
변환 전과 후의 대응되는 점의 좌표가 최소 4개 이상 필요하다.
# 실제 영상 처리 분야에서 비스듬하게 찍힌 문서나 자동차 번호판의 네 모서리를 탐지하여 정면 상태로 바로잡거나
증강 현실 기술에서 실제 바닥의 원근감에 맞춰 가상의 물체를 자연스럽게 합성하거나 자동차의 어라운드 뷰 시스템에서
카메라의 비스듬한 시점을 하늘에서 내려다보는 시점으로 변환할 때 사용된다.
#
# 9. 원근 변환을 수행하는 opencv함수들을 예시하고 각 인수들을 설명하시오
# 원근 변환을 수행하기 위해 OpenCV에서 제공하는 함수들은 어파인 변환과 유사하게 변환 행렬을 생성하는 단계와 이를
적용하는 단계로 구성된다.
먼저 투영 변환 행렬을 생성하기 위해 cv2.getPerspectiveTransform 함수를 사용하여 "3 \times 3" 크기의 원근 변환 행렬을
계산하여 반환한 값을 얻어낸다. 주요 인수는 변환 전 원본 영상에서의 네 꼭짓점 좌표를 담은 src와 변환 후 결과 영상에서
대응될 네 꼭짓점 좌표를 담은 dst이다. 어파인 변환과 달리 원근 변환은 자유도가 더 높기 때문에 반드시 4개의 대응점 좌표 쌍이 필요하며, 각 좌표는 np.float32 형식의 리스트나 배열로 전달해야 한다.
# 두 번째로 이미지 전체에 변환을 적용하는 함수는 cv2.warpPerspective함수로 앞서 구한 "3 \times 3" 행렬을 이용해
이미지의 모든 픽셀을 새로운 좌표로 재배치한다. 필수 인수는 입력 이미지인 src, 변환 행렬인 M, 그리고 결과 영상의 가로와 세로 크기를 지정하는 dsize이고, 추가로 flags 인수를 통해 cv2.INTER_LINEAR나 cv2.INTER_CUBIC 같은 보간법을
선택할 수 있으며, borderMode를 통해 변환 후 생기는 빈 공간을 어떻게 처리할지 결정할 수 있다.
# 이 외에도 여러 개의 점 좌표들로부터 최적의 투영 행렬을 추정해야 할 때는 cv2.findHomography를 사용하기도 하는데,
이 함수는 4개 이상의 대응점들이 주어졌을 때 최소자승법이나 RANSAC 알고리즘을 사용하여 오차를 최소화하는
"3 \times 3" 행렬을 계산한다. 인수는 원본 좌표 집합인 srcPoints, 대상 좌표 집합인 dstPoints, 그리고 이상치 제거를 위한
알고리즘 선택 옵션인 method 등이 포함된다.
#
# 10. 원본 영상에 (50, 60) 좌표만큼 평행이동을 수행하는 프로그램을 작성하시오(직접 구현, opencv 함수 사용 2가지 작성)
import cv2
import numpy as np
src = cv2.imread('best_frame.jpg')
if src is None:
print("이미지를 불러올 수 없습니다.")
exit()
h, w = src.shape[:2]
tx, ty = 50, 60
dst_direct = np.zeros_like(src)
for y in range(h):
for x in range(w):
old_x = x - tx
old_y = y - ty
if 0 <= old_x < w and 0 <= old_y < h:
dst_direct[y, x] = src[old_y, old_x]
M = np.float32([[1, 0, tx], [0, 1, ty]])
dst_opencv = cv2.warpAffine(src, M, (w, h))
cv2.imshow('Direct Implementation', dst_direct)
cv2.imshow('OpenCV Function', dst_opencv)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 11. 영상파일을 입력받아서 (100, 100)좌표를 기준으로 30도 회전하도록 작성하시오
import cv2
import numpy as np
src = cv2.imread('best_frame.jpg')
if src is None:
print("이미지를 불러올 수 없습니다.")
exit()
h, w = src.shape[:2]
center = (100, 100)
angle = 30
scale = 1.0
M = cv2.getRotationMatrix2D(center, angle, scale)
dst = cv2.warpAffine(src, M, (w, h))
cv2.imshow('Original', src)
cv2.imshow('Rotated at (100, 100)', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 12. 컬러 영상을 회전시키는 함수를 직접 구현하시오
import cv2
import numpy as np
import math
def rotate_image(image, center, angle):
h, w = image.shape[:2]
cx, cy = center
rad = math.radians(-angle)
cos_v, sin_v = math.cos(rad), math.sin(rad)
x, y = np.meshgrid(np.arange(w), np.arange(h))
src_x = (x - cx) * cos_v - (y - cy) * sin_v + cx
src_y = (x - cx) * sin_v + (y - cy) * cos_v + cy
src_x, src_y = np.round(src_x).astype(int), np.round(src_y).astype(int)
mask = (src_x >= 0) & (src_x < w) & (src_y >= 0) & (src_y < h)
dst = np.zeros_like(image)
dst[y[mask], x[mask]] = image[src_y[mask], src_x[mask]]
return dst
src = cv2.imread('best_frame.jpg')
if src is not None:
result = rotate_image(src, (100, 100), 30)
cv2.imshow('Original', src)
cv2.imshow('Custom Rotation', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 13. 입력 영상의 윈도우에서 마우스 드래그를 통해서 두 개의 좌표를 얻어 직선을 긋고 이 직선의 길이와 기울기를 계산하는 프로그램을 작성하시오
import cv2
import math
def on_mouse(event, x, y, flags, param):
global start_point, drawing, img_copy
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
start_point = (x, y)
elif event == cv2.EVENT_MOUSEMOVE:
if drawing:
img_copy = img.copy()
cv2.line(img_copy, start_point, (x, y), (0, 255, 0), 2)
cv2.imshow('Image', img_copy)
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
end_point = (x, y)
cv2.line(img, start_point, end_point, (255, 0, 0), 2)
dx = end_point[0] - start_point[0]
dy = end_point[1] - start_point[1]
length = math.sqrt(dx**2 + dy**2)
if dx != 0:
slope = dy / dx
slope_str = f"{slope:.2f}"
else:
slope_str = "Infinity"
print(f"시작점: {start_point}, 끝점: {end_point}")
print(f"길이: {length:.2f}, 기울기: {slope_str}")
text = f"Len: {length:.1f}, Slope: {slope_str}"
cv2.putText(img, text, start_point, cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv2.imshow('Image', img)
start_point = (0, 0)
drawing = False
img = cv2.imread('best_frame.jpg')
if img is None:
print("이미지를 찾을 수 없습니다.")
exit()
img_copy = img.copy()
cv2.namedWindow('Image')
cv2.setMouseCallback('Image', on_mouse)
cv2.imshow('Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 14. 13번 문제에서 얻은 두 좌표의 길이를 이용해서 원본 영상을 평행이동하는 프로그램을 작성하시오
import cv2
import numpy as np
start_point = None
img = None
def on_mouse(event, x, y, flags, param):
global start_point, img
if event == cv2.EVENT_LBUTTONDOWN:
start_point = (x, y)
print(f"시작점 등록: {start_point}")
elif event == cv2.EVENT_LBUTTONUP:
if start_point:
end_point = (x, y)
tx = end_point[0] - start_point[0]
ty = end_point[1] - start_point[1]
print(f"끝점 등록: {end_point}")
print(f"이동량 계산 -> tx: {tx}, ty: {ty}")
h, w = img.shape[:2]
M = np.float32([[1, 0, tx], [0, 1, ty]])
dst = cv2.warpAffine(img, M, (w, h))
cv2.imshow('Translated Image', dst)
start_point = None
img = cv2.imread('best_frame.jpg')
if img is None:
print("이미지를 불러올 수 없습니다.")
exit()
cv2.namedWindow('Original Image')
cv2.setMouseCallback('Original Image', on_mouse)
print("Original Image 창에서 마우스를 드래그하여 이동 방향과 길이를 정하세요.")
cv2.imshow('Original Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 15. 13번 문제에서 그은 직선의 기울기로 영상의 기울기를 보정하는 프로그램을 작성하시오
import cv2
import numpy as np
import math
start_point = None
img = None
def on_mouse(event, x, y, flags, param):
global start_point, img
if event == cv2.EVENT_LBUTTONDOWN:
start_point = (x, y)
elif event == cv2.EVENT_LBUTTONUP:
if start_point:
end_point = (x, y)
dx = end_point[0] - start_point[0]
dy = end_point[1] - start_point[1]
angle_rad = math.atan2(dy, dx)
angle_deg = math.degrees(angle_rad)
print(f"측정된 각도: {angle_deg:.2f}도")
h, w = img.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle_deg, 1.0)
dst = cv2.warpAffine(img, M, (w, h))
cv2.imshow('Corrected Image', dst)
start_point = None
img = cv2.imread('best_frame.jpg')
if img is None:
print("이미지를 불러올 수 없습니다.")
exit()
cv2.namedWindow('Original Image')
cv2.setMouseCallback('Original Image', on_mouse)
print("수평을 맞추고 싶은 방향(기울어진 방향)으로 드래그하세요.")
cv2.imshow('Original Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 16. 마우스 이벤트로 원근 왜곡 보정 프로그램(p.397)을 카메라로 받아온 영상에서 실행되도록 수정하시오
import cv2
import numpy as np
points = []
img_card = None
is_dragging = False
def on_mouse(event, x, y, flags, param):
global points, img_card, is_dragging
if event == cv2.EVENT_LBUTTONDOWN:
if len(points) < 4:
points.append((x, y))
print(f"점 {len(points)} 등록: ({x}, {y})")
if len(points) == 4:
is_dragging = True
elif event == cv2.EVENT_RBUTTONDOWN:
points = []
img_card = None
is_dragging = False
print("좌표 초기화")
def perspective_transform(image, pts):
src_pts = np.array(pts, dtype=np.float32)
dst_pts = np.array([[0, 0], [300, 0], [300, 400], [0, 400]], dtype=np.float32)
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
dst = cv2.warpPerspective(image, M, (300, 400))
return dst
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("카메라를 열 수 없습니다.")
exit()
cv2.namedWindow('Camera')
cv2.setMouseCallback('Camera', on_mouse)
print("보정할 카드의 네 모서리를 순서대로 클릭하세요 (좌상 -> 우상 -> 우하 -> 좌하)")
print("초기화하려면 우클릭, 종료하려면 'q'를 누르세요.")
while True:
ret, frame = cap.read()
if not ret:
print("프레임을 가져올 수 없습니다.")
break
display_frame = frame.copy()
for i, pt in enumerate(points):
cv2.circle(display_frame, pt, 5, (0, 0, 255), -1)
if i > 0:
cv2.line(display_frame, points[i-1], points[i], (0, 255, 0), 2)
if i == 3:
cv2.line(display_frame, points[3], points[0], (0, 255, 0), 2)
if len(points) == 4 and is_dragging:
img_card = perspective_transform(frame, points)
cv2.imshow('Corrected Card', img_card)
cv2.imshow('Camera', display_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
#
# 17. cv2.warpAffine() 함수를 이용하여 영상을 좌우 혹은 상하로 뒤집는 어파인 변환 행렬을 구성하여 프로그램을 작성하시오
import cv2
import numpy as np
src = cv2.imread('best_frame.jpg')
if src is None:
print("이미지를 불러올 수 없습니다.")
exit()
h, w = src.shape[:2]
M1 = np.float32([[-1, 0, w],
[0, 1, 0]])
dst_horizontal = cv2.warpAffine(src, M1, (w, h))
M2 = np.float32([[1, 0, 0],
[0, -1, h]])
dst_vertical = cv2.warpAffine(src, M2, (w, h))
compare = np.hstack((src, dst_horizontal, dst_vertical))
cv2.imshow('Original | Horizontal Flip | Vertical Flip', compare)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 18. 태극 문양에 좌, 우로 각각 건, 감을 출력하시오
import cv2
import numpy as np
img = np.full((600, 1000, 3), 255, dtype=np.uint8)
h, w = img.shape[:2]
cx, cy = w // 2, h // 2
r = 120
cv2.ellipse(img, (cx, cy), (r, r), 0, 180, 360, (0, 0, 255), -1)
cv2.ellipse(img, (cx, cy), (r, r), 0, 0, 180, (255, 0, 0), -1)
cv2.circle(img, (cx - r//2, cy), r//2, (0, 0, 255), -1)
cv2.circle(img, (cx + r//2, cy), r//2, (255, 0, 0), -1)
cv2.circle(img, (cx, cy), r, (0, 0, 0), 1)
def draw_bar(x, y, is_broken):
bw, bh = 22, 120
gap = 12
if not is_broken:
cv2.rectangle(img, (x, y), (x + bw, y + bh), (0, 0, 0), -1)
else:
cv2.rectangle(img, (x, y), (x + bw, y + bh//2 - gap//2), (0, 0, 0), -1)
cv2.rectangle(img, (x, y + bh//2 + gap//2), (x + bw, y + bh), (0, 0, 0), -1)
bar_dist = 40
start_x_left = cx - r - 150
for i in range(3):
draw_bar(start_x_left + i * bar_dist, cy - 60, False)
start_x_right = cx + r + 70
for i in range(3):
draw_bar(start_x_right + i * bar_dist, cy - 60, True)
cv2.imshow('Taegeukgi Spaced', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
# 19. 18번 예제를 적당히 회전시키고 곤, 이 를 추가하여 태극기를 완성해 출력하시오
import cv2
import numpy as np
# 이미지 생성 및 기본 형태 지정
img = np.full((600, 1000, 3), 255, dtype=np.uint8)
h, w = img.shape[:2]
cx, cy = w // 2, h // 2
r = 120
# 괘의 막대 하나 그리기 (쪼개진 것, 안 쪼개진 것)
def draw_rotated_bar(x, y, is_broken, angle_deg):
bw, bh = 22, 120 # 막대의 가로세로 길이
x_end, y_end = x + bw, y + bh # 끝점 변수화
y_center = y + 60 # 중앙점 (bh//2)
gap = 6 # 괘의 틈새(12)의 절반
M = cv2.getRotationMatrix2D((cx, cy), angle_deg, 1)
if not is_broken:
pts = np.array([[[x, y]], [[x_end, y]], [[x_end, y_end]], [[x, y_end]]], dtype=np.float32)
rotated_pts = cv2.transform(pts, M).astype(np.int32)
cv2.fillPoly(img, [rotated_pts], (0, 0, 0))
else:
pts = np.array([
[[x, y]], [[x_end, y]], [[x_end, y_center - gap]], [[x, y_center - gap]], # 위 조각
[[x, y_center + gap]], [[x_end, y_center + gap]], [[x_end, y_end]], [[x, y_end]] # 아래 조각
], dtype=np.float32)
rotated_pts = cv2.transform(pts, M).astype(np.int32)
cv2.fillPoly(img, [rotated_pts[:4], rotated_pts[4:]], (0, 0, 0))
# 기울어진 태극 무늬 그리기
angle = np.degrees(np.arctan2(2, 3))
cv2.ellipse(img, (cx, cy), (r, r), angle, 0, 180, (255, 0, 0), -1)
cv2.ellipse(img, (cx, cy), (r, r), angle, 180, 360, (0, 0, 255), -1)
r2 = r // 2
rad = np.radians(angle)
c1 = (cx - r2 * np.cos(rad), cy - r2 * np.sin(rad))
c2 = (cx + r2 * np.cos(rad), cy + r2 * np.sin(rad))
cv2.circle(img, (int(c1[0]), int(c1[1])), r2, (0, 0, 255), -1)
cv2.circle(img, (int(c2[0]), int(c2[1])), r2, (255, 0, 0), -1)
cv2.circle(img, (cx, cy), r, (0, 0, 0), 1)
# 괘 그리기
bar_dist, margin = 40, 100
start_x_left = cx - r - margin - (bar_dist * 2 + 22)
start_x_right = cx + r + margin
for i in range(3):
draw_rotated_bar(start_x_left + i * bar_dist, cy - 60, False, -angle) # 건
draw_rotated_bar(start_x_right + i * bar_dist, cy - 60, True, -angle) # 곤
gam_p, yi_p = [True, False, True], [False, True, False]
for i in range(3):
draw_rotated_bar(start_x_left + i * bar_dist, cy - 60, gam_p[i], angle) # 감
draw_rotated_bar(start_x_right + i * bar_dist, cy - 60, yi_p[i], angle) # 이
# 이미지 출력
cv2.imshow('Taegeukgi Optimized', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
#
|
|

첫댓글 강의자료에 있는 자료로 AI공부시작할것