Skip to main content

Command Palette

Search for a command to run...

5. 기본 배열 연산 (Operations on Arrays)

Published
•15 min read•View as Markdown
U
Keep learning toward wellbeing life with co-livers

목차


Chapter 5 전체 구조


5.1 기본 배열 처리 함수

행렬(배열)의 형태를 변환하는 함수들이다. 카메라 영상처럼 좌우가 뒤집힌 영상을 보정하거나, 영상을 반복·전치하는 데 활용한다.

주요 함수

함수설명
cv2.flip(src, flipCode[, dst])배열을 수직/수평/양축으로 뒤집는다
cv2.repeat(src, ny, nx[, dst])배열을 세로 ny회, 가로 nx회 반복한다
cv2.transpose(src[, dst])배열의 전치 행렬을 반환한다

cv2.flip() flipCode 기준

flipCode기준 축효과
0수평축 (x-axis)상하 반전
1수직축 (y-axis)좌우 반전 (거울 효과)
-1양축 동시상하 + 좌우 반전 (180도 회전과 동일)

[, dst] 인수의 의미

# dst 생략 → 새 배열 반환
flipped = cv2.flip(img, 1)

# dst 지정 → 결과가 dst 배열에 저장 (메모리 재사용)
dst = np.empty_like(img)
cv2.flip(img, 1, dst)

[dst]는 선택적 인수이다. 생략하면 OpenCV가 새 배열을 만들어 반환하고, 지정하면 해당 배열에 결과를 저장하므로 메모리를 재사용할 수 있다.

예시 코드 (01.mat_array.py)

import numpy as np, cv2

image = cv2.imread('images/flip_test.jpg', cv2.IMREAD_COLOR)

x_axis   = cv2.flip(image, 0)   # 상하 반전
y_axis   = cv2.flip(image, 1)   # 좌우 반전
xy_axis  = cv2.flip(image, -1)  # 상하+좌우 반전
rep_image   = cv2.repeat(image, 1, 2)   # 가로 방향으로 2회 반복
trans_image = cv2.transpose(image)      # 전치 (행↔열 교환)

transpose() 동작 원리

원본 (M×N):          전치 (N×M):
[ a  b  c ]          [ a  d ]
[ d  e  f ]    →     [ b  e ]
                     [ c  f ]

행과 열이 교환됨
영상에서는 90도 회전과 유사하지만 정확히 같지 않음

5.2 채널 처리 함수

OpenCV에서 컬러 영상은 BGR 3채널 행렬로 저장된다. 채널을 분리하거나 합성하여 특정 채널만 처리할 수 있다.

주요 함수

함수설명
cv2.merge(mv[, dst])단일채널 배열들을 → 다채널 배열로 합성
cv2.split(m[, mv])다채널 배열을 → 단일채널 배열들로 분리

채널 분리/합성 예시 (02.mat_channel.py)

import numpy as np, cv2

ch0 = np.zeros((2, 4), np.uint8) + 10   # 값 10으로 채워진 채널
ch1 = np.ones((2, 4), np.uint8) * 20    # 값 20
ch2 = np.full((2, 4), 30, np.uint8)     # 값 30

list_bgr  = [ch0, ch1, ch2]
merge_bgr = cv2.merge(list_bgr)   # (2, 4, 3) 다채널 행렬
split_bgr = cv2.split(merge_bgr)  # 다시 3개의 단일채널로 분리

print(merge_bgr.shape)             # (2, 4, 3)
print(np.array(split_bgr).shape)   # (3, 2, 4)

cv2.split()의 반환값은 튜플이다. (numpy 배열의 리스트가 아님)

실제 컬러 영상 채널 분리 (03.image_channels.py)

image = cv2.imread("images/color.jpg", cv2.IMREAD_COLOR)
bgr = cv2.split(image)  # B, G, R 채널을 각각 분리

