pythonopencvimage-processingimage-segmentationsteganography

Looping over the threshold area does not change pixel values


I am currently working on a design of steganography system that detects a certain area in an image using several techniques (K-means, Canny edge detection) using Python and OpenCV library. I am facing a huge problem updating the image pixel values to contain my secret data in the least significant bit.

I started with finding the threshold after several calculations.

thresh = cv.adaptiveThreshold(imgray,100,cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY_INV,11,2)

And to test the area I did the following:

Testim=np.copy(originalImage) 
Testim[thresh==0]=[0,255,0]
plt.imshow(Testim)
plt.show()

and it showed this image which indicates the area I want to loop over:

Image showing after the threshold Isolation in green

After that I went through a loop that I will show you a snippet of it, it iterates over the RGB values and changes each least significant bit with a bit from the secret data. I would like to note that the original image shape is (1024,1024,3) and the shape of the image[thresh==0] is (863843, 3) :

 for i in range(image[thresh==0].shape[0]): 
      # convert RGB values to binary format
      r, g, b = to_bin(image[thresh==0][i])
              # modify the least significant bit only if there is still data to store
      if data_index < data_len:
              # least significant red pixel bit
        image[thresh==0][i][0] =int (r[:-1] + binary_secret_data[data_index], 2)
        data_index += 1 
      if data_index < data_len:
              # least significant green pixel bit
        image[thresh==0][i][1] = int (g[:-1] + binary_secret_data[data_index], 2)
        data_index += 1
      if data_index < data_len:
              # least significant blue pixel bit
        image[thresh==0][i][2] = int (b[:-1] + binary_secret_data[data_index], 2)
        data_index += 1
                # if data is encoded, just break out of the loop
      if data_index >= data_len:
        plt.imshow(image)
        break
    return image,thresh

The problem is that the values of RGB are not changing in and out of the loop at all, I added some print statements, and it keeps showing zero, I also tried to assign 1 explicitly, and it didn't work either.

I want to note that this is part of the encode function only

I would appreciate your help with this


Solution

  • Your issue is that advanced indexing returns copies of arrays, so by doing image[thresh==0][i][j] all the time you modify freshly created copies which are then discarded.

    Let's start with some fake data

    import cv2
    import numpy as np
    
    np.random.seed(0)
    
    original = np.random.randint(0, 255, (1024, 1024, 3)).astype(np.uint8)
    gray = cv2.cvtColor(original, cv2.COLOR_RGB2GRAY)
    thresh = cv2.adaptiveThreshold(gray, 100, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
    copy = np.copy(original)
    copy[thresh==0] = [0, 255, 0]
    

    And now see what happens

    >>> copy[thresh==0][0]
    array([  0, 255,   0], dtype=uint8)
    >>> copy[thresh==0][0] = [1, 2, 3]
    >>> copy[thresh==0][0]
    array([  0, 255,   0], dtype=uint8)
    

    In order for the results to stick, you should use numpy.where to get the individual indices where your threshold is 0.

    idx = np.where(thresh==0)
    for row, col in zip(*idx):
        r, g, b = to_bin(copy[row,col])
        copy[row,col,0] = modified_red_pixel
        # etc
    

    I would personally suggest the following so you avoid python loop and constant checks to see if you still have data to embed.

    binary_secret_data = '01010000101010010101010001001100'
    binary_secret_data = np.array(list(map(int, binary_secret_data)))
    secret_len = len(binary_secret_data)
    
    # The result is a tuple of (array of all row coords, array of all column coords)
    idx = np.where(thresh==0)
    # Make 3 copies of each pixel coordinate and add 0, 1, 2 repeatedly for the RGB of each pixel
    idx = (np.repeat(idx[0], 3), np.repeat(idx[1], 3), np.array([0, 1, 2] * len(idx[0])))
    # Only keep as many pixels as data you have to embed
    idx = (idx[0][:secret_len], idx[1][:secret_len], idx[2][:secret_len])
    
    # Boom, power of vectorisation
    copy[idx] = copy[idx] & 0xfe | binary_secret_data
    

    And now you can see the first 32 pixels have been successfully modified

    >>> copy[thresh==0][:15]
    array([[  0, 255,   0],
           [  1, 254,   0],
           [  0, 254,   1],
           [  0, 255,   0],
           [  1, 254,   0],
           [  1, 254,   1],
           [  0, 255,   0],
           [  1, 254,   0],
           [  0, 255,   0],
           [  0, 255,   1],
           [  0, 254,   0],
           [  0, 255,   0],
           [  0, 255,   0],
           [  0, 255,   0],
           [  0, 255,   0]], dtype=uint8)