본문 바로가기
Image Processing/OpenCV

[OpenCV] 02. 화소처리 (히스토그램)

by AteN 2022. 11. 23.

 

영상 히스토그램 계산 - 1

import numpy as np, cv2
 

 

def calc_histo(image, channels, hsize, ranges ):
    ch = len(channels)
    shape = hsize if ch >1 else (hsize[0], 1)
    hist = np.zeros(shape, np.float32)         # 히스토그램 누적 행렬
    gap = np.divide(ranges[1::2], hsize)                # 계급 간격

    for row in image:  # 2차원 행렬 순회 방식
        for val in row:
            idx = np.divide(val[channels], gap).astype('uint')
            hist[tuple(idx)]+= 1
    return hist
 
image = cv2.imread("img/pixel.jpg") # 영상 읽기
if image is None: raise Exception("영상 파일 읽기 오류 발생")
 
ch, hsize, ranges =[0,1],  [8, 8], [0, 256,0, 256]                  # 히스토그램 간격수, 값 범위
hist1 = calc_histo(image, ch ,hsize, ranges)                  # 사용자 정의 히스토그램 계산
hist2 = cv2.calcHist([image], ch, None, hsize, ranges)  # OpenCV 함수

print("사용자 정의 함수: \n", hist1)                           # 행렬을 벡터로 변환하여 출력
print("사용자 정의 함수: \n", hist2)
cv2.imshow("image", image)
cv2.waitKey(0)
사용자 정의 함수: 
 [[2.0220e+03 4.6920e+03 5.3890e+03 9.1500e+02 6.9000e+01 0.0000e+00
  0.0000e+00 0.0000e+00]
 [1.2210e+03 2.2990e+03 1.9860e+03 3.3230e+03 5.9920e+03 8.9000e+02
  0.0000e+00 0.0000e+00]
 [5.2000e+01 3.3280e+03 1.4240e+03 1.0690e+03 1.0250e+03 7.8300e+02
  1.0000e+00 0.0000e+00]
 [0.0000e+00 4.1500e+02 2.1020e+03 1.6460e+03 9.1700e+02 4.1600e+02
  5.0000e+00 0.0000e+00]
 [0.0000e+00 0.0000e+00 2.9400e+02 1.1890e+03 2.6190e+03 1.0360e+03
  1.2200e+02 0.0000e+00]
 [0.0000e+00 0.0000e+00 0.0000e+00 4.6000e+01 8.2400e+02 8.7820e+03
  2.0220e+03 4.0000e+00]
 [0.0000e+00 0.0000e+00 0.0000e+00 0.0000e+00 4.0000e+00 4.1200e+02
  1.0451e+04 5.3200e+02]
 [0.0000e+00 0.0000e+00 0.0000e+00 0.0000e+00 0.0000e+00 2.0000e+00
  3.0300e+02 1.3770e+03]]
사용자 정의 함수: 
 [[2.0220e+03 4.6920e+03 5.3890e+03 9.1500e+02 6.9000e+01 0.0000e+00
  0.0000e+00 0.0000e+00]
 [1.2210e+03 2.2990e+03 1.9860e+03 3.3230e+03 5.9920e+03 8.9000e+02
  0.0000e+00 0.0000e+00]
 [5.2000e+01 3.3280e+03 1.4240e+03 1.0690e+03 1.0250e+03 7.8300e+02
  1.0000e+00 0.0000e+00]
 [0.0000e+00 4.1500e+02 2.1020e+03 1.6460e+03 9.1700e+02 4.1600e+02
  5.0000e+00 0.0000e+00]
 [0.0000e+00 0.0000e+00 2.9400e+02 1.1890e+03 2.6190e+03 1.0360e+03
  1.2200e+02 0.0000e+00]
 [0.0000e+00 0.0000e+00 0.0000e+00 4.6000e+01 8.2400e+02 8.7820e+03
  2.0220e+03 4.0000e+00]
 [0.0000e+00 0.0000e+00 0.0000e+00 0.0000e+00 4.0000e+00 4.1200e+02
  1.0451e+04 5.3200e+02]
 [0.0000e+00 0.0000e+00 0.0000e+00 0.0000e+00 0.0000e+00 2.0000e+00
  3.0300e+02 1.3770e+03]]

