일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- multiparty
- Thumbnail
- AWS
- streaming
- 버킷
- 게임서버
- 시작하기
- 좌표
- 이미지프로세싱
- 화소
- S3
- Sharp
- 스트리밍서버
- 이미지서버
- 지하철역
- 디지털영상
- nodejs
- 데이터
- 샘플링
- lightsail
- 노선
- 리사이즈
- Bucket
- 이미지
- 튜토리얼
- Node
- 프라우드넷
- ProudNet
- resize
- 아날로그영상
- Today
- Total
Deep Studying
[이미지 프로세싱] python과 openCV로 이미지 픽셀 수정하기 1 - 리사이징 본문
저번 포스트에서는 양자화와 리사이징에 대해서 설명했습니다.
이전 포스트 확인하기 -> https://minisp.tistory.com/3
이전 포스트에서 언급한 내용을 참고하여 python과 openCV로 이미지를 읽고 출력하고 간단히 수정하는 방법을 배워보도록 할건데요, python과 openCV, 그리고 numpy는 설치가 되어있다고 가정하고 시작하겠습니다.
1. 이미지 불러오기
1-1. 이미지 파일 불러오기( imread )
일반적으로 파이썬에서 이미지를 불러올 때 Pillow를 사용하는 것 같습니다. 하지만 앞으로의 강의에서 openCV를 사용할 것이기 때문에 이미지를 불러오는 것 부터 openCV를 사용하도록 하겠습니다.
imgArray = cv2.imread( filename , flags)
filename: 불러올 이미지 파일( BMP, JPEG, PNG 등)의 경로 입니다.
flags: 읽기 옵션에 해당합니다. 아래와 같은 값들이 올 수 있습니다.
- cv2.IMREAD_COLOR: 컬러로 읽기 (default)
- cv2.IMREAD_GRAYSCALE: 흑백으로 읽기
- cv2.IMREAD_UNCHANGED: Alpha채널 포함
#cv2는 python에서 사용할 수 있는 opencv 모듈입니다.
import cv2
#불러올 이미지 파일의 경로
imageFile1 = './image1.jpg'
imageFile2 = './image2.jpg'
#이미지를 불러옵니다.
imgArray1 = cv2.imread(imageFile1)
imgArray2 = cv2.imread(imageFile2, 0) #cv2.imread(imageFile2, cv2.IMREAD_GRAYSCALE)
imread 함수를 사용하면 파라미터로 넘겨준 경로의 이미지를 불러옵니다. 또한 불러온 이미지를 numpy.ndarray의 배열로 읽어 반환하며 읽기에 실패하면 None을 반환합니다.
1-2. 이미지 저장 형식 ( ndArray )
먼저 IMREAD_GRAYSCALE 옵션으로 읽은 흑백 이미지부터 살펴보겠습니다.
위 이미지는 20x10사이즈의 흑백 이미지입니다. 아래 코드로 해당 이미지를 불러온 뒤 그대로 출력해보겠습니다.
※ 원본을 열어보면 낮은 해상도의 사진이라 확대하면 굉장히 각져있는 모습을 볼 수 있는데요, 업로드하여 확대하면 어느정도 보정이 들어가는 것 같습니다.
import cv2
import numpy as np
imageFile = './image.png'
img = cv2.imread(imageFile, 0)
print(img)
cv2.imread(imageFile, 0)은 cv2.imread(imageFile, cv2.IMREAD_GRAYSCALE)과 동일합니다.
위에 나온 숫자들은 모두 하나하나의 픽셀에 해당되는 밝기입니다.
불러온 이미지는 M=10, N=20, L=256인 이미지인 셈이죠.
픽셀의 개별 요소에 접근할 때는 2차원 배열을 접근하듯이 Array[y좌표][x좌표]로 사용할 수도 있고 Array[y좌표, x좌표]의 형식으로 접근할 수도 있습니다.
하지만 여러 픽셀을 접근할 때는 Array[y좌표1:y좌표2, x좌표1:x좌표2]의 형태로만 사용하시기 바랍니다.
우선은 이렇게 이미지 픽셀을 접근할 수 있다 정도만 알아두시면 될 것 같습니다. 자세한 설명은 뒤에 덧붙이겠습니다.
2. 이미지 출력하기 ( imwrite, imshow )
이미지를 불러오고, 수정했다면 어떤 방법으로든 확인할 수 있어야겠죠? 이 때 사용할 수 있는 두 가지 방법이 있습니다.
imwrite 함수는 ndArray를 이미지 파일로 저장해주고, imshow는 ndArray를 이미지로 볼 수 있게 윈도우창에 띄워줍니다. 자세한 사용 방법을 보도록 하겠습니다.
2-1. 파일로 저장하기 ( imwrite )
cv2.imwrite(filename, img, [ params ])
filename: 저장될 경로입니다. 확장자를 포함해야하며 파일 확장자에 따라 이미지 포맷이 지정됩니다.
img: 이미지파일로 저장할 ndArray입니다.
params: 압축률, 이미지품질 등을 지정합니다. 이에 대한 설명은 아직은 불필요하다 생각하여 생략하도록 하겠습니다.
import cv2
import numpy as np
imageFile='./dog.jpg'
src = cv2.imread(imageFile, 0)
cv2.imwrite('./gray_dog.jpg')
위 코드를 실행하면 dog.jpg 이미지를 흑백으로 열어 gray_dog.jpg 이미지로 저장할 수 있습니다.
2-2. 윈도우에 출력하기 ( imshow )
cv2.imshow(winname, mat)
winname: 윈도우의 이름입니다.
mat: 이미지로 출력할 배열입니다.
imshow를 이용하여 이미지를 화면에 출력해보겠습니다.
코드에 대한 설명은 아래에 하도록 하겠습니다.
import cv2
import numpy as np
imageFile='./dog.jpg'
src = cv2.imread(imageFile, 0) ... (1)
cv2.imshow('sample',src) ...(2)
cv2.waitKey(0) ...(3)
cv2.destroyAllWindows() ...(4)
(1) imread - 이미지를 불러옵니다. flags에 0을 넣어 흑백으로 불러왔습니다.
(2) imshow - 이미지를 출력합니다. 'sample' 이란 창에 불러온 src를 이미지로 출력합니다.
(3) waitKey - 이미지를 출력하는 윈도우에서 키보드 입력이 될 때 까지 대기합니다.
※waitKey함수는 다양한 쓰임으로 사용할 수 있습니다. 아래에 덧붙이도록 하겠습니다.
(4) destroyAllWindows - 모든 윈도우를 파괴합니다.
코드를 실행하면 위와 같이 윈도우를 생성하고 이미지를 출력합니다.
2-3. waitKey 함수
waitKey 함수는 엄밀히 따지자면 이미지를 불러오는 것 하고는 관련이 없는데요. 그래도 여기서 설명을 하고 넘어가야 할 것 같아서 설명을 하도록 하겠습니다.
input = cv2.waitKey( delay )
키보드를 입력할 수 있는 delay 만큼의 시간을 줍니다. (단위는 ms, milli second)
- 주어진 시간 안에 키를 누르면 해당 키에 해당하는 코드를 반환합니다.
- 만약 지정된 시간에 키를 누르지 않으면 -1을 반환합니다.
그런데 delay의 default값은 0입니다. delay가 0이면 입력할 시간을 주지 않는걸까요?
사실 delay의 값이 0으로 주어지면 이 함수는 키 입력이 될 때 까지 무한히 대기합니다. 따라서 imshow 함수 설명을 위해 적었던 코드에서 cv2.waitKey(0)는 "키보드를 입력할 때 까지 계속 기다려라." 라는 의미가 되는 셈이죠.
3. 픽셀 수정하기
3-1. 이미지 크기 확인하기 ( shape )
이미지의 크기가 항상 같지는 않죠. 그렇기 때문에 불러온 이미지의 크기를 아는게 가장 기본입니다. 이 때 활용할 수 있는 것이 numpy.ndarray의 shape 속성입니다. shape는 배열의 크기를 출력해 주는데요. 사용법은 아래와 같습니다.
import cv2
import numpy as np
imageFile='./dog.jpg'
src_color = cv2.imread(imageFile)
src_gray = cv2.imread(imageFile, 0)
height, width = src_gray.shape ... (1)
print(src_color.shape)
print(src_gray.shape)
print(height, width)
>>> (400, 400, 3)
>>> (400, 400)
>>> 400, 400
결론만 말씀드리자면 shape값은 아래와 같은 형태의 튜플입니다.
- 컬러 영상의 경우: ( y길이, x길이, 채널 수 )
- 흑백 영상의 경우: ( y길이, x길이 )
또한 위 코드 (1)에서 처럼 튜플의 각 요소를 한 번에 받아올 수도 있습니다.
> 컬러 영상에 대한 설명은 아직 이 문서에 추가해야할지, 다른 문서에 따로 설명해야할지 모르겠어서 하지 않았습니다. 처음엔 흑백 영상에 대해서만 하고 필요하게 된다면 어떠한 방식으로든 추가하도록 하겠습니다.
3-2. 픽셀 수정 ( 단일 픽셀 )
위에서 잠깐 언급한 내용입니다. ( y, x )픽셀에 접근할 때는 Array[y좌표, x좌표] 형식을 사용하면 접근할 수 있었습니다.
400x400크기의 강아지 사진 중 아래 절반을 검은색 화소로 바꾸려면 어떻게 할까요?
import cv2
import numpy as np
imageFile='./dog.jpg'
img = cv2.imread(imageFile, 0)
height, width = img.shape
for j in range( height//2, height):
for i in range(width):
img[j,i] = 0
cv2.imshow('sample',img)
cv2.waitKey()
cv2.destroyAllWindows()
간단한 for문을 이용하여 j값이 height/2 ~ height 사이의 모든 픽셀 값을 0으로 바꿨습니다.
아래 절반의 픽셀이 0 (검은색)으로 바뀐 모습을 확인할 수 있었습니다.
3-3. 픽셀 수정 ( 영역별 수정 )
> 바꾸려는 영역의 크기가 유동적이라면?
> 특정한 패턴으로 for문이 중첩된다면? ( 특정 범위의 평균 밝기를 구하는 경우 등 )
모두 for문이나 기타 반복문을 사용해 픽셀을 하나하나 접근해 처리할 수 있습니다. 하지만 경우에 따라서는 코드가 복잡해 보일 수 있기 때문에 픽셀 하나하나의 개별적인 접근법 외에도 픽셀의 영역별로 접근할 수 있는 방법이 있습니다.
Array[y1:y2, x1:x2] 형식으로 사용하며 y좌표가 y1~y2이고 x좌표가 x1~x2인 모든 요소를 접근한다는 의미입니다.
Array[y1:y2, x1:x2] = value 처럼 사용하면 해당 범위의 화소값이 전부 value로 바뀝니다. 그렇다면 위의 예제를 영역 접근으로 바꿔보겠습니다.
import cv2
import numpy as np
imageFile='./dog.jpg'
img = cv2.imread(imageFile, 0)
height, width = img.shape
img[height//2:height, 0:width] = 0
cv2.imshow('sample2',img)
cv2.waitKey()
cv2.destroyAllWindows()
처음 작성한 코드와 같은 결과가 나오는 것을 확인할 수 있습니다. 이러한 영역별 접근은 위에서 말한 '특정 범위의 평균 밝기를 구하는 경우'에 유용하게 사용할 수 있습니다. 저번 포스트에서 언급했던 리사이징을 기억하시나요?
400x400 크기의 강아지 사진을 위처럼 16x16 해상도의 이미지로 바꾸려면
1. 강아지 이미지를 읽어오고 400 x 400 배열에 저장합니다.
2-1. 16 x 16 행렬을 만듭니다. 이 때 결과의 한 화소에 대응되는 원본은 25x25 사이즈의 화소들이 됩니다.2-2. 가로 세로가 각각 25 화소인 영역의 밝기 평균을 구한다.
3-1. 구한 밝기를 소수점 버림하고 newArray의 해당 칸에 넣는다.
3-2. 2부터 3까지 반복하여 newArray의 모든 칸을 채운다.
위와 같은 과정을 거쳐 만들 수 있습니다.
import cv2
import numpy as np
imageFile='./dog.jpg'
img = cv2.imread(imageFile, 0)
img_height, img_width = img.shape
newImage = np.zeros( (16,16), dtype = img.dtype ) ... (1)
new_height = img_height//16
new_width = img_width//16 ... (2)
for j in range(16):
for i in range(16):
y = j*new_height
x = i*new_width ... (3)
pixel = img[y:y+new_height, x:x+new_width] ... (4)
newImage[j,i] = pixel.sum(dtype='int64')//(new_height*new_width) ... (5)
#newImage[j,i] = cv2.mean(pixel)[0] ... (6)
cv2.imshow('sample',newImage)
cv2.waitKey()
cv2.destroyAllWindows()
(1) - np.zeros 함수로 (16,16) 사이즈의 배열을 만듭니다. 이 때 배열안의 데이터 타입은 img와 동일하게 설정했습니다.
(2) - new_height과 new_width는 위에서 계산한 400/16=25 로 선택 영역의 세로,가로 크기입니다.
(3) - y와 x는 선택 영역의 시작 좌표입니다. 자세한 설명은 (4)에서 이어가겠습니다.
(4) - 픽셀 (y,x) 부터 가로 사이즈 new_width, 세로 사이즈 new_height 크기의 사각형 영역을 위와 같이 img[y:y+new_height, x:x+new_width] 로 지정하였습니다. 따라서 변수 pixel은 해당 범위를 슬라이스한 ndarray가 됩니다.
(5) - numpy의 내장 함수 sum을 사용하여 해당 배열의 원소 합을 구합니다. 영역의 밝기 합을 구한 뒤, 영역의 크기인 new_height*new_width로 나누어 평균값을 구했고, new_image 배열에 저장했습니다.
(6) - numpy의 sum을 사용한 것이 아닌 openCV의 내장함수 mean을 사용한 예입니다. [0]이 붙는 이유는 위 함수는 채널별로 합을 구해서 리턴해주기 때문입니다. 자세한 설명은 컬러이미지 내용을 추가할 때 덧붙이겠습니다. 다만 흑백영상에서는 이런식으로 사용할 수 있다 정도로 넘어가면 될 것 같습니다.
※ 정확히 1/2, 1/3 이렇게 딱 떨어지는 리사이징은 영역별 평균값을 구해 처리해도 비슷해 보일 수 있지만 그렇지 않은 경우도 있습니다. 이럴 때는 최대한 원본과 비슷한 이미지를 구하기 위해 다른 복잡한 방법을 사용하기도 합니다.
'컴퓨터비전' 카테고리의 다른 글
[이미지 프로세싱] 양자화와 리사이징 (0) | 2019.06.26 |
---|---|
[이미지 프로세싱] 디지털 신호란? 디지털 영상과 샘플링 (0) | 2019.06.25 |