계기
무작정 OpenCV 박치기를 해보니 openCV의 이미지파일이 numpy 배열이라는 것을 알게 되었다. 하지만 알아봤자 대체 rgba가 어떤 식으로 펼쳐져 있는지, 파이썬에서 어떻게 배열을 다뤄야하는지 몰라 난항을 겪고 있다. 일단 뭘 하는지 알아내기 위해 각 채널 별 배열부터 알아보자.
OpenCV의 채널
openCV에서 이미지 파일을 읽을 때 cv2.imread()
함수를 사용한다. 첫 번째 인자는 파일의 주소
이고 두 번째 인자는 flag
다. flag
에 따라 이미지를 불러 올 때 채널 값이 바뀐다. 채널은 불러온 이미지에.shape
를 하여 얻은 배열의 3번 째 값이다.
cv2.IMREAD_COLOR 혹은 1 : 이미지를 Color로 읽음. 투명 무시. Default.
cv2.IMREAD_GRAYSCALE 혹은 0 : 이미지를 Grayscale로 읽음.
cv2.IMREAD_UNCHANGED 혹은 -1 : 이미지파일을 alpha channel까지 포함하여 읽음. 투명 보존.
채널이 큰 숫자일수록 더 다양한 표현이 가능하다.
큰 이미지를 사용하니 배열을 파악하기 어려워 대충 아래같은 png파일을 만들었다. 세로 5px, 가로 5px이다.
Grayscale
color이 디폴트이긴 하지만 채널이 적은 grayscale이 쉬울 것 같으니 이걸로 시작한다.
import cv2
grayscale = cv2.imread('.\\img\\cvTest.png', 0)
print("shape :",grayscale.shape)
print("arr :\n",grayscale)
grayscale_big=cv2.resize(grayscale, dsize=(0, 0), fx=100, fy=100, interpolation=cv2.INTER_NEAREST)
cv2.imwrite('.\\img\\grayscale.png', grayscale_big)
결과
shape : (5, 5)
arr :
[[ 0 255 0 255 0]
[255 138 255 138 255]
[150 255 150 255 150]
[255 203 255 203 255]
[144 255 144 255 144]]
채널이 없는데 그 이유는 .shape
는 배열 형태를 알려주는 numpy의 메소드이기 때문이다. 밑을 보면 알겠지만 color와 unchanged는 3중 배열이기 때문에 마지막 값이 있다.
이중 배열 속에 위에서부터 한 줄씩 픽셀값이 담겨있다. 저장된 이미지는 다음과 같다.
지금 내가 1차로 필요한게 클립스튜디오에 있는 기능 중 하나인 휘도를 투명도로 변환
인데, grayscale
을 잘 활용하면 편하게 될 것 같다.
Color
rgb가 필요하기 때문에 color의 채널은 3이다.
import cv2
color = cv2.imread('.\\img\\cvTest.png', 1)
print("shape :", color.shape)
print("arr :\n", color)
color_big=cv2.resize(color, dsize=(0, 0), fx=100, fy=100, interpolation=cv2.INTER_NEAREST)
cv2.imwrite('.\\img\\grayscale.png', color_big)
결과
shape : (5, 5, 3)
arr :
[[[ 0 0 0]
[255 255 255]
[ 0 0 0]
[255 255 255]
[ 0 0 0]]
[[255 255 255]
[ 36 28 237]
[255 255 255]
[ 36 28 237]
[255 255 255]]
[[232 162 0]
[255 255 255]
[232 162 0]
[255 255 255]
[232 162 0]]
[[255 255 255]
[ 29 230 181]
[255 255 255]
[ 29 230 181]
[255 255 255]]
[[190 146 112]
[255 255 255]
[190 146 112]
[255 255 255]
[190 146 112]]]
3중 배열이다.
3중 배열의 첫 번째 2중 배열에는 5개의 배열이 들어있다. 검정색의 rgb값은 (0, 0, 0)이고 흰색은 rgb값은 (255, 255, 255)이므로, 각 2중 배열은 한 줄씩을 담당한다는 것을 알 수 있다.
채널에 관계없이 제일 처음에 접하는 배열 속 원소들은 각각 가로줄 하나를 뜻하는 듯 하다.
이미지에 투명 부분이 없기 때문에 결과는 그냥 똑같다.
Unchanged
드디어 alpha chennel을 다루나 했는데 아무래도 내가 그림판으로 대강 만든 이미지에는 alpha chennel이 없는 듯 하다. 플래그 이름이 unchanged
이니 이미 없는건 당연히 못만들고.. 급한대로 이미 가지고 있던 투명도 있는 png파일을 작게 편집했다.
좀 길까봐 3*3 으로 했다.
unchanged = cv2.imread('.\\img\\cvTestAlpha.png', -1)
print("shape :", unchanged.shape)
print("arr :\n", unchanged)
unchanged_big=cv2.resize(unchanged, dsize=(0, 0), fx=100, fy=100, interpolation=cv2.INTER_NEAREST)
cv2.imwrite('.\\img\\unchanged.png', unchanged_big)
결과
shape : (3, 3, 4)
arr :
[[[ 0 0 0 255]
[235 116 116 255]
[ 0 0 0 255]]
[[234 107 107 255]
[ 0 0 0 255]
[237 140 140 255]]
[[ 0 0 0 255]
[233 123 123 255]
[ 0 0 0 255]]]
아까처럼 이번에도 3중 배열이며, 최상위 배열의 원소들은 각각 한 줄씩을 담당한다. 제일 하위의 배열에 원소가 하나 더 는게 color와의 차이점이다.
내가 투명하지 않은 부분을 잘랐는지 불투명도는 모두 255이다.
투명화하기
지금 필요한 것은 그래픽 툴 클립스튜디오의 휘도를 투명도로 변환
와 유사한 기능이다.
특정 픽셀이 검은색과 먼만큼 그 부분을 투명도로 바꿔주는 것이다. 이렇게 하면 이미지는 흑백이 되지만 투명도가 생겨 여기저기 넣기 좋다.
과정을 생각해보면
- 이미지에서 픽셀별 검은 정도를 추출
- rgba에서 rgb는 (0, 0, 0)으로 검정색으로 만들고 a에 (255-검은 정도) 넣기
1번은 직접 추출하지 않고 이미지를 grayscale로 변환해 흑백으로 만든 뒤 그게 가지는 값을 이용한다.
실제 구현은 다음과 같다.
def change_to_transparent(img):
channel = get_channel(img.shape)
result = np.zeros((img.shape[0], img.shape[1], 4), dtype=np.uint8)
if channel == 3 :
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
elif channel == 4 :
img = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
for i in range(0, img.shape[0]):
for j in range(0, img.shape[1]):
result[i, j, 3] = 255 - img[i][j]
return result
코드 해설
channel = get_channel(img.shape)
위에서 말한 것처럼 grayscale은 2중배열이라 shape 값이 아예 비어있으므로 마냥 .shape[2]
를 하면 out of index
오류가 발생한다. javascript
에서처럼 ||
을 통해 예외처리를 하려고 했는데 되지 않아 그냥 따로 함수로 빼서 처리했다.
// 자바스크립트로 채널을 추출할 시
const channel = img.shape[2] || 0
파이썬에서 위처럼 한줄로 없는 요소를 처리할 수 있는 방법이 있다면 누군가 친히 알려주세요.
result = np.zeros((img.shape[0], img.shape[1], 4), dtype=np.uint8)
np.zeros는 0으로 가득찬 배열을 생성해준다. img.shape[0]
은 세로 픽셀 수, img.shape[1]
은 가로 픽셀 수이다.dtype=np.unit8
은 배열 속에 들어가는 숫자의 타입을 uint8
로 설정한다. 이렇게 하는 이유는 openCV
의 api들 중 int
타입을 지원하지 않는 경우가 있기 때문이다.
if channel == 3 :
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
elif channel == 4 :
img = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
cv2.cvtColor
은 색상공간을 변환해준다. 색상공간은 grayscale, rgba, rgb등이다.
채널 수에 따라 다른 플래그를 넣어준다.
for i in range(0, img.shape[0]):
for j in range(0, img.shape[1]):
result[i, j, 3] = 255 - img[i][j]
마지막 단계로, 처음에 만들어뒀던 0으로 가득찬 이미지 배열에서 모든 픽셀을 순회하며 투명도를 조작한다. 각 픽셀 배열(rgba이므로 length가 4인 배열이다)의 마지막 값(a)에 새로운 값을 부여하면 된다.
제일 위에 썼던 것처럼 grayscale로 변환했을 때의 값을 이용한다.
결과:
결론
처음 글을 쓰다가 다른 프로젝트들 때문에 한동안 미루었지만.. 일단 원하는 부분은 만들었다.
크기가 다른 두 이미지를 합성하기 전 단계로서 만든 기능이다.
결점은 이미 투명한 이미지를 넣을 경우에는 grayscale로 바꾸었을 때 모두 검정색 처리가 되어(사실 추정이다. 실험 안해봤다.) 결과도 검정색으로 나오게 된다. 따라서 투명한 이미지의 경우 뒤에 흰색 배경(numpy를 통해 생성, cv2.add()를 이용)을 깔아주고 위 함수에 넣어야 할 것이다.
'Programming > Python' 카테고리의 다른 글
[OpenCV] OpenCV-Python으로 배우는 영상 처리 및 응용 - 6장 연습문제 풀이 (1) | 2025.07.01 |
---|---|
[OpenCV] OpenCV-Python으로 배우는 영상 처리 및 응용 - 5장 연습문제 풀이 (0) | 2025.06.30 |
[OpenCV] OpenCV-Python으로 배우는 영상 처리 및 응용 - 4장 연습문제 풀이 (6) | 2025.06.30 |
[Jupyter extension] Jupyter 확장프로그램 개발 환경 구축 (1) | 2024.09.02 |