영상 히스토그램 계산 - 2

import numpy as np, cv2
def calc_histo(image, hsize, ranges=[0, 256]):  # 행렬 원소의 1차원 히스토그램 계산
    hist = np.zeros((hsize, 1), np.float32)  # 히스토그램 누적 행렬
    gap = ranges[1] / hsize  # 계급 간격

    for i in range(image.shape[0]):  # 2차원 행렬 순회 방식
        for j in range(image.shape[1]):
            idx = int(image.item(i,j) / gap)
            hist[idx] += 1
    return hist
 
image = cv2.imread("img/pixel.jpg", cv2.IMREAD_GRAYSCALE) # 영상 읽기
if image is None: raise Exception("영상 파일 읽기 오류 발생")
 
hsize, ranges = [32], [0, 256]                  # 히스토그램 간격수, 값 범위
gap = ranges[1]/hsize[0]
ranges_gap  = np.arange(0, ranges[1]+1, gap)
hist1 = calc_histo(image, hsize[0], ranges)  # User 함수
hist2 = cv2.calcHist([image], [0], None, hsize, ranges)  # OpenCV 함수
hist3, bins = np.histogram(image, ranges_gap )
 
print("User 함수: \n", hist1.flatten())                # 행렬을 벡터로 변환하여 출력
print("OpenCV 함수: \n", hist2.flatten())                # 행렬을 벡터로 변환하여 출력
print("numpy 함수: \n", hist3)                           # 행렬을 벡터로 변환하여 출력
User 함수: 
 [  97.  247.  563. 1001. 1401. 1575. 1724. 1951. 2853. 3939. 3250. 2549.
 2467. 2507. 2402. 2418. 2727. 3203. 3410. 3161. 2985. 2590. 3384. 4312.
 4764. 3489. 2802. 2238. 1127.  628.  199.   37.]
OpenCV 함수: 
 [  97.  247.  563. 1001. 1401. 1575. 1724. 1951. 2853. 3939. 3250. 2549.
 2467. 2507. 2402. 2418. 2727. 3203. 3410. 3161. 2985. 2590. 3384. 4312.
 4764. 3489. 2802. 2238. 1127.  628.  199.   37.]
numpy 함수: 
 [  97  247  563 1001 1401 1575 1724 1951 2853 3939 3250 2549 2467 2507
 2402 2418 2727 3203 3410 3161 2985 2590 3384 4312 4764 3489 2802 2238
 1127  628  199   37]

색상 히스토그램 그리기

import numpy as np, cv2
 
def make_palette(rows):
    # 리스트 생성 방식
    hue = [round(i * 180 / rows) for i in range(rows)]  # hue 값 리스트 계산
    hsv = [[(h, 255, 255)] for h in hue]                # (hue, 255,255) 화소값 계산
    hsv = np.array(hsv, np.uint8)                       # numpy 행렬의 uint8형 변환
    # # 반복문 방식
    # hsv = np.full((rows, 1, 3), (255,255,255), np.uint8)
    # for i in range(0, rows):                                # 행수만큼 반복
    #     hue = round(i / rows * 180 )                        # 색상 계산
    #     hsv[i] = (hue, 255, 255)                            # HSV 컬러 지정

    return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)         # HSV 컬러 -> BGR 컬러
 