cv2.imshow("Blue channel",  bgr[0])  # 파란색이 많은 영역이 밝게 나타남
cv2.imshow("Green channel", bgr[1])
cv2.imshow("Red channel",   bgr[2])
채널 분리 결과해석
Blue 채널 밝은 영역원본 영상에서 파란색이 강한 부분
Green 채널 밝은 영역원본 영상에서 초록색이 강한 부분
Red 채널 밝은 영역원본 영상에서 빨간색이 강한 부분

5.3 산술 연산 함수

5.3.1 사칙 연산

OpenCV의 산술 연산은 포화 연산(Saturate)을 수행한다. 결과값이 자료형의 범위를 초과하면 최솟값/최댓값으로 클리핑된다. (예: uint8에서 255+10 = 255, 0-10 = 0)

주요 공통 인수

인수설명
src1, src2입력 배열 또는 스칼라
dst결과 출력 배열
mask8비트 단일채널 마스크 — 0이 아닌 위치만 연산 수행
dtype출력 배열의 자료형

cv2.add() / cv2.subtract()

m1 = np.full((3, 6), 10, np.uint8)
m2 = np.full((3, 6), 50, np.uint8)

# 마스크: 오른쪽 절반(3열~)만 연산 수행
m_mask = np.zeros(m1.shape, np.uint8)
m_mask[:, 3:] = 1

m_add1 = cv2.add(m1, m2)               # 전체 덧셈
m_add2 = cv2.add(m1, m2, mask=m_mask)  # 마스크 영역만 덧셈

cv2.multiply() — scale 인수 주의

cv2.multiply(src1, src2[, dst[, scale[, dtype]]])
  • scale: 두 배열의 원소 곱에 추가로 곱해주는 배율
  • 수식: dst(i) = saturate(src1(i) × src2(i) × scale)

cv2.addWeighted() — 영상 블렌딩에 자주 사용

cv2.addWeighted(src1, alpha, src2, beta, gamma)

$$dst = \alpha \cdot src1 + \beta \cdot src2 + \gamma$$

인수설명
alphasrc1의 가중치
betasrc2의 가중치
gamma합산 결과에 추가로 더해주는 스칼라
# 예: 두 영상을 50:50으로 합성
blended = cv2.addWeighted(img1, 0.5, img2, 0.5, 0)

uint8 vs float32 나눗셈 비교 (04.arithmetic_op.py)

m1 = np.full((3, 6), 10, np.uint8)
m2 = np.full((3, 6), 50, np.uint8)

m_div1 = cv2.divide(m1, m2)                          # uint8 → 소수점 버림 (0)
m1 = m1.astype(np.float32)
m2 = np.float32(m2)
m_div2 = cv2.divide(m1, m2)                          # float32 → 0.2 정확히 계산

uint8 나눗셈 시 소수 부분이 버려진다. 정밀한 나눗셈 결과가 필요하면 반드시 float32 형변환 후 연산한다.


5.3.2 지수·로그·제곱근 관련 함수

모든 수학 함수는 ndarray 객체만 입력 가능하다. 파이썬 리스트는 직접 전달 불가.

기본 수학 함수

함수수식비고
cv2.exp(src)$e^{src}$자연지수
cv2.log(src)$\ln(\src\)$자연로그, 절댓값 먼저
cv2.sqrt(src)$\sqrt{src}$제곱근
cv2.pow(src, p)$src^p$p승
v1 = np.array([1, 2, 3], np.float32)   # float32 필수
v2 = np.array([[1], [2], [3]], np.float32)  # 열벡터 (3×1)
v3 = np.array([[1, 2, 3]], np.float32)      # 행벡터 (1×3)

v1_exp  = cv2.exp(v1)
v1_log  = cv2.log(v1)
v2_sqrt = cv2.sqrt(v2)
v3_pow  = cv2.pow(v3, 3)   # 세제곱

# 열벡터 출력 시 활용하는 변환 패턴
print(v1_log.transpose())  # 전치 → 행벡터
print(np.ravel(v2_sqrt))   # ravel() → 1차원 배열
print(v3_pow.flatten())    # flatten() → 1차원 배열

크기(magnitude)와 각도(phase)

$$magnitude(i) = \sqrt{x(i)^2 + y(i)^2}$$

