Python opencv를 사용한 이미지 스테 가노 그래피, 임베디드 이미지를 재구성하는 것은 매우 시끄 럽습니다.

Nov 23 2020

opencv 4.4.0.44와 함께 python 3.6.8을 사용하여 다른 이미지 (Image Steganography) 안에 이미지를 숨기고 있습니다. 나는 Windows 10 컴퓨터에 있습니다.

내가 사용하는 알고리즘은 다음과 같습니다. 마지막 두 개의 최하위 비트에서 0으로 마스크를 정의했습니다. 그런 다음이 마스크와 "bitwise and"를 사용하여 기본 이미지의 모든 픽셀의 마지막 2 비트를 0으로 만듭니다. 두 개의 이미지가 있는데 하나는 두 번째 이미지 (숨겨진 이미지)를 수용하는 기본 이미지입니다. 숨겨진 이미지의 크기가 기본 이미지의 1/4 이하인지 확인했습니다. 또한 하나의 채널 만 처리하기 위해 두 이미지를 그레이 스케일로 변경했습니다.

이미지를 삽입하고 추출하는 데 성공했지만 추출 된 이미지가 매우 시끄 럽습니다. 이미지의 내용이 변경되지 않았기 때문에 놀랍습니다.

import numpy as np
import cv2 as cv
import os


def mask_n_bit_of_image(img_array, mask):
    """
    Applies a mask bitwise on an image to make the n lowest bit zero
    :param img: input image
    :param mask: mask to make the n lowest significant bits zero. Maske sample: int('11111110', 2)
    :return: masked image
    """
    for i in range(img_array.shape[0]):
        for j in range(img_array.shape[1]):
            new_value = img_array[i, j] & mask
            img_array[i, j] = new_value

    return img_array


def draw_img_side_by_side(img1, img2, caption):
    h_im = cv.hconcat([img_cp, img])
    cv.imshow(caption, h_im)


def image_binary_content(input_array):
    """
   Calculates the binary content of an input numpy array of type int.
   :param input_array: input numpy array which is a gray_scale image
   :return: binary content of the image in str format
   """

    img_cp = []
    for x in range(0, input_array.shape[0]):
        for y in range(0, input_array.shape[1]):
            img_cp.append(bin(int(input_array[x, y]))[2:])

    # reshaping the list to match the image size and order
    new_img_arr = np.reshape(img_cp, (input_array.shape[0], input_array.shape[1]))
    return new_img_arr


def padding_zeros_to_make_8bits_images(input_image):
    """
    Checks the output of image_binary_content(img) to add zeros to the left hand side of every byte.
    It makes sure every pixel is represented by 8 bytes
    :param input_image: input image or numpy 2D array
    :return: numpy 2D array of 8-bits pixels in binary format
    """
    for i in range(input_image.shape[0]):
        for j in range(input_image.shape[1]):
            if len(input_image[i, j]) < 8:
                # print(input_image[i, j])
                zeros_to_pad = 8 - len(input_image[i, j])
                # print('Zeros to pad is {}'.format(zeros_to_pad))
                elm = input_image[i, j]
                for b in range(zeros_to_pad):
                    elm = '0' + elm
                # print('New value is {} '.format(elm))
                input_image[i, j] = elm
                # print('double check {} '.format(input_image[i, j]))

    return input_image



def write_img(path, name, img):
    """

    :param path:
    :param name:
    :param img:
    :return:
    """
    name = os.path.join(path, name)
    cv.imwrite(name, img)



img_path = 's2.bmp'

img = cv.imread(img_path, 0)
cv.imshow('original image', img)
img_cp = img.copy()
path_dest = r'color'
print('Original image shape {}'.format(img.shape))


mask = int('11111100', 2)
print('mask = {}'.format(mask))
img_n2 = mask_n_bit_of_image(img, mask)
# draw_img_side_by_side(img_cp, img_n2, 'Modified image n=2')

img_to_hide_path = r'2.jpeg'
img_to_hide = cv.imread(img_to_hide_path, 0)
img_to_hide = cv.resize(img_to_hide, (220, 220), interpolation=cv.INTER_NEAREST)


# for images which are bigger than 1/4 of the base image, resize them:
# img_to_hide = cv.resize(img_to_hide, (500, 420), interpolation=cv.INTER_NEAREST)


cv.imshow('hidden image', img_to_hide)

h_flat = img_to_hide.flatten()
print('LENGTH OF FLAT HIDDEN IMAGE IS {}'.format(len(h_flat)))
# for i in range(len(h_flat)):
#     print(bin(h_flat[i]))