def draw_histo_hue(hist, shape=(200, 256,3)):
    hsv_palate = make_palette(hist.shape[0])                      # 색상 팔레트 생성
    hist_img = np.full(shape, 255, np.uint8)
    cv2.normalize(hist, hist, 0, shape[0], cv2.NORM_MINMAX)    # 정규화

    gap = hist_img.shape[1] / hist.shape[0]  # 한 계급 크기
    for i, h in enumerate(hist):
        x, w = int(round(i * gap)), int(round(gap))
        color = tuple(map(int, hsv_palate[i][0]))                    # 정수형 튜플로 변환
        cv2.rectangle(hist_img, (x,0,w, int(h) ), color , cv2.FILLED) # 팔레트 색으로 그리기

    return cv2.flip(hist_img, 0)
 
image = cv2.imread("img/hue_hist.jpg", cv2.IMREAD_COLOR)  # 영상 읽기
if image is None: raise Exception("영상 파일 읽기 오류")

hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)   # BGR 컬러 -> HSV 컬러     
hue_hist = cv2.calcHist( [hsv_img], [0], None, [18], [0,180])       # Hue 채널 히스토그램 계산
hue_hist_img = draw_histo_hue(hue_hist, (200, 360, 3)) # 히스토그램 그래프

cv2.imshow("image", image)
cv2.imshow("hue_hist_img", hue_hist_img)
cv2.waitKey(0)

히스토그램 스트래칭

import numpy as np, cv2
 
def make_palette(rows):
    # 리스트 생성 방식
    hue = [round(i * 180 / rows) for i in range(rows)]  # hue 값 리스트 계산
    hsv = [[(h, 255, 255)] for h in hue]                # (hue, 255,255) 화소값 계산
    hsv = np.array(hsv, np.uint8)                       # numpy 행렬의 uint8형 변환
    # # 반복문 방식
    # hsv = np.full((rows, 1, 3), (255,255,255), np.uint8)
    # for i in range(0, rows):                                # 행수만큼 반복
    #     hue = round(i / rows * 180 )                        # 색상 계산
    #     hsv[i] = (hue, 255, 255)                            # HSV 컬러 지정

    return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)         # HSV 컬러 -> BGR 컬러
 
def draw_histo_hue(hist, shape=(200, 256,3)):
    hsv_palate = make_palette(hist.shape[0])                      # 색상 팔레트 생성
    hist_img = np.full(shape, 255, np.uint8)
    cv2.normalize(hist, hist, 0, shape[0], cv2.NORM_MINMAX)    # 정규화

    gap = hist_img.shape[1] / hist.shape[0]  # 한 계급 크기
    for i, h in enumerate(hist):
        x, w = int(round(i * gap)), int(round(gap))
        color = tuple(map(int, hsv_palate[i][0]))                    # 정수형 튜플로 변환
        cv2.rectangle(hist_img, (x,0,w, int(h) ), color , cv2.FILLED) # 팔레트 색으로 그리기

    return cv2.flip(hist_img, 0)
 
def search_value_idx(hist, bias = 0):
    for i in range(hist.shape[0]):
        idx = np.abs(bias - i)                     # 검색 위치 (처음 또는 마지막)
        if hist[idx] > 0:  return idx                             # 위치 반환
    return -1                                      # 대상 없으면 반환
 
image = cv2.imread("img/dst.jpg", cv2.IMREAD_GRAYSCALE)   # 영상읽기
if image is None: raise Exception("영상 파일 읽기 오류")
 
bsize, ranges = [64], [0,256]                        # 계급 개수 및 화소 범위
hist = cv2.calcHist([image], [0], None, bsize, ranges)

bin_width  = ranges[1]/bsize[0]                      # 계급 너비
high = search_value_idx(hist, bsize[0] - 1) * bin_width
low  = search_value_idx(hist, 0) * bin_width

idx = np.arange(0, 256)
idx = (idx - low) * 255/(high-low)	# 수식 적용하여 인덱스 생성
idx[0:int(low)] = 0
idx[int(high+1):] = 255
 
dst = cv2.LUT(image, idx.astype('uint8'))
## 룩업 테이블 사용하지 않고 직접 구현
# dst = np.zeros(image.shape, dtype=image.dtype)
# for i in range(dst.shape[0]):
#     for j in range(dst.shape[1]):
#         dst[i,j] = idx[image[i,j]]
 
hist_dst = cv2.calcHist([dst], [0], None, bsize, ranges)  # 결과 영상 히스토그램 재계산
hist_img = draw_histo_hue(hist, (200,360))          # 원본 영상 히스토그램 그리기
hist_dst_img = draw_histo_hue(hist_dst,(200,360))  # 결과 영상 히스토그램 그리기
 
print("high_value = ", high)
print("low_value = " , low)
cv2.imshow("image", image)        
cv2.imshow("hist_img", hist_img)
cv2.imshow("dst", dst)             
cv2.imshow("hist_dst_img", hist_dst_img)
cv2.waitKey(0)
high_value =  76.0
low_value =  12.0

 

히스토그램 평활화

import numpy as np, cv2
 
def make_palette(rows):
    # 리스트 생성 방식
    hue = [round(i * 180 / rows) for i in range(rows)]  # hue 값 리스트 계산
    hsv = [[(h, 255, 255)] for h in hue]                # (hue, 255,255) 화소값 계산
    hsv = np.array(hsv, np.uint8)                       # numpy 행렬의 uint8형 변환
    
    return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)         # HSV 컬러 -> BGR 컬러

def draw_histo_hue(hist, shape=(200, 256,3)):
    hsv_palate = make_palette(hist.shape[0])                      # 색상 팔레트 생성
    hist_img = np.full(shape, 255, np.uint8)
    cv2.normalize(hist, hist, 0, shape[0], cv2.NORM_MINMAX)    # 정규화

    gap = hist_img.shape[1] / hist.shape[0]  # 한 계급 크기
    for i, h in enumerate(hist):
        x, w = int(round(i * gap)), int(round(gap))
        color = tuple(map(int, hsv_palate[i][0]))                    # 정수형 튜플로 변환
        cv2.rectangle(hist_img, (x,0,w, int(h) ), color , cv2.FILLED) # 팔레트 색으로 그리기

    return cv2.flip(hist_img, 0)
 
image = cv2.imread("img/equalize.jpg", cv2.IMREAD_GRAYSCALE) # 영상 읽기
if image is None: raise Exception("영상 파일 읽기 오류")
 
bins, ranges = [256], [0, 256]
hist = cv2.calcHist([image], [0], None, bins, ranges)    # 히스토그램 계산
 
# 히스토그램 누적합 계산
accum_hist = np.zeros(hist.shape[:2], np.float32)
accum_hist[0] = hist[0]
for i in range(1, hist.shape[0]):
    accum_hist[i] = accum_hist[i - 1] + hist[i]

accum_hist = (accum_hist / sum(hist)) * 255                 # 누적합의 정규화
dst1 = [[accum_hist[val] for val in row] for row in image] # 화소값 할당
dst1 = np.array(dst1, np.uint8)

# #numpy 함수 및 룩업 테이블 사용
# accum_hist = np.cumsum(hist)                      # 누적합 계산
# cv2.normalize(accum_hist, accum_hist, 0, 255, cv2.NORM_MINMAX)  # 정규화
# dst1 = cv2.LUT(image, accum_hist.astype("uint8"))  #룩업 테이블로 화소값할당
 
dst2 = cv2.equalizeHist(image)                # OpenCV 히스토그램 평활화
hist1 = cv2.calcHist([dst1], [0], None, bins, ranges)   # 히스토그램 계산
hist2 = cv2.calcHist([dst2], [0], None, bins, ranges)   # 히스토그램 계산
hist_img = draw_histo_hue(hist)
hist_img1 = draw_histo_hue(hist1)
hist_img2 = draw_histo_hue(hist2)
 
cv2.imshow("image", image);             cv2.imshow("hist_img", hist_img)
cv2.imshow("dst1_User", dst1);          cv2.imshow("User_hist", hist_img1)
cv2.imshow("dst2_OpenCV", dst2);        cv2.imshow("OpenCV_hist", hist_img2)
cv2.waitKey(0)
 
 

댓글