$$angle(i) = \text{atan2}(y(i), x(i))$$

x = np.array([1, 2, 3, 5, 10], np.float32)
y = np.array([2, 5, 6, 2,  9], np.float32)

mag = cv2.magnitude(x, y)           # 크기 계산
ang = cv2.phase(x, y)               # 각도 계산 (기본: 라디안)
p_mag, p_ang = cv2.cartToPolar(x, y)    # 직교 → 극 좌표 동시 변환
x2, y2 = cv2.polarToCart(p_mag, p_ang) # 극 → 직교 좌표 복원
인수설명
anglenDegrees=True각도를 도(degree)로 반환
anglenDegrees=False (기본값)각도를 라디안으로 반환

5.3.3 논리(비트) 연산 함수

비트 단위 연산으로 영상의 특정 영역을 추출하거나 마스킹할 때 핵심적으로 사용된다.

진리표

비트 A비트 BANDORXORNOT A
000001
010111
100110
111100

함수 목록

함수연산특징
cv2.bitwise_and(src1, src2)비트별 AND두 영역의 교집합 추출
cv2.bitwise_or(src1, src2)비트별 OR두 영역의 합집합
cv2.bitwise_xor(src1, src2)비트별 XOR두 영역이 다른 부분만 추출
cv2.bitwise_not(src)비트별 NOT흑백 반전
# 07.bitwise_op.py
image1 = np.zeros((300, 300), np.uint8)
image2 = image1.copy()
cx, cy = 150, 150

cv2.circle(image1, (cx, cy), 100, 255, -1)       # 원
cv2.rectangle(image2, (0, 0, cx, 300), 255, -1)  # 좌측 절반 사각형

image3 = cv2.bitwise_or(image1, image2)   # 합집합
image4 = cv2.bitwise_and(image1, image2)  # 교집합
image5 = cv2.bitwise_xor(image1, image2)  # 차집합
image6 = cv2.bitwise_not(image1)          # 반전

실전 응용 — 로고 합성 (08.bitwise_overlap.py)

# 이진화로 로고의 마스크 생성
masks = cv2.threshold(logo, 220, 255, cv2.THRESH_BINARY)[1]
masks = cv2.split(masks)

fg_pass_mask = cv2.bitwise_or(masks[0], masks[1])
fg_pass_mask = cv2.bitwise_or(masks[2], fg_pass_mask)  # 전경 마스크
bg_pass_mask = cv2.bitwise_not(fg_pass_mask)            # 배경 마스크

foreground = cv2.bitwise_and(logo, logo, mask=fg_pass_mask)  # 로고 전경
background = cv2.bitwise_and(roi,  roi,  mask=bg_pass_mask)  # 원본 배경
dst = cv2.add(background, foreground)  # 합성

5.4 절댓값 연산

영상 차분(Frame Difference) 등에서 음수값이 발생할 때 절댓값으로 변환하여 다음 처리 단계로 넘긴다.

주요 함수

함수수식설명
cv2.absdiff(src1, src2)$\src1 - src2\$두 배열 간 차분의 절댓값
cv2.convertScaleAbs(src, alpha, beta)$\src \times \alpha + \beta\$ → uint8스케일 + 절댓값 + uint8 변환
# 10.mat_abs.py
dif_img1 = cv2.subtract(image1, image2)  # 음수 → 0 클리핑 (정보 손실)

# 음수 보존이 필요한 경우: int16으로 변환 후 연산
dif_img2 = cv2.subtract(np.int16(image1), np.int16(image2))
abs_dif1 = np.absolute(dif_img2).astype('uint8')

# 가장 간단한 방법: absdiff()
abs_dif2 = cv2.absdiff(image1, image2)  # 권장

단순히 cv2.subtract(uint8, uint8)를 사용하면 음수가 0으로 클리핑되어 정보가 손실된다. 차분 절댓값이 필요하다면 cv2.absdiff()를 사용하거나, int16으로 형변환 후 처리한다.


5.4.1 원소의 최솟값과 최댓값

