비트 평면 분할
그레이스케일 픽셀 값은 1바이트, 즉 8비트이다. 그래서 그레이스케일 값의 범위가 0~255이다.
비트 평면이란 각 비트의 값이 0인지 1인지 확인하여 새로운 이미지를 만드는 것이다. 새롭게 만들어질 이미지의 그레이스케일 값은 해당 비트의 값이 0이면 0으로 하고 1이면 255로 한다.
위 그림을 보면 원본 이미지의 첫 번째 픽셀의 그레이스케일 값은 128이다. 128은 2진수로 10000000이다. 이때 각 비트가 0인지 1인지 확인한다. 최상위 비트는 1이기 때문에 최상위 비트로 만들 새 이미지의 첫 번째 픽셀의 값은 255가 된다. 원본 이미지의 다음 픽셀의 값은 125이고 2진수로 01111101이다. 이때 최상위 비트는 0이다. 따라서 최상위 비트로 만들 새 이미지의 첫 번째 픽셀의 값은 0이 된다.
같은 방법으로 최상위 비트의 다음 비트에 대해서도 동일한 작업을 한다. 첫 번째 픽셀의 그레이스케일 값 10000000에서 차상위 비트는 0이다. 따라서 새롭게 만들어질 이미지의 첫 번째 픽셀의 값은 0이다. 두 번째 픽셀은 01111101이고 이때 차상위 비트는 1이다. 따라서 두 번째 픽셀은 255가 된다.
이러한 작업을 각 픽셀의 각 비트에 대해서 모두 실행하면 8개의 새로운 이미지가 만들어진다.
비트 평면 분할의 결과를 보면 최상위 비트로 분할된 이미지(Bit8)는 상대적으로 가장 윤곽이 뚜렷하다. 그래서 최상위 비트를 보통 가장 중요한 비트라고 한다. 반대로 최하위 비트는 형체를 거의 알 수 없다. 윤곽이 거의 없고 잡음 형태로 나타난다. 따라서 상대적으로 제일 덜 중요한 비트이다.
이러한 특성을 활용해서 최하위 비트에 실제 사진에서는 보이지 않는 특정 정보를 넣을 수 있다.
여기에서 camera7.bmp를 다운로드하여 파일을 열어보자. 아무리 눈을 크게 뜨고 봐도 7이란 문자가 보이지 않는다. 하지만 비트 평면 분할을 해보면 최하위 비트에 7이라는 문자 이미지가 들어가 있는 것을 확인할 수 있다. 일종의 워터마크인 셈이다.
여기서 한 가지 궁금증이 생겼다. 책(Visual C++ 영상 처리 프로그래밍)에는 없는 내용인데 그렇다면 워터마크는 어떻게 넣을 수 있을까? 그래서 다음 포스팅에서는 워터마크를 넣는 방법에 대해서 다뤄볼 것이다.
코드를 간단히 살펴보면 draw_image() 함수에 ax = fig.add_subplot(3, 5, 6)가 있다.
add_subplot(3, 5, 6)의 각 인자 값의 의미는 첫 번째는 행, 두 번째는 열, 세 번째는 행렬 내 인덱스이다. 즉 아래와 같이 3x5 행렬로 이미지를 배치할 수 있다는 뜻이다.
이때 원본 이미지는 인덱스를 6으로 지정했으니 위 그림의 6에 해당하는 위치에 나오는 것이다.
그리고 각 비트를 나눠서 0인지 1인지 확인하는 부분은 if (img[i, j] & (1 << 7))이다.
(1 << 7)의 의미는 아래 그림처럼 1에 해당하는 8비트를 좌측으로 7칸 이동시킨 것이다. 간단한 비트 연산이다. 그 후에 and 연산을 통해서 원본의 그레이스케일 값과 비교하면 최상위 비트가 1일 때 참이 된다. 그때 255를 새 이미지 그레이스케일 값으로 설정하고 1이 아니면(0이면) 0으로 설정하면 된다.
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