img_hidden_bin = image_binary_content(img_to_hide)
print('binary of hidden image type: {}'.format(type(img_hidden_bin)))
# reformat evey byte of the hidden image to have 8 bits pixels
img_hidden_bin = padding_zeros_to_make_8bits_images(img_hidden_bin)
print(img_hidden_bin.shape)

all_pixels_hidden_img = img_hidden_bin.flatten()

print('Length of flattened hidden image to embed is {}'.format(len(all_pixels_hidden_img)))
# for i in range(0, 48400):
#     print(all_pixels_hidden_img[i])

num_pixels_to_modify = len(all_pixels_hidden_img) * 4
print('Number of pixels to modify in base image is {}'.format(num_pixels_to_modify))

# parts = [your_string[i:i+n] for i in range(0, len(your_string), n)]
two_bit_message_list = []
for row in all_pixels_hidden_img:
    for i in range(0, 8, 2):
        two_bit_message_list.append(row[i: i+2])
print('TWO BITS MESSAGE LIST LENGTH {}'.format(len(two_bit_message_list)))

# reconstruct the hidden msg to make sure for the next part
# c_h_img = []
# for i in range(0, len(two_bit_message_list), 4):
#     const_byte = two_bit_message_list[i] + two_bit_message_list[i+1] + two_bit_message_list[i+2] + two_bit_message_list[i+3]
#     c_h_img.append(const_byte)
#
# print('constructed image length c_h_img {}'.format(len(c_h_img)))
# for i in range(48400):
#     print(c_h_img[i])
# c_h_img = np.array(c_h_img, np.float64)
# c_h_img = c_h_img.reshape(img_to_hide.shape)
# cv.imshow('C_H_IMG', c_h_img.astype('uint16'))

# insert 6 zeros to left hand side of every entry to two_bit_message_list
new_hidden_image = []
for row in two_bit_message_list:
    row = '000000' + row
    new_hidden_image.append(row)

base_img_flat = img_cp.flatten()
num_bytes_to_fetch = len(two_bit_message_list)
img_base_flat = img_n2.flatten()
print('LENGTH OF TWO BIT MSG LIST {}'.format(num_bytes_to_fetch))

print('Bit length of the bytes to fetch is {} '.format(bin(num_bytes_to_fetch)))
# scanned from new constructed image
print(bin(num_bytes_to_fetch)[2:])
print(len( bin(num_bytes_to_fetch)[2:] ))



print('Start of loop to embed the hidden image in base image')
for i in range(num_bytes_to_fetch):
    # First 12 bytes are reserved for the hidden image size to be embedded
    new_value = img_base_flat[i] | int( new_hidden_image[i], 2)
    img_base_flat[i] = new_value

image_with_hidden_img = img_base_flat.reshape(img_n2.shape)
cv.imshow('Image with hidden image embedded', image_with_hidden_img)



# Reading embedded image from constructed image
constructed_image_with_message_embedded = image_binary_content(image_with_hidden_img)
constructed_image_with_message_embedded_zero_padded = padding_zeros_to_make_8bits_images(constructed_image_with_message_embedded)
flat_constructed_image_with_message_embedded = constructed_image_with_message_embedded_zero_padded.flatten()

embedded_img_list = []
for i in range(num_bytes_to_fetch):
    embedded_img_list.append(flat_constructed_image_with_message_embedded[i][-2:])

# [print(rec) for rec in embedded_img_list]
print('EMBEDDED IMAGE LIST LENGTH {}'.format(len(embedded_img_list)))

const_byte_list = []
for i in range(0, len(embedded_img_list), 4):
    const_byte = embedded_img_list[i] + embedded_img_list[i+1] + embedded_img_list[i+2] + embedded_img_list[i+3]
    const_byte_list.append(const_byte)

# [print(rec) for rec in const_byte_list]
print('LENGTH OF CONSTRUCT BYTES IS {}'.format(len(const_byte_list)))

const_byte_list_tmp = np.array(const_byte_list, np.float64)
const_byte_2D_array = const_byte_list_tmp.reshape(img_to_hide.shape)  #((220,220))
const_byte_2D_array = const_byte_2D_array.astype('uint16')
cv.imshow('Constructed image from base', const_byte_2D_array)
cv.imwrite('reconstructed_image.jpeg', const_byte_2D_array)

cv.waitKey(0)
cv.destroyAllWindows()

s2.bmp

2.jpeg