함수반환값설명
cv2.min(src1, src2)배열원소 간 비교하여 작은 값으로 구성된 배열
cv2.max(src1, src2)배열원소 간 비교하여 큰 값으로 구성된 배열
cv2.minMaxLoc(src)(minVal, maxVal, minLoc, maxLoc)전체 최솟값/최댓값과 그 위치 반환
# 10.mat_min_max.py
data = [10, 200, 5, 7, 9, 15, 35, 60, 80, 180, 100, 2, 55, 27, 70]
m1 = np.reshape(data, (3, 5))
m2 = np.full((3, 5), 50)

m_min = cv2.min(m1, 30)   # 각 원소와 스칼라 30 중 작은 값
m_max = cv2.max(m1, m2)   # 두 행렬의 원소 간 큰 값

min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(m1)

minMaxLoc()의 반환 좌표 (loc)는 (x열, y행) 순서이다. 행렬의 인덱스 순서 [행][열]과 반대이므로 주의한다.

실전 응용 — 영상 정규화 (11.image_min_max.py)

min_val, max_val, _, _ = cv2.minMaxLoc(image)
ratio = 255 / (max_val - min_val)
dst = np.round((image - min_val) * ratio).astype('uint8')
# 결과: 원본의 최솟값 → 0, 최댓값 → 255로 선형 변환

5.5 통계 관련 함수

배열 원소들의 합·평균·표준편차·정렬 등 통계적 처리를 담당한다.

합과 평균 (12.sum_avg.py)

image = cv2.imread("images/sum_test.jpg", cv2.IMREAD_COLOR)

# 마스크 영역(ROI) 지정
mask = np.zeros(image.shape[:2], np.uint8)
mask[60:160, 20:120] = 255

sum_value  = cv2.sumElems(image)       # 채널별 합 → 튜플 (B합, G합, R합, 0)
mean_val1  = cv2.mean(image)           # 채널별 평균 → 튜플
mean_val2  = cv2.mean(image, mask)     # 마스크 영역만 평균

mean, stddev   = cv2.meanStdDev(image)       # 전체 평균·표준편차
mean2, stddev2 = cv2.meanStdDev(image, mask=mask)  # 마스크 영역
함수반환 자료형반환 구조
cv2.sumElems()tuple(B합, G합, R합, 0)
cv2.mean()tuple(B평균, G평균, R평균, 0)
cv2.meanStdDev()numpy.ndarray (float64)(채널수×1) 배열

정렬 (13.sort.py, 14.sortIdx.py)

m = np.random.randint(0, 100, 15).reshape(3, 5)

# cv2.sort() — 정렬된 값 반환
sort1 = cv2.sort(m, cv2.SORT_EVERY_ROW)                           # 행 단위 오름차순
sort2 = cv2.sort(m, cv2.SORT_EVERY_COLUMN)                        # 열 단위 오름차순
sort3 = cv2.sort(m, cv2.SORT_EVERY_ROW + cv2.SORT_DESCENDING)    # 행 단위 내림차순

# cv2.sortIdx() — 정렬된 원소의 인덱스 반환
sort1_idx = cv2.sortIdx(m, cv2.SORT_EVERY_ROW)     # 각 행에서 원소의 원래 열 인덱스
sort2_idx = cv2.sortIdx(m, cv2.SORT_EVERY_COLUMN)  # 각 열에서 원소의 원래 행 인덱스

# NumPy 대응 함수
sort4 = np.sort(m, axis=1)             # cv2.sort + SORT_EVERY_ROW 동일
sort5 = np.sort(m, axis=0)             # cv2.sort + SORT_EVERY_COLUMN 동일
sort6 = np.sort(m, axis=1)[:, ::-1]   # 행 단위 내림차순
sort7 = np.argsort(m, axis=0)          # cv2.sortIdx + SORT_EVERY_COLUMN 동일

cv2.sortIdx()는 값 대신 정렬 후 원소들의 원래 인덱스를 반환한다. 정렬 순서를 추적하거나 원본 데이터 위치를 참조할 때 유용하다.

행렬 축소 (16.mat_reduce.py)

