NumPy로 이미지 데이터의 본질 이해하기
shape·dtype·range로 이미지 배열을 이해하고 OpenCV 전처리 버그를 예방하는 핵심 가이드
Posted
By okorion
NumPy로 이미지 데이터의 본질 이해하기
TL;DR
- 이미지는 “그림”이 아니라 NumPy 배열이다.
- 모든 이미지 버그의 80%는
shape / dtype / value rangemisunderstanding에서 발생한다. - OpenCV 문제는 대부분 연산 자체가 아니라 데이터 상태의 문제다.
uint8 ↔ float32전환은 결과를 완전히 바꾼다.- slicing / broadcasting은 이미지 조작의 핵심 도구다.
- 이 글을 이해하지 못하면 이후 모든 CV 학습은 운에 맡기게 된다.
1. 핵심 개념: 이미지 = 배열이라는 말의 정확한 의미
이미지를 배열이라고 말할 때, 이는 비유가 아니다. 정확히 NumPy ndarray다.
기본 구조
1
2
3
4
5
6
7
8
9
10
11
이미지 배열의 3요소
├─ shape
│ ├─ H: height (행)
│ ├─ W: width (열)
│ └─ C: channel (색상)
├─ dtype
│ ├─ uint8 (0~255)
│ └─ float32 (0.0~1.0 or 그 이상)
└─ value range
├─ 정수 이미지: [0, 255]
└─ 실수 이미지: [0.0, 1.0] (관례)
OpenCV 기본 이미지는 다음 형태다:
1
(H, W, 3), dtype=uint8, BGR
2. 왜 이걸 반드시 이해해야 하는가
이미지 연산은 수학 연산이다. 문제는 이미지가 “보여서” 직관을 속인다는 점이다.
- 같은 연산이라도 dtype이 다르면 결과가 완전히 달라진다.
- 값 범위를 넘으면 overflow / clip이 조용히 발생한다.
- slicing은 복사(copy)가 아니라 뷰(view) 일 수 있다.
👉 즉, 코드가 돌아가도 결과가 틀릴 수 있다.
3. 언제 이 문제가 실제로 터지는가
- 밝기/대비 조절이 이상할 때
- threshold 결과가 예상과 다를 때
- 색상이 갑자기 깨질 때
- 모델 입력 전처리에서 성능이 안 나올 때
- 같은 코드를 다른 환경에서 돌렸을 때
4. 최소 실습: NumPy로 이미지의 본질 직접 만져보기
외부 이미지 ❌ NumPy로 직접 생성한다.
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
32
33
34
import numpy as np
def print_info(name, arr):
print(f"[{name}] shape={arr.shape}, dtype={arr.dtype}, "
f"min={arr.min()}, max={arr.max()}")
# 1. 가짜 이미지 생성 (그라디언트)
H, W = 100, 200
img = np.tile(np.linspace(0, 255, W, dtype=np.uint8), (H, 1))
img = np.stack([img, img, img], axis=-1) # (H, W, 3)
print_info("original", img)
# 2. 밝기 증가 (uint8)
bright = img + 50
print_info("bright_uint8", bright)
# 3. float 변환 후 밝기 조절
img_f = img.astype(np.float32) / 255.0
bright_f = np.clip(img_f + 0.2, 0.0, 1.0)
print_info("bright_float", bright_f)
# 4. 마스킹 (중앙 영역만)
mask = np.zeros((H, W), dtype=bool)
mask[30:70, 60:140] = True
masked = img.copy()
masked[~mask] = 0
print_info("masked", masked)
# 5. 채널 스왑 (RGB <-> BGR 실험용)
swapped = img[..., ::-1]
print_info("channel_swapped", swapped)
결과 해석 포인트
uint8에서 덧셈은 255를 넘어가면 overflowfloat32에서는 값 범위를 직접 관리해야 한다- 마스킹은 좌표계(H, W)에 대한 정확한 이해가 필수
- 채널 순서가 바뀌어도 shape은 같아서 버그가 조용히 발생
5. 이미지 연산이 어려운 이유 (실수 사례 기반)
이미지 연산이 어려운 이유는 하나다.
눈에 보이는 결과가 항상 논리적으로 옳지 않다
대표적인 함정:
- 값은 정상인데 dtype이 틀림
- 연산은 맞는데 범위가 틀림
- shape은 맞는데 의미가 틀림
- 복사한 줄 알았는데 view였음
6. 흔한 실수 / 디버깅 체크리스트 (중요)
uint8상태에서 덧셈/곱셈 수행- overflow 발생했는데 눈치 못 챔
- float 이미지인데 값 범위 0~255 유지
- BGR/RGB 혼동
(H,W)와(H,W,1)혼동- slicing 결과가 view인지 copy인지 모름
- mask dtype이 bool이 아님
- 채널 축(axis=-1) 개념 불명확
astype위치가 잘못됨- min/max 출력 없이 연산 진행
- 정규화 후 다시 uint8로 안 돌림
7. OpenCV로 넘어가기 전에 반드시 점검할 5문장
- 나는 이미지의
shape / dtype / range를 항상 출력한다. - uint8 연산의 위험성을 설명할 수 있다.
- float 이미지에서 clip/정규화 이유를 안다.
- slicing이 view인지 copy인지 구분한다.
- BGR/RGB 문제를 즉시 의심한다.
섹션 요약
- 이미지는 수치 데이터다.
- OpenCV 문제의 대부분은 NumPy 문제다.
- 이 글을 기준으로 이후 모든 이미지 처리는 “데이터 상태 점검 → 연산” 순서로 진행해야 한다.
다음 글 / 다음 학습
➡️ 다음 글: OpenCV 이미지 처리 연산: 목적별 분류와 전처리 파이프라인
다음 글에서는 NumPy로 이해한 이 개념들이 OpenCV API에서 어떻게 구현되는지를 본격적으로 연결한다.