jpg, png 및 bmp를 포함한 다른 이미지 확장자를 시도했습니다. 그들 모두에서 재구성 된 이미지가 왜곡되었습니다. 아래 이미지에서 재구성 된 이미지가 얼마나 시끄러운 지 확인할 수 있습니다. 자연의 이미지는 lsb에 숨겨진 이미지를 포함하는 기본 이미지, 위쪽 눈은 숨겨진 이미지, 아래쪽 눈은 재구성 된 숨겨진 이미지입니다.

내 생각 : 다른 이미지 유형에 대해이 문제가 발생했고 내 코드에서 볼 수 있듯이 주석 처리 한 블록이 있습니다 (github의 134 행에서 시작), 문제의 원인은 "image_binary_content"메소드에 있어야한다고 생각합니다. ". 134 행에서 블록의 주석 처리를 제거하면 기본 이미지에 포함하기 전에도 정확히 동일한 재구성 된 이미지를 얻을 수 있습니다. 비교를했는데 숨겨진 이미지의 내용이 올바르게 검색되었지만 포함되기 전에 일부 데이터가 손실되었습니다.

내 코드는 다음과 같으며이 github_link 에서 이름으로 사용할 수 있습니다 hw3_task1_embed_image_in_base_image.py. 기본 및 숨겨진 이미지도 거기에서 사용할 수 있습니다. 또한 cv.imwrite의 "reconstructed_image.png"(스크린 샷), "reconstructed_image.jpeg"라는 이름으로 기본 이미지에서 처리 한 후 복원 된 숨겨진 이미지를 찾을 수 있습니다. 흥미롭게도 imwrite로 저장 한 내용은 코드를 실행하여 표시되는 것보다 품질이 훨씬 낮습니다.

답변

Reti43 Nov 24 2020 at 15:21

의 내용은 이진 문자열 형식의 비밀 이미지 픽셀 인의 내용과 const_byte_list동일합니다 all_pixels_hidden_img. 귀하의 오류는 곧

const_byte_list_tmp = np.array(const_byte_list, np.float64)

이진 문자열 '11001000'을 값 200으로 변환한다고 생각할 수 있지만 실제로는 부동 숫자 11001000.0으로 바뀝니다. 대신 다음을 원합니다.

const_byte_list_tmp = np.array([int(pixel, 2) for pixel in const_byte_list], dtype=np.uint8)

배열이 어떻게 uint16이 아닌 uint8 유형으로 설정되었는지 확인하십시오.


모든 것을 말했지만, 당신은 잘못된 방향으로 가고 있습니다. 어딘가에서 BITAND 연산을 사용 했으므로 비트 연산에 대해 알고 있습니다. 그리고 이것이 정수에 작용하는 이러한 연산으로 스테 가노 그래피가 수행되어야하는 방법입니다. 깊은 0b11111111, 255 및 0xff는 모두 동일한 숫자를 나타냅니다. 정수를 이진 문자열로 변환하고 잘라내어 스티치 한 다음 다시 정수로 바꿀 필요가 없습니다.

Numpy는 벡터화 된 연산도 지원하므로 array & mask명시적인 루프가 필요없는 모든 요소에이를 적용합니다. 대체로 코드는 다음과 같을 수 있습니다.

MASK_ZERO = 0b11111100
MASK_EXTRACT = 0b00000011

cover_path = 's2.bmp'
secret_path = '2.jpeg'

# EMBED
cover = cv.imread(cover_path, 0)
secret = cv.imread(secret_path, 0)
secret = cv.resize(secret, (220, 220), interpolation=cv.INTER_NEAREST)

secret_bits = []
for pixel in secret.flatten():
    secret_bits.extend(((pixel >> 6) & MASK_EXTRACT,
                        (pixel >> 4) & MASK_EXTRACT,
                        (pixel >> 2) & MASK_EXTRACT,
                        pixel & MASK_EXTRACT))
secret_bits = np.array(secret_bits)
secret_length = len(secret_bits)

stego = cover.copy().flatten()
stego[:secret_length] = (stego[:secret_length] & MASK_ZERO) | secret_bits


# EXTRACT
extracted_bits = stego[:secret_length] & MASK_EXTRACT
extracted = []
for i in range(0, secret_length, 4):
    extracted.append((extracted_bits[i] << 6) |
                     (extracted_bits[i+1] << 4) |
                     (extracted_bits[i+2] << 2) |
                     extracted_bits[i+3])
extracted = np.array(extracted, dtype=np.uint8)
extracted = extracted.reshape(secret.shape)

print('Is extracted secret correct: {}'.format(np.all(secret == extracted)))