1. 이동(Translation)
■ 기존 좌표를 \( x_{\text{old}}, y_{\text{old}} \)라고 했을 때, 이미지를 이동하는 방법은 기존 좌표에 이동시키려는 거리 \( d_1, d_2 \)를 더해주면 된다. \[
\begin{cases}
x_{\text{new}} = x_{\text{old}} + d_1 \\
y_{\text{new}} = y_{\text{old}} + d_2
\end{cases}
\Leftrightarrow
\begin{cases}
x_{\text{new}} = 1x_{\text{old}} + 0y_{\text{old}} + d_1 \\
y_{\text{new}} = 0x_{\text{old}} + 1y_{\text{old}} + d_2
\end{cases}
\] ■ 이 방정식을 행렬식으로 표현하면 다음과 같다. \[
\begin{pmatrix}
1 & 0 & d_1 \\
0 & 1 & d_2
\end{pmatrix}
\cdot
\begin{pmatrix}
x \\
y \\
1
\end{pmatrix}
=
\begin{pmatrix}
d_1 + x \\
d_2 + y
\end{pmatrix}
\] 여기서 2 x 3 행렬 \(
\begin{pmatrix}
1 & 0 & d_1 \\
0 & 1 & d_2
\end{pmatrix}
\)이 이미지의 좌표를 이동시키는 변환 행렬이다. 이 변환 행렬에 좌표를 곱해주면 이동된 좌표가 계산된다.
■ 변환 행렬에서 (1, 0 , 0, 1) 부분이 확대/축소, 회전, 변형의 역할을 수행하고 \( d_1, d_2 \)은 각 축(\( x, y \))에 대한 이동을 수행한다. 여기서 단위 행렬 부분의 대각 원소가 1이므로 '이동'만 고려한 변환 행렬임을 알 수 있다.
■ OpenCV에서는 cv2.warpAffine(src, matrix, dsize, dst, flags, borderMode, borderValue) 함수로 변환 행렬을 이용해서 이미지를 변환할 수 있다.
- src는 원본 이미지 (배열)
- matrix에는 2 x 3 변환 행렬을 지정한다.
- dsize는 결과 이미지의 크기 (width, height)
- flags는 보간법 알고리즘 플래그로 다음과 같은 방법들을 선택할 수 있다.
flags | 의미 |
cv2.INTER_LINEAR | 인접한 4개 픽셀 값에 거리 가중치 사용 (디폴트 값) |
cv2.INTER_NEAREST | 가장 가까운 픽셀 값 사용 |
cv2.INTER_AREA | 픽셀 영역 관계를 이용한 리샘플링 방법 |
cv2.INTER_CUBIC | 인접한 16개 픽셀(= 4 x 4 픽셀) 값에 거리 가중치 사용 |
- borderMode는 가장자리 영역 보정 플래그로 다음과 같은 방법들을 선택할 수 있다.
borderMode | 의미 |
cv2.BORDER_CONSTANT | 고정 색상 값 |
cv2.BORDER_REPLICATE | 가장자리 복제 |
cv2.BORDER_WRAP | 반복 |
cv2.BORDER_REFLECT | 반사 |
- borderValue는 가장자리 영역 보정 플래그로 cv2.BORDER_CONSTANT를 지정할 경우 사용할 가장자리 색상 값으로 디폴트 값은 0이다.
img = cv2.imread('opencv_logo.png')
## height를 행, width를 열 또는 y, x 축으로 볼 수 있음
height, width = img.shape[0:2] # 영상/이미지의 크기
## 이동할 거리
dx, dy = 10, 20
## 변환 행렬
trans_matrix = np.float32([[1, 0, dx], [0, 1, dy]]) # 이동만 고려
trans_matrix
```#결과#```
array([[ 1., 0., 10.],
[ 0., 1., 20.]], dtype=float32)
````````````
## 단순 이동
dst = cv2.warpAffine(threshold_127, trans_matrix, (width, height))
dst2 = cv2.warpAffine(img, trans_matrix, (width+dx, height+dy))
## 지워진 가장자리 픽셀 영역 보정
dst3 = cv2.warpAffine(img, trans_matrix, (width+dx, height+dy), None,
cv2.INTER_LINEAR, cv2.BORDER_CONSTANT, (255,0,0) ) # 파란색
dst4 = cv2.warpAffine(img, trans_matrix, (width+dx, height+dy), None,
cv2.INTER_LINEAR, cv2.BORDER_REFLECT) # 원본 반사
dst5 = cv2.warpAffine(img, trans_matrix, (width+dx, height+dy), None,
cv2.INTER_LINEAR, cv2.BORDER_WRAP) # 반복
titles = ['original', 'trans1', 'trans2', 'border_constant', 'border_reflect','border_replicate']
images = [img, dst, dst2, dst3, dst4, dst5]
for i in range(6):
plt.subplot(2, 3, i+1)
plt.imshow(images[i][:,:,::-1])
plt.title(titles[i])
plt.xticks([]); plt.yticks([])
plt.show()
- trans1을 제외한 이미지들은 원본 이미지를 오른쪽(x)으로 10픽셀, 아래(y)로 20픽셀 평행 이동시킨 결과이다.
- 평행 이동이 적용되면 기존 영역의 가장자리 부분이 잘리게 된다. 이때 borderMode 파라미터에 지정해 잘린 가장자리 픽셀 영역을 보정할 수 있다.
2. 확대/축소(Scaling)
■ 이미지를 일정 비율로 확대/축소하는 방법은 단위 행렬 부분의 대각 원소에 특정 값을 곱하면 된다. 이를 방정식과 행렬식으로 표현하면 다음과 같다. \[
\begin{cases}
x_{\text{new}} = a_1 \cdot x_{\text{old}} \\
y_{\text{new}} = a_2 \cdot y_{\text{old}}
\end{cases}
\Leftrightarrow
\begin{cases}
x_{\text{new}} = a_1 \cdot x_{\text{old}} + 0 \cdot y_{\text{old}} + 0 \cdot 1 \\
y_{\text{new}} = 0 \cdot x_{\text{old}} + a_2 \cdot y_{\text{old}} + 0 \cdot 1
\end{cases}
\Leftrightarrow
\begin{pmatrix}
a_1 & 0 & 0 \\
0 & a_2 & 0
\end{pmatrix}
\cdot
\begin{pmatrix}
x \\
y \\
1
\end{pmatrix}
=
\begin{pmatrix}
a_1 x \\
a_2 y
\end{pmatrix}
\] cf) cv2.warpAffine( ) 함수를 사용하기 위해 변환 행렬을 2 x 3 크기로 맞춰야 한다.
img = cv2.imread('opencv_logo.png')
height, width = img.shape[:2]
## 0.5배 (축소) 변환 행렬
shrink_05 = np.float32([[0.5, 0, 0],
[0, 0.5,0]])
## 2배 (확대) 변환 행렬
zoom_2 = np.float32([[2, 0, 0],
[0, 2, 0]])
## 보간법 적용 없이 확대/축소
dst1 = cv2.warpAffine(img, shrink_05, (int(height*0.5), int(width*0.5)))
dst2 = cv2.warpAffine(img, zoom_2, (int(height*2), int(width*2)))
## 보간법 적용 확대/축소
dst3 = cv2.warpAffine(img, shrink_05, (int(height*0.5), int(width*0.5)), None, cv2.INTER_AREA)
dst4 = cv2.warpAffine(img, zoom_2, (int(height*2), int(width*2)), None, cv2.INTER_CUBIC)
img.shape, dst1.shape, dst2.shape, dst3.shape, dst4.shape
```#결과#```
((120, 98, 3), (49, 60, 3), (196, 240, 3), (49, 60, 3), (196, 240, 3))
````````````
- 일반적으로 축소에는 cv2.INTER_AREA를, 확대에는 cv2.INTER_CUBIC, cv2.INTER_LINEAR를 사용한다.
titles = ['original', 'dst1', 'dst2', 'dst3', 'dst4']
images = [img, dst, dst2, dst3, dst4, dst5]
for i in range(5):
plt.subplot(2, 3, i+1)
plt.imshow(images[i][:,:,::-1])
plt.title(titles[i])
plt.xticks([]); plt.yticks([])
plt.subplots_adjust(wspace=0.5)
plt.show()
■ cv2.resize(src, dsize, dst, fx, fy, interpolation) 함수를 사용하면 변환 행렬을 사용하지 않고도 확대/축소를 몇 픽셀로 할 것인지 또는 몇 배율을 적용할 것인지를 선택할 수 있다.
- src는 원본 이미지 (배열)
- dsize: 결과 이미지의 크기(확대/축소 목표 크기) (width, height)형식으로, 생략하면 fx, fy 배율을 적용한다.
- fx, fy: 크기 배율이다. 만약, dsize가 지정되면 dsize를 적용한다.
- interpolation: 보간법 알고리즘 선택 플래그로 cv2.warpAffine()과 동일하다.
img = cv2.imread('opencv_logo.png')
height, width = img.shape[:2]
## 크기(dsize) 지정으로 축소
dst1 = cv2.resize(img, (int(width*0.5), int(height*0.5)), interpolation=cv2.INTER_AREA)
## 배율(fx, fy) 지정으로 확대
dst2 = cv2.resize(img, None, None, 2, 2, cv2.INTER_CUBIC)
img.shape, dst1.shape, dst2.shape
```#결과#```
((120, 98, 3), (60, 49, 3), (240, 196, 3))
````````````
- fx = 2, fy = 2이면 x 축으로 2배, y축으로 2배 스케일링한다는 의미이다.
3. 회전(Rotation)
■ 이미지 회전의 경우, 이미지는 2차원 이므로 회전 행렬을 사용하면 된다.
■ OpenCV에서는 cv2.getRotationMatrix2D(center, angle, scale) 함수로 회전 행렬을 생성할 수 있다.
- center는 회전축 중심 좌표 (x, y)
- angle은 회전할 각도 (60 진법)
- scale은 확대/축소 비율
img = cv2.imread('opencv_logo.png')
height, width = img.shape[:2]
## 회전 행렬 정의
# 회전축:중앙, 각도:45, 배율:0.5
rotation_45 = cv2.getRotationMatrix2D((width/2,height/2),45,0.5)
# 회전축:중앙, 각도:90, 배율: 1.5
rotation_90 = cv2.getRotationMatrix2D((width/2,height/2),90,1.5)
## 회전 행렬 적용
img45 = cv2.warpAffine(img, rotation_45, (width, height))
img90 = cv2.warpAffine(img, rotation_90, (width, height))
titles = ['original', 'img45', 'img90']
images = [img, img45, img90]
for i in range(3):
plt.subplot(1, 3, i+1)
plt.imshow(images[i][:,:,::-1])
plt.title(titles[i])
plt.xticks([]); plt.yticks([])
plt.subplots_adjust(wspace=0.5)
plt.show()
4. 어핀 변환(Affine Transform)
■ 이미지를 뒤틀기(왜곡)하는 방법으로 크게 어핀 변환과 원근 변환이 있다.
■ 이 중 어핀 변환은 선의 평행성을 유지하면서 이미지에 이동, 확대/축소, 반전을 적용하는 기법으로, 이를 위해서는 서로 대응하는 3쌍의 점(좌표)이 필요하다.
■ 변환 전, 후의 대응점을 기반으로 변환 행렬을 구할 수 있으며, 변환 행렬을 이용해 이미지를 원하는 형태로 뒤틀 수 있다.
■ OpenCV에서 cv2.getAffineTransform(pts1, pts2) 함수로 어핀 변환의 변환 행렬을 계산할 수 있다.
- pts1은 변환 전 영상/이미지의 좌표 3개 (3 x 2 배열)
- pts2는 변환 후 영상/이미지의 좌표 3개 (3 x 2 배열)
■ 계산된 변환 행렬을 cv2.warpAffine( )에 전달해 주면 어핀 변환이 적용된다.
img = cv2.imread('chessboard.jpg')
rows, cols = img.shape[:2]
## 변환 전, 후 3개의 좌표 생성
pts1 = np.float32([[200,100],[400,100],[200,200]]) # 변환 전 좌표
pts2 = np.float32([[200,300],[400,200],[200,400]]) # 변환 후 좌표(이동 점)
# 변환 전 좌표 표시. Affine 변환 후 이동 점 확인.
cv2.circle(img, (200,100), 10, (255,0,0),-1)
cv2.circle(img, (400,100), 10, (0,255,0),-1)
cv2.circle(img, (200,200), 10, (0,0,255),-1)
## 짝지은 3개의 좌표로 변환 행렬 계산
M = cv2.getAffineTransform(pts1, pts2)
print(M) print(M) # 1열 & 2열이 변형, 3열이 이동의 역할
```#결과#```
[[ 1. 0. 0. ]
[ -0.5 1. 300. ]]
````````````
## 어핀 변환 적용
dst = cv2.warpAffine(img, M, (int(cols*1.3),rows))
plt.subplot(121),plt.imshow(img),plt.title('image'),plt.xticks([]); plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title('Affine'),plt.xticks([]); plt.yticks([])
plt.show()
- 어핀 변환은 위의 그림과 같이 3개의 대응점을 이용해서 이미지를 2차원으로 뒤트는 변환이다.
5. 원근 변환(Perspective Transform)
■ 원근 변환은 이미지에 원근법의 원리(멀리 있는 것은 작게, 가까이 있는 것은 크게 보임)를 적용하는 방법으로 4개의 대응점을 이용해 이미지를 3차원으로 뒤트는 변환이다.
■ 즉 어핀 변환은 x, y축을 변형했다면, 원근 변환은 x, y, z축을 변형한다.
■ OpenCV에서 cv2.getPerspectiveTransform(pts1, pts2) 함수로 원근 변환의 변환 행렬을 계산할 수 있다.
- pts1은 변환 전 영상/이미지의 좌표 4개 (4 x 2 배열)
- pts2는 변환 후 영상/이미지의 좌표 4개 (4 x 2 배열)
■ 계산된 변환 행렬을 cv2.warpPerspective( )에 전달해 주면 원근 변환이 적용된다.
img = cv2.imread('perspective.jpg')
rows, cols = img.shape[:2]
## 변환 전, 후 4개의 좌표 생성
## 좌표점은 좌상->좌하->우상->우하
pts1 = np.float32([[504,1003],[243,1525],[1000,1000],[1280,1685]]) # 변환 전 좌표
pts2 = np.float32([[10,10],[10,1000],[1000,10],[1000,1000]]) # 변환 후 좌표(이동 점)
## 변환 전 좌표 표시. perspective 변환 후 이동 점 확인.
cv2.circle(img, (504,1003), 20, (255,0,0),-1)
cv2.circle(img, (243,1524), 20, (0,255,0),-1)
cv2.circle(img, (1000,1000), 20, (0,0,255),-1)
cv2.circle(img, (1280,1685), 20, (0,0,0),-1)
## 짝지은 4개의 좌표로 변환 행렬 계산
M = cv2.getPerspectiveTransform(pts1, pts2)
print(M) # 1열 & 2열이 변형, 3열이 이동의 역할
```#결과#```
[[-2.02153837e+00 -1.02691611e+00 2.04001743e+03]
[-2.24880859e-02 -3.30149532e+00 3.31389904e+03]
[-2.62496544e-04 -1.74594051e-03 1.00000000e+00]]
````````````
## 원근 변환 적용
dst = cv2.warpPerspective(img, M, (1100,1100))
print(f'변환 전 shape {img.shape}, 변환 후 shape {dst.shape}')
```#결과#```
변환 전 shape (2048, 1536, 3), 변환 후 shape (1100, 1100, 3)
````````````
plt.subplot(121),plt.imshow(img),plt.title('image'),plt.xticks([]); plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title('Perspective'),plt.xticks([]); plt.yticks([])
plt.show()
- 실행 결과, 철도 위에서 아래를 보는 것처럼 보인다.
■ 이렇게 원근 변환은 원근 이미지를 평면 이미지로 변환할 수 있으며, 반대로 평면 이미지의 폭을 조절해 원근 이미지로 변환할 수도 있다.
cf) 변환 행렬
- 3행을 사용하지 않으면 어핀 변환, 3행을 사용하면 원근 변환
'OpenCV' 카테고리의 다른 글
필터와 블러링 (0) | 2024.12.18 |
---|---|
기하학적 변환 (2) (0) | 2024.12.16 |
이미지 프로세싱 (3) (0) | 2024.12.15 |
이미지 프로세싱 (2) (0) | 2024.12.14 |
이미지 프로세싱 (1) (0) | 2024.12.13 |