cv2.reduce()는 행렬을 열 방향(dim=0) → 1행 또는 행 방향(dim=1) → 1열로 축소한다.

rtype 상수연산
cv2.REDUCE_SUM합산
cv2.REDUCE_AVG평균
cv2.REDUCE_MAX최댓값
cv2.REDUCE_MIN최솟값
m = np.random.rand(3, 5) * 100

reduce_sum = cv2.reduce(m, dim=0, rtype=cv2.REDUCE_SUM)  # 각 열의 합 → (1, 5) 행벡터
reduce_avg = cv2.reduce(m, dim=1, rtype=cv2.REDUCE_AVG)  # 각 행의 평균 → (3, 1) 열벡터
reduce_max = cv2.reduce(m, dim=0, rtype=cv2.REDUCE_MAX)  # 각 열의 최댓값
reduce_min = cv2.reduce(m, dim=1, rtype=cv2.REDUCE_MIN)  # 각 행의 최솟값

cv2.reduce()는 np.float32 또는 np.float64 타입 배열에서만 사용 가능하다.


5.6 행렬 연산 함수

OpenCV는 선형대수 계산을 위한 행렬 연산 함수를 제공한다.

cv2.gemm() — 일반화 행렬 곱셈

$$dst = \alpha \cdot src1^T \cdot src2 + \beta \cdot src3^T$$

cv2.gemm(src1, src2, alpha, src3, beta[, dst[, flags]])
인수설명
src1, src2곱할 두 입력 행렬 (float32/float64, 2채널 이하)
alpha행렬 곱에 대한 가중치
src3행렬 곱 결과에 더해지는 델타 행렬 (없으면 None)
betasrc3에 곱해지는 가중치
flags전치 플래그 조합
flags동작
cv2.GEMM_1_Tsrc1을 전치한 후 곱셈
cv2.GEMM_2_Tsrc2을 전치한 후 곱셈
cv2.GEMM_3_Tsrc3을 전치
# 17.gemm.py
src1 = np.array([1, 2, 3, 1, 2, 3], np.float32).reshape(2, 3)
src2 = np.array([1, 2, 3, 4, 5, 6], np.float32).reshape(2, 3)

# src1^T × src2  (src1을 전치)
dst1 = cv2.gemm(src1, src2, 1.0, None, 1.0, flags=cv2.GEMM_1_T)

# src1 × src2^T  (src2를 전치)
dst2 = cv2.gemm(src1, src2, 1.0, None, 1.0, flags=cv2.GEMM_2_T)

실전 응용 — 좌표 회전 변환 (18.point_transform.py)

회전 변환 행렬과 cv2.gemm()을 조합하면 좌표를 임의 각도로 회전시킬 수 있다.

theta = 20 * np.pi / 180  # 20도 → 라디안
rot_mat = np.array([[np.cos(theta), -np.sin(theta)],
                    [np.sin(theta),  np.cos(theta)]], np.float32)

pts1 = np.array([(250, 30), (400, 70), (350, 250), (150, 200)], np.float32)
pts2 = cv2.gemm(pts1, rot_mat, 1, None, 1, flags=cv2.GEMM_2_T)  # 회전 적용

# 결과 시각화
cv2.polylines(image, [np.int32(pts1)], True, (0, 255, 0), 2)  # 원본: 초록
cv2.polylines(image, [np.int32(pts2)], True, (255, 0, 0), 3)  # 회전: 파랑

cv2.invert() — 역행렬 계산

cv2.invert(src[, dst[, flags]])  → (retval, dst)
flags역행렬 계산 방법입력 행렬 조건
cv2.DECOMP_LU가우시안 소거법역행렬이 존재하는 정방행렬
cv2.DECOMP_SVD특이치 분해(SVD)정방행렬이 아닌 경우 → 의사 역행렬
cv2.DECOMP_CHOLESKY촐레스키 분해대칭 + 양의 정부호 정방행렬

cv2.solve() — 연립방정식 풀이

$$Ax = b \quad \Rightarrow \quad x = A^{-1}b$$

# 19.equation.py  —  연립방정식 Ax = b 풀기
data = [3, 0, 6, -3, 4, 2, -5, -1, 9]
m1 = np.array(data, np.float32).reshape(3, 3)   # 계수 행렬 A
m2 = np.array([36, 10, 28], np.float32)          # 상수 벡터 b

# 방법 1: 역행렬로 직접 계산
ret, inv = cv2.invert(m1, cv2.DECOMP_LU)
if ret:
    dst1 = inv.dot(m2)                        # NumPy 행렬곱
    dst2 = cv2.gemm(inv, m2, 1, None, 1)     # OpenCV 행렬곱

# 방법 2: cv2.solve() 직접 사용 (권장)
_, dst3 = cv2.solve(m1, m2, cv2.DECOMP_LU)

cv2.solve()는 역행렬을 명시적으로 구하지 않고 방정식을 직접 풀기 때문에 수치 안정성이 더 좋고 일반적으로 더 빠르다.


핵심 함수 정리


중요 포인트 요약

주제핵심 주의사항
포화 연산cv2.add(uint8): 결과 > 255 → 255 클리핑, 결과 < 0 → 0 클리핑
나눗셈 정밀도uint8나눗셈은 소수 버림 → 정밀도 필요 시 float32 변환 필수
차분 절댓값cv2.subtract(uint8)는 음수를 0으로 버림 → cv2.absdiff() 사용 권장
수학 함수 입력cv2.exp() 등은 Python 리스트 불가 → 반드시 np.ndarray 전달
minMaxLoc 좌표반환값이 (x열, y행) 순서 → 행렬 인덱스 [행][열]과 반대
reduce 자료형cv2.reduce()는 float32 / float64 배열만 처리 가능
gemm 자료형cv2.gemm()은 float32 / float64 배열, 최대 2채널까지 가능
split 반환값cv2.split() 반환 타입은 tuple (리스트 아님)

More from this blog

카메라 영상 $\rarr$ 유속 추정(A to Z) 프로젝트 소개

프로젝트 목적 말 그대로 카메라 영상에서 하천 흐름의 유속값과 유속의 분포 데이터를 안정적으로 추정하기 위한 파이프 라인을 설계하기 위해 제로 베이스에서 시작해서 이해도를 높여보기 위한 프로젝트 수식에 대한 이해와 함께 opencv, python 기반으로 실제 구현 코드를 작성해보고 및 소스코드의 현장 적용성도 함께 검토하고자 한다. 프로젝트 수행 방법 기존에 사용하고 있는 태블릿의 코틀린 코드와 UI를 최대한 그대로 사용하고자 하며, 1...

May 1, 20265 min read2

print(obj.__secret) # 오류 발생! (직접 접근 불가)

객체 지향 프로그래밍 클래스와 인스턴스 클래스 메서드들은 중첩된 def이며, 특수한 첫번째 인자는 묵시적으로 대상 인스턴스를 받는다. 네, 정확합니다! self를 설명하는 내용이 맞습니다. 이 문장을 조금 더 자세히 풀어드리면 다음과 같습니다. 1. "클래스 메서드들은 중첩된 def이며" 파이썬에서 클래스 내부의 함수(메서드)는 클래스라는 '객체 내부'에 정의되어 있습니다. def는 함수를 만드는 키워드인데, 클래스라는 범위(scope) ...

Apr 28, 20264 min read2

9장 함수(Python)

9.3 인수와 매개변수 자바와 동일하게? 함수 외부에서는 인수라고 하지만 내부에서는 매개변수라고 한다. 함수로 전달한 값을 인수라고 부른다. 인수의 값은 함수 내에서 해당하는 매개변수에 복사된다. 함수가 명시적으로 return 을 호출하지 않으면, 호출자는 반환값으로 None을 얻는다. 9.3.1 유용한 None None은 아무것도 없다는 것을 뜻하는 파이썬의 특별한 값이다. 빠뜨린 빈 값을 구분하기 위해 None을 사용한다. 불리언 값 ...

Apr 20, 202613 min read

[ITSantiago]의 개발 블로그 | TIL & Tech Log

36 posts