brunch

파이썬 이미지 프로세싱 (3)

비트 평면 분할

by 서준수

파이썬 이미지 프로세싱 (3)


그레이스케일 픽셀 값은 1바이트, 즉 8비트이다. 그래서 그레이스케일 값의 범위가 0~255이다.


cal.png


비트 평면이란 각 비트의 값이 0인지 1인지 확인하여 새로운 이미지를 만드는 것이다. 새롭게 만들어질 이미지의 그레이스케일 값은 해당 비트의 값이 0이면 0으로 하고 1이면 255로 한다.


newbit.PNG


위 그림을 보면 원본 이미지의 첫 번째 픽셀의 그레이스케일 값은 128이다. 128은 2진수로 10000000이다. 이때 각 비트가 0인지 1인지 확인한다. 최상위 비트는 1이기 때문에 최상위 비트로 만들 새 이미지의 첫 번째 픽셀의 값은 255가 된다. 원본 이미지의 다음 픽셀의 값은 125이고 2진수로 01111101이다. 이때 최상위 비트는 0이다. 따라서 최상위 비트로 만들 새 이미지의 첫 번째 픽셀의 값은 0이 된다.


같은 방법으로 최상위 비트의 다음 비트에 대해서도 동일한 작업을 한다. 첫 번째 픽셀의 그레이스케일 값 10000000에서 차상위 비트는 0이다. 따라서 새롭게 만들어질 이미지의 첫 번째 픽셀의 값은 0이다. 두 번째 픽셀은 01111101이고 이때 차상위 비트는 1이다. 따라서 두 번째 픽셀은 255가 된다.


이러한 작업을 각 픽셀의 각 비트에 대해서 모두 실행하면 8개의 새로운 이미지가 만들어진다.


bitplaneslicing_result.PNG 비트 평면 분할


비트 평면 분할의 결과를 보면 최상위 비트로 분할된 이미지(Bit8)는 상대적으로 가장 윤곽이 뚜렷하다. 그래서 최상위 비트를 보통 가장 중요한 비트라고 한다. 반대로 최하위 비트는 형체를 거의 알 수 없다. 윤곽이 거의 없고 잡음 형태로 나타난다. 따라서 상대적으로 제일 덜 중요한 비트이다.


이러한 특성을 활용해서 최하위 비트에 실제 사진에서는 보이지 않는 특정 정보를 넣을 수 있다.

여기에서 camera7.bmp를 다운로드하여 파일을 열어보자. 아무리 눈을 크게 뜨고 봐도 7이란 문자가 보이지 않는다. 하지만 비트 평면 분할을 해보면 최하위 비트에 7이라는 문자 이미지가 들어가 있는 것을 확인할 수 있다. 일종의 워터마크인 셈이다.


bitplaneslicing_result_7.PNG 워터마크가 들어있는 이미지의 비트 평면 분할


여기서 한 가지 궁금증이 생겼다. 책(Visual C++ 영상 처리 프로그래밍)에는 없는 내용인데 그렇다면 워터마크는 어떻게 넣을 수 있을까? 그래서 다음 포스팅에서는 워터마크를 넣는 방법에 대해서 다뤄볼 것이다.


코드를 간단히 살펴보면 draw_image() 함수에 ax = fig.add_subplot(3, 5, 6)가 있다.

add_subplot(3, 5, 6)의 각 인자 값의 의미는 첫 번째는 행, 두 번째는 열, 세 번째는 행렬 내 인덱스이다. 즉 아래와 같이 3x5 행렬로 이미지를 배치할 수 있다는 뜻이다.


index.PNG


이때 원본 이미지는 인덱스를 6으로 지정했으니 위 그림의 6에 해당하는 위치에 나오는 것이다.


그리고 각 비트를 나눠서 0인지 1인지 확인하는 부분은 if (img[i, j] & (1 << 7))이다.

(1 << 7)의 의미는 아래 그림처럼 1에 해당하는 8비트를 좌측으로 7칸 이동시킨 것이다. 간단한 비트 연산이다. 그 후에 and 연산을 통해서 원본의 그레이스케일 값과 비교하면 최상위 비트가 1일 때 참이 된다. 그때 255를 새 이미지 그레이스케일 값으로 설정하고 1이 아니면(0이면) 0으로 설정하면 된다.


code1.PNG



import matplotlib.pyplot as plt

import cv2



def draw_image(original_img, out_list, title, sub_title1):

fig = plt.figure()

fig.suptitle(title)


ax = fig.add_subplot(3, 5, 6)

ax.imshow(original_img, cmap=plt.cm.gray)

ax.set_title(sub_title1)


ax = fig.add_subplot(3, 5, 2)

ax.imshow(out_list[0], cmap=plt.cm.gray)

ax.set_title("Bit8")


ax = fig.add_subplot(3, 5, 3)

ax.imshow(out_list[1], cmap=plt.cm.gray)

ax.set_title("Bit7")


ax = fig.add_subplot(3, 5, 4)

ax.imshow(out_list[2], cmap=plt.cm.gray)

ax.set_title("Bit6")


ax = fig.add_subplot(3, 5, 5)

ax.imshow(out_list[3], cmap=plt.cm.gray)

ax.set_title("Bit5")


ax = fig.add_subplot(3, 5, 12)

ax.imshow(out_list[4], cmap=plt.cm.gray)

ax.set_title("Bit4")


ax = fig.add_subplot(3, 5, 13)

ax.imshow(out_list[5], cmap=plt.cm.gray)

ax.set_title("Bit3")


ax = fig.add_subplot(3, 5, 14)

ax.imshow(out_list[6], cmap=plt.cm.gray)

ax.set_title("Bit2")


ax = fig.add_subplot(3, 5, 15)

ax.imshow(out_list[7], cmap=plt.cm.gray)

ax.set_title("Bit1")


plt.show()



def do_bit_plane_slicing():

file_path = "img\camera.bmp"

# file_path = "img\camera7.bmp"


img = cv2.imread(file_path, cv2.IMREAD_GRAYSCALE)


out_img1 = img.copy()

out_img2 = img.copy()

out_img3 = img.copy()

out_img4 = img.copy()

out_img5 = img.copy()

out_img6 = img.copy()

out_img7 = img.copy()

out_img8 = img.copy()


row, col = img.shape


for i in range(0, row):

for j in range(0, col):

out_img1[i, j] = 255 if (img[i, j] & (1 << 7)) else 0

out_img2[i, j] = 255 if (img[i, j] & (1 << 6)) else 0

out_img3[i, j] = 255 if (img[i, j] & (1 << 5)) else 0

out_img4[i, j] = 255 if (img[i, j] & (1 << 4)) else 0

out_img5[i, j] = 255 if (img[i, j] & (1 << 3)) else 0

out_img6[i, j] = 255 if (img[i, j] & (1 << 2)) else 0

out_img7[i, j] = 255 if (img[i, j] & (1 << 1)) else 0

out_img8[i, j] = 255 if (img[i, j] & (1 << 0)) else 0


out_list = [out_img1, out_img2, out_img3, out_img4, out_img5, out_img6, out_img7, out_img8]


cv2.imwrite("bit8_slicing.bmp", out_img1)

draw_image(img, out_list, "Bit Plane Slicing", "Original Image")



do_bit_plane_slicing()



ref.)

Visual C++ 영상 처리 프로그래밍 : https://thebook.io/006796/ch07/04/01_01/

이미지 출처 : https://github.com/gilbutITbook/006796/tree/master/images/ch07

keyword
매거진의 이전글파이썬 이미지 프로세싱 (2)