오늘 짤 로직은 주어진 사진에서 박스를 색깔별로 구분해 낼 수 있는 프로그램!

RGB와 같은 3개의 기준이 아닌 0~255의 흑백 픽셀값, 하나의 기준으로 객체를 분류해야 하기 때문에 이미지를 하나의 기준으로 판단할 수 있게 흑백이미지로 변환한다.
컬러 이미지를 사용하면 비효율적이다
# 이미지 읽어올 때 흑백으로 불러오기
src = cv2.imread('파일명.jpg', cv2.IMREAD_GRAYSCALE)
# 컬러 이미지(RGB)를 흑백이미지(GRAY)로 변환
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
임계값을 정해서 임계값보다 낮거나 높냐에 따라 0(흑) or 255(백)의 픽셀 값으로 만든다. 말 그래도 이진화해서 바이너리 이미지(binary image)로 만든다
# --- ① NumPy API로 바이너리 이미지 만들기
thresh_np = np.zeros_like(img) # 원본과 동일한 크기의 0으로 채워진 이미지
thresh_np[ img > 127] = 255 # 127 보다 큰 값만 255로 변경
# ---② OpenCV API로 바이너리 이미지 만들기
ret, thresh_cv = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
print(ret) # 127.0, 바이너리 이미지에 사용된 문턱 값 반환
threshold()
ret, out = cv2.threshold(img, threshold, value, type_flag)
한 번에 임계값을 찾을 수 있는 방법
임계값을 임의로 정해 픽셀을 두 부류로 나누고 두 부류의 명암 분포를 구하는 작업 반복 → 모든 경우의 수 중에서 두 부류의 명암 분포가 가장 균일할 때의 임계값 선택한다.
# cv2.THRESH_OTSU는 임계값을 자동으로 구하는 알고리즘
_, src_bin = cv2.threshold(src, 0, 255, cv2.THRESH_OTSU)
threshold 파라미터는 아무 값이어도 괜찮음
원본 이미지에서 조명이 일정하지 않거나 배경색이 여러 개인 경우 → 여러개의 임계값이 필요람
addaptiveThreshold()cv2.adaptiveThreshold(img, value, method, type_flag, block_size, C)
전체 이미지에 총 9개(block_size)의 블록을 설정한다, 이미지를 9등분한다 → 블록별로 임계값 정한다
import cv2
import numpy as np
img_color = cv2.imread('test_img.png')
img_hsv = cv2.cvtColor(img_color, cv2.COLOR_BGR2HSV) # 이미지 hsv 색상으로 변환
color_list = [[41, 48, 151],[101, 140, 55], [184, 152, 69]] # BGR 순서로 기입
color_name = ["red", "green", "blue"]
for i, color in enumerate(color_list):
pixel = np.uint8([[color]]) # 한 픽셀로 구성된 이미지로 변환
hsv = cv2.cvtColor(pixel, cv2.COLOR_BGR2HSV)
hsv_h = int(hsv[0][0][0]) # hue값 -> 색깔 구별(0~360) + numpy.unit8값 Int로 변환
HSV
RGB색 공간보다 좀 더 우리들이 색을 판단하는 과정과 유사한 것이 HSV 색 공간
색 끼리의 조합이 아니라 색깔 자체를 알려주므로 직관성이 좋음 → 색깔을 통해 이미지에서 물체를 검출하고 싶으면 HSV공간이 적합하다
한 픽셀로 구성된 이미지를 기준으로 각각의 색상을 hsv값으로 바꿔준다
주의할 점은 hsv를 기준으로 색상을 체크할 것이므로 이미지 색상도 cvtColor() 함수를 써서 hsv로 바꿔줘야 한다는 점!
lower = (hsv_h_low, 30, 30) # hsv 이미지에서 바이너리 이미지로 생성 , 적당한 값 30
upper = (hsv_h_high, 255, 255)
hsv로 색상을 변경하면 가장 좋은 점이 inRange()함수를 사용하기 편리하다
이미지를 볼 때 검출하고자 하는 객체희 모든 테두리가 동일한 색상을 가진 픽셀이 아니다
따라서 색상에 대한 범위가 필요한데, hsv를 사용하면 밝기와 진함 정도의 최대 최소는 적당한 값으로 고정시키고 색상(Hue)만 변경해준다. 그러면 그 배경과 객체의 경계점에 있는 흐릿한 픽셀의 색상 검출이 가능하다😀
S와 V값은 고정시키고 H값만 변경
img_mask = cv2.inRange(img_hsv, lower, upper)
img_result = cv2.bitwise_and(img_color, img_color, mask = img_mask)
라벨링을 해주기 위해 방금 정한 범위 내의 픽셀들은 흰색, 나머지는 검은색으로 만들어준다
바이너리 이미지(img_mask)를 마스크로 사용하여 원본이미지에서 범위값에 해당하는 영상부분을 획득한다
영어를 그대로 해석하면 잔디 + 불 알고리즘이다, 잔디에 불을 붙였을 때 불이 퍼져나가는 형태로 로직이 처리되기 때문이다.
불을 지핀 곳에서 부터 출발하여 상하좌우를 살핀 후, 원하는 픽셀값을 가진 곳으로 확장해나간다
# return
# cnt : 객체 총 갯수 - 1(배경 제외)
# labels : 각 객체 번호
# stats : x, y, w ,h
# centroids : 각 객체 중심 좌표
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(src_bin)
for s in range(1, cnt):
(x, y, w, h, area) = stats[s]
if area < 30:
continue
num += 1
connectedComponentsWithStats()
cv2.connectedComponentsWithStats(image, labels=None, stats=None, centroids=None, connectivity=None, ltype=None) -> retval, labels, stats, centroids
해당 메서드로 구한 cnt값을 통해 객체를 둘러보며 area가 일정 수준 이상이면 num값을 올린다
어떤 블로그가 그렇게 하길래,, 한 객체를 이루기 위한 최소한의 픽셀 개수를 지정해줘서 ‘일정 수준 이상의 정확도를 가져야 객체로 사용한다’라고 해석하면 될듯
import cv2
import numpy as np
img_color = cv2.imread('test_img.png')
img_hsv = cv2.cvtColor(img_color, cv2.COLOR_BGR2HSV) # 이미지 hsv 색상으로 변환
color_list = [[41, 48, 151],[101, 140, 55], [184, 152, 69]] # BGR 순서로 기입
color_name = ["red", "green", "blue"]
for i, color in enumerate(color_list):
pixel = np.uint8([[color]]) # 한픽셀로 구성된 이미지로 변환
hsv = cv2.cvtColor(pixel, cv2.COLOR_BGR2HSV)
hsv_h = int(hsv[0][0][0]) # hue값 -> 색깔 구별(0~360) + numpy.unit8값 Int로 변환
# print(hsv_h)
if hsv_h >= 10:
hsv_h_low = hsv_h - 5
else:
hsv_h_low = 0
if hsv_h <= 350:
hsv_h_high = hsv_h + 5
else:
hsv_h_high = 360
lower = (hsv_h_low, 30, 30) # hsv 이미지에서 바이너리 이미지로 생성 , 적당한 값 30
upper = (hsv_h_high, 255, 255)
img_mask = cv2.inRange(img_hsv, lower, upper)
# 라벨링 -> cnt 개수가 객체 개수
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(img_mask)
# print(cnt)
num = 0
for s in range(1, cnt):
(x, y, w, h, area) = stats[s]
if area < 30:
continue
num += 1
img_result = cv2.bitwise_and(img_color, img_color, mask = img_mask)
cv2.imshow(color_name[i] + ' mask', img_mask)
cv2.imshow(color_name[i] + ' result', img_result)
print(color_name[i], num)
cv2.waitKey(0)
cv2.destroyAllWindows()
이와 같이 잘 검출이 되는 것을 볼 수 있다😊


어쨌든 우여곡절 끝에 완성!