Oxaric’s Blog

A compendium of amazing things…

Posts Tagged ‘Matlab’

Hidden Image Watermarking

Posted by oxaric on December 9, 2008




I’ve written some code in Matlab to take an image of a forest, shown in figure 1, and an image of the X-Men character Wolverine, shown in figure 3, and hide the image of Wolverine inside of the image of the tree, figure 2. The code then retrieves the hidden image of Wolverine, shown in figure 4. I chose these two images at random from Google Image Search.


The main impetus for writing this code is for the purposes of watermarking. Watermarking is useful if you have an image or video you want to distribute professionally or personally and you want to be able to determine if someone else is using the same image you distributed. Hidden watermarking is nice when you want to be able to track your images while not visibly changing the original image. This code can also be used for Steganography purposes.


The code can easily be reused for any images and is heavily commented to be clear and easy to understand. It also gives the option to combine two images in any ratio so it can also be used for neat effects like blending two images, creating visible watermarks, or neat fade effects. The code also demonstrates how the hidden watermark can still be partially retrieved after resizing the combined image, figure 5, heavily compressing the combined image as a JPEG, figure 6, and after adding random noise to the combined image, figure 7. These tests show how hard it would be for someone to completely eradicate hidden watermarks from an image as simple editing of an image will not remove the watermark.






Figure 1 – Original forest image
Forest


Figure 2 – Forest image with hidden Wolverine watermark
Forest and Wolverine Combined


Figure 3 – Original Wolverine Image
Wolverine


Figure 4 – Wolverine watermark retrieved from the watermarked forest image
Restored Wolverine


Figure 5 – Wolverine watermark retrieved after the watermarked forest image was resized
Retrieved Wolverine After Resizing Combined


Figure 6 – Wolverine watermark retrieved after the watermarked forest image was saved as a heavily compressed .jpg
Retrieved Wolverine After Heavily Compressing Combined


Figure 7 – Wolverine watermark retrieved after the watermarked forest image had random noise added
Retreived Wolverine After Adding Random Noise to Combined






Currently the code is hard coded to load ‘tree_path.jpg’ and ‘wolverine.jpg’ so I have zipped those two images with the code. And possibly of note is that I am using Matlab 7.6.0.

Click to directly download stegocombine.zip




% file name 'stegocombine.m'

% Run through Matlab


% By: Louis Casillas, oxaric@gmail.com


% Input:

% tree_path.jpg (RGB)

% wolverine.jpg (RGB)

% Output:

% displays these two images combined and the restored wolverine 

% image pulled from the combined image

% How:

% resizes wolverine.jpg to the size of tree_path.jpg,

% adds the resized wolverine image to tree_path.jpg by weighted values

% specified by WEIGHTED_COMBINE_VALUE:

% combined_image = tree_path + (wolverine_resized * WEIGHTED_COMBINE_VALUE)

% then tries to restore the wolverine image by subtracting the original

% tree_path.jpg values from the combined_image values and dividing by

% WEIGHTED_COMBINE_VALUE:

% restored_wolverine_image = (tree_path - combined_image) / WEIGHTED_COMBINE_VALUE

% the percent used to multiply the wolverine.jpg color values by before

% adding them to tree_path.jpg

% 1.0 = 100%

WEIGHTED_COMBINE_VALUE = 0.01;

% false => only display combined image and restored image

% true => display all images

DISPLAY_INTERMEDIATE_IMAGES = false;

% set DISPLAY_TESTING_IMAGES to a number below to run those tests

% extra tests will show how the wolverine image is restored from the 

% combined image for these 3 cases:

% 0 => no extra tests

% 1 => if the combined image is resized

% 2 => if the combined image is stored as a heavily compressed .jpg

% 3 => if the combined image has added noise

% 4 => show all tests

DISPLAY_TESTING_IMAGES = 4;

% load the images

original_tree_path_image = imread( 'tree_path.jpg' );

original_wolverine_image = imread( 'wolverine.jpg' );

% grab the x and y resolution for tree_path.jpg

tree_path_x_size = size( original_tree_path_image, 2 );

tree_path_y_size = size( original_tree_path_image, 1 );

% grab the x and y resolution for wolverine.jpg

original_wolverine_x_size = size( original_wolverine_image, 2 );

original_wolverine_y_size = size( original_wolverine_image, 1 );

if DISPLAY_INTERMEDIATE_IMAGES


    % display the original tree_path.jpg

    figure, imshow( original_tree_path_image ); title( 'Original - tree_path.jpg' );

    % display the original wolverine.jpg

    figure, imshow( original_wolverine_image ); title( 'Original - wolverine.jpg' );

end

% resize wolverine.jpg to be the same same as tree_path.jpg

resized_wolverine_image = imresize( original_wolverine_image, [tree_path_y_size, tree_path_x_size] );

if DISPLAY_INTERMEDIATE_IMAGES

    

    % display the resized wolverine.jpg

    figure, imshow( resized_wolverine_image ); title( 'Resized Wolverine' );

end

% create a blank image the same size as tree_path.jpg

% this will store the new image that is a combination of tree_path.jpg and

% wolverine.jpg

combined_image = zeros( tree_path_y_size, tree_path_x_size, 3 );

% add tree_path.jpg and wolverine.jpg but only give the pixels in

% the resized wolverine.jpg a weighted value determined by

% WEIGHTED_COMBINE_VALUE

for x_value = 1:tree_path_x_size

    for y_value = 1:tree_path_y_size

        combined_image( y_value, x_value, 1 ) = original_tree_path_image( y_value, x_value, 1 ) + (resized_wolverine_image( y_value, x_value, 1 ) * WEIGHTED_COMBINE_VALUE);

        combined_image( y_value, x_value, 2 ) = original_tree_path_image( y_value, x_value, 2 ) + (resized_wolverine_image( y_value, x_value, 2 ) * WEIGHTED_COMBINE_VALUE);

        combined_image( y_value, x_value, 3 ) = original_tree_path_image( y_value, x_value, 3 ) + (resized_wolverine_image( y_value, x_value, 3 ) * WEIGHTED_COMBINE_VALUE);

    end

end

% force the combined image color values to be between 0 and 255

combined_image = uint8( combined_image );

% display the combined tree_path.jpg and wolverine.jpg

figure, imshow( combined_image ); title( 'Weight Combined Image' );

% create a blank image the same size as tree_path.jpg

wolverine_restored_big_image = zeros( tree_path_y_size, tree_path_x_size, 3 );

% take the values in the combined image and subtract them from the original

% tree_path.jpg values, this will give the values that were added to

% tree_path.jpg above, then divide these values by WEIGHTED_COMBINE_VALUE in order to try 

% to get the original values in wolverine.jpg

for x_value = 1:tree_path_x_size

    for y_value = 1:tree_path_y_size

        wolverine_restored_big_image( y_value, x_value, 1 ) = ( combined_image( y_value, x_value, 1 ) - original_tree_path_image( y_value, x_value, 1 ) ) / WEIGHTED_COMBINE_VALUE;

        wolverine_restored_big_image( y_value, x_value, 2 ) = ( combined_image( y_value, x_value, 2 ) - original_tree_path_image( y_value, x_value, 2 ) ) / WEIGHTED_COMBINE_VALUE;

        wolverine_restored_big_image( y_value, x_value, 3 ) = ( combined_image( y_value, x_value, 3 ) - original_tree_path_image( y_value, x_value, 3 ) ) / WEIGHTED_COMBINE_VALUE;

    end

end

% force wolverine restored image color values to be between 0 and 255

wolverine_restored_big_image = uint8( wolverine_restored_big_image );

if DISPLAY_INTERMEDIATE_IMAGES

    

    % display the restored wolverine.jpg that is still the size of tree_path.jpg

    figure, imshow( wolverine_restored_big_image ); title( 'Restored Wolverine - Tree Path Size' );

end

% resize wolverine_restored_big_image to the size of the original wolverine.jpg

wolverine_restored_image = imresize( wolverine_restored_big_image, [ original_wolverine_y_size , original_wolverine_x_size ] );

% display the restored wolverine image that is the original wolverine.jpg size

figure, imshow( wolverine_restored_image ); title( 'Restored Wolverine - Original Size' );

% compute the Peak Signal-to-Noise Ratio (PSNR) between the original

% wolverine.jpg and the wolverine image restored from the combined image

% The bigger the PSNR the closer the two images are to being exactly the

% same and if the PSNR is infinity the images are exactly the same

% If not infinity values of 30 to 50 are very good and values above 50 are

% excellent/amazing

% Displayed in the Matlab command window

mean_square_error = sum( sum( sum( ( original_wolverine_image - wolverine_restored_image ).^2 ) ) ) / double( original_wolverine_x_size * original_wolverine_y_size * 3 );

disp(' ')

disp('The PSNR between the original wolverine.jpg and the restored wolverine taken from the combined image:')

PSNR = 10 * log10( ( 255 )^2 / mean_square_error )

% DISPLAY_TESTING_IMAGES =

% 0 => no extra tests

% 1 => if the combined image is resized

% 2 => if the combined image is stored as a heavily compressed .jpg

% 3 => if the combined image has added noise

% 4 => show all tests

if (DISPLAY_TESTING_IMAGES ~= 0) && (DISPLAY_TESTING_IMAGES < 5)

    % try to restore the wolverine image from the combined image when the 

    % combined image is resized

    if (DISPLAY_TESTING_IMAGES == 1) || (DISPLAY_TESTING_IMAGES == 4)

    

        % the percent to resize the combined image

        PERCENT_TO_RESIZE = 0.4;

        % resize the combined image

        resized_combined_image = imresize( combined_image, [ tree_path_y_size * PERCENT_TO_RESIZE, tree_path_x_size * PERCENT_TO_RESIZE ] );

        

        if DISPLAY_INTERMEDIATE_IMAGES

            

            % display the resized combined image

            figure, imshow( resized_combined_image ); title( 'Combine Image - Resized' );

        end

        

        % restore the resized combined image to its original size

        restored_combined_image = imresize( resized_combined_image, [tree_path_y_size, tree_path_x_size ] );

        if DISPLAY_INTERMEDIATE_IMAGES


            % display the combined image that has been returned to its

            % original size

            figure, imshow( restored_combined_image ); title( 'Combined Image - Resized to Original Size' );

        end

        

        % try to get the original values in wolverine.jpg

        for x_value = 1:tree_path_x_size

            for y_value = 1:tree_path_y_size

                wolverine_restored_big_image( y_value, x_value, 1 ) = ( restored_combined_image( y_value, x_value, 1 ) - original_tree_path_image( y_value, x_value, 1 ) ) / WEIGHTED_COMBINE_VALUE;

                wolverine_restored_big_image( y_value, x_value, 2 ) = ( restored_combined_image( y_value, x_value, 2 ) - original_tree_path_image( y_value, x_value, 2 ) ) / WEIGHTED_COMBINE_VALUE;

                wolverine_restored_big_image( y_value, x_value, 3 ) = ( restored_combined_image( y_value, x_value, 3 ) - original_tree_path_image( y_value, x_value, 3 ) ) / WEIGHTED_COMBINE_VALUE;

            end

        end

        % force wolverine restored image color values to be between 0 and 255

        wolverine_restored_big_image = uint8( wolverine_restored_big_image );

        if DISPLAY_INTERMEDIATE_IMAGES


            % display the restored wolverine.jpg that is still the size of tree_path.jpg

            figure, imshow( wolverine_restored_big_image ); title( 'Restored Wolverine - Tree Path Size - Combined Image Resized' );

        end

        % resize wolverine_restored_big_image to the size of the original wolverine.jpg

        wolverine_restored_image = imresize( wolverine_restored_big_image, [ original_wolverine_y_size , original_wolverine_x_size ] );

        % display the restored wolverine image that is the original wolverine.jpg size

        figure, imshow( wolverine_restored_image ); title( 'Restored Wolverine - Original Size - Combined Image Resized' );

                

        % compute the Peak Signal-to-Noise Ratio (PSNR)

        % Displayed in the Matlab command window

        mean_square_error = sum( sum( sum( ( original_wolverine_image - wolverine_restored_image ).^2 ) ) ) / double( original_wolverine_x_size * original_wolverine_y_size * 3 );

        disp(' ')

        disp('The PSNR after resizing the combined image:')

        PSNR = 10 * log10( ( 255 )^2 / mean_square_error )

    end

    

    % try to restore the wolverine image from the combined image when the

    % combined image is made a heavily compressed .jpg

    if (DISPLAY_TESTING_IMAGES == 2) || (DISPLAY_TESTING_IMAGES == 4)

    

        % accepts values 0 to 100

        % even if you specify 100 Matlab will compress the image to a minor

        % degree, if you want absolutely no loss set PERCENT_OF_COMPRESSION

        % to 100 and add this line to the inside of the imwrite function:

        % , 'Mode', 'lossless'

        PERCENT_OF_COMPRESSION = 50;

        

        % uses Matlab to save the combined image as the compressed .jpg

        % image 'compressed_combined.jpg'

        imwrite( combined_image, 'combined_compressed.jpg', 'Quality', PERCENT_OF_COMPRESSION );

        

        % reads in the compressed combined .jpg image

        restored_combined_image = uint8( imread( 'combined_compressed.jpg' ) );

        if DISPLAY_INTERMEDIATE_IMAGES


            % display the compressed combined image .jpg image

            figure, imshow( restored_combined_image ); title( 'Combined Image - After Saving As a Compressed .jpg' );

        end

        % try to get the original values in wolverine.jpg

        for x_value = 1:tree_path_x_size

            for y_value = 1:tree_path_y_size

                wolverine_restored_big_image( y_value, x_value, 1 ) = ( restored_combined_image( y_value, x_value, 1 ) - original_tree_path_image( y_value, x_value, 1 ) ) / WEIGHTED_COMBINE_VALUE;

                wolverine_restored_big_image( y_value, x_value, 2 ) = ( restored_combined_image( y_value, x_value, 2 ) - original_tree_path_image( y_value, x_value, 2 ) ) / WEIGHTED_COMBINE_VALUE;

                wolverine_restored_big_image( y_value, x_value, 3 ) = ( restored_combined_image( y_value, x_value, 3 ) - original_tree_path_image( y_value, x_value, 3 ) ) / WEIGHTED_COMBINE_VALUE;

            end

        end

        % force wolverine restored image color values to be between 0 and 255

        wolverine_restored_big_image = uint8( wolverine_restored_big_image );

        if DISPLAY_INTERMEDIATE_IMAGES


            % display the restored wolverine.jpg that is still the size of tree_path.jpg

            figure, imshow( wolverine_restored_big_image ); title( 'Restored Wolverine - Tree Path Size - Combined Image Saved As a Compressed .jpg' );

        end

        % resize wolverine_restored_big_image to the size of the original wolverine.jpg

        wolverine_restored_image = imresize( wolverine_restored_big_image, [ original_wolverine_y_size , original_wolverine_x_size ] );

        % display the restored wolverine image that is the original wolverine.jpg size

        figure, imshow( wolverine_restored_image ); title( 'Restored Wolverine - Original Size - Combined Image Saved As a Compressed .jpg' );

        

        % compute the Peak Signal-to-Noise Ratio (PSNR)

        % Displayed in the Matlab command window

        mean_square_error = sum( sum( sum( ( original_wolverine_image - wolverine_restored_image ).^2 ) ) ) / double( original_wolverine_x_size * original_wolverine_y_size * 3 );

        disp(' ')

        disp('The PSNR after compressing the combined image to a .jpg:')

        PSNR = 10 * log10( ( 255 )^2 / mean_square_error )

    end


    % try to restore the wolverine image from the combined image when the

    % combined image has random added noise

    if (DISPLAY_TESTING_IMAGES == 3) || (DISPLAY_TESTING_IMAGES == 4)

    

        % specifies the maximum noise value

        MAX_NOISE_VALUE = 20;

        % specifies the approximate amount of noise

        % amount of noise = (1 / AMOUNT_OF_NOISE)%

        % 0 means every pixel of the combined image will have added noise

        % 1 means ~50% will have added noise, 2 means ~33%, 3 means ~25%, ....

        AMOUNT_OF_NOISE = 3;

        

        noise_image = uint8( zeros( tree_path_y_size, tree_path_x_size, 3 ) );

        

        % create a random noise image

        for x_value = 1:tree_path_x_size

            for y_value = 1:tree_path_y_size

                if round( rand() * AMOUNT_OF_NOISE ) == 0

                    noise_image( y_value, x_value, 1 ) = round( rand() * (MAX_NOISE_VALUE + 1) );

                    noise_image( y_value, x_value, 2 ) = round( rand() * (MAX_NOISE_VALUE + 1) );

                    noise_image( y_value, x_value, 3 ) = round( rand() * (MAX_NOISE_VALUE + 1) );

                end

                

            end

        end

                       

        % add the noise image to the combined image

        noise_added_combined_image = combined_image + noise_image;

        

        if DISPLAY_INTERMEDIATE_IMAGES

            noise_display_image = ones( tree_path_y_size, tree_path_x_size, 3 ) * 255;


            % create a random noise image

            for x_value = 1:tree_path_x_size

                for y_value = 1:tree_path_y_size

                    if noise_image( y_value, x_value, 1 ) ~= 0

                        noise_display_image( y_value, x_value, 1 ) = noise_image( y_value, x_value, 1 );

                    end

                    

                    if noise_image( y_value, x_value, 2 ) ~= 0

                        noise_display_image( y_value, x_value, 2 ) = noise_image( y_value, x_value, 2 );

                    end

                    

                    if noise_image( y_value, x_value, 3 ) ~= 0

                        noise_display_image( y_value, x_value, 3 ) = noise_image( y_value, x_value, 3 );

                    end

                end

            end

            

            % display the noise image

            figure, imshow( uint8(noise_display_image) ); title( 'Noise to Add to Combined Image' );

            

            % display the combined image with added noise

            figure, imshow( restored_combined_image ); title( 'Combined Image - After Adding Noise' );

        end

        % try to get the original values in wolverine.jpg

        for x_value = 1:tree_path_x_size

            for y_value = 1:tree_path_y_size

                                

                    wolverine_restored_big_image( y_value, x_value, 1 ) = ( noise_added_combined_image( y_value, x_value, 1 ) - original_tree_path_image( y_value, x_value, 1 ) ) / WEIGHTED_COMBINE_VALUE;

                    wolverine_restored_big_image( y_value, x_value, 2 ) = ( noise_added_combined_image( y_value, x_value, 2 ) - original_tree_path_image( y_value, x_value, 2 ) ) / WEIGHTED_COMBINE_VALUE;

                    wolverine_restored_big_image( y_value, x_value, 3 ) = ( noise_added_combined_image( y_value, x_value, 3 ) - original_tree_path_image( y_value, x_value, 3 ) ) / WEIGHTED_COMBINE_VALUE;                

            end

        end

        % force wolverine restored image color values to be between 0 and 255

        wolverine_restored_big_image = uint8( wolverine_restored_big_image );

        if DISPLAY_INTERMEDIATE_IMAGES


            % display the restored wolverine.jpg that is still the size of tree_path.jpg

            figure, imshow( wolverine_restored_big_image ); title( 'Restored Wolverine - Tree Path Size - Combined Image With Added Noise' );

        end

        % resize wolverine_restored_big_image to the size of the original wolverine.jpg

        wolverine_restored_image = imresize( wolverine_restored_big_image, [ original_wolverine_y_size , original_wolverine_x_size ] );

        % display the restored wolverine image that is the original wolverine.jpg size

        figure, imshow( wolverine_restored_image ); title( 'Restored Wolverine - Original Size - Combined Image With Added Noise' );

        

        % compute the Peak Signal-to-Noise Ratio (PSNR)

        % Displayed in the Matlab command window

        mean_square_error = sum( sum( sum( ( original_wolverine_image - wolverine_restored_image ).^2 ) ) ) / double( original_wolverine_x_size * original_wolverine_y_size * 3 );

        disp(' ')

        disp('The PSNR after adding random noise to the combined image:')

        PSNR = 10 * log10( ( 255 )^2 / mean_square_error )

    end

end

Posted in Imaging, Programming, Steganography | Tagged: , , , , , , , , , , , | 1 Comment »

Deinterlacing Images

Posted by oxaric on November 29, 2008




Today I worked with Matlab to find the best way to deinterlace a grayscale image. I used the famous Lena picture, figure 1, and set all of the odd-numbered lines to black, figure 2. My goal was to find the best way to replace the black lines so that the final image resembled the original as closely as possible.


You can view the algorithms I used and their resulting peak signal-to-noise ratio, PSNR, in table 1. I have not written down specific instructions for each algorithm but if you have questions about the algorithms feel free to contact me. The equation for finding the PSNR can be found on Wikipedia here.


Before testing any algorithms I wanted to see what I could expect for the PSNR boundaries. If you calculate the PSNR of an image to itself you will get infinity because there is only signal and no noise. However, once you change an image even slightly the PSNR is almost guaranteed to drop below 100. I found that if I changed one pixel of the original image by adding 1 to it’s color value the PSNR dropped to 96.293. And after replacing the odd-numbered lines in the original image with black lines I found the PSNR was 10.187. This let me know I could not expect a PSNR above 100 or below 10. Always a good thing to start out knowing your practical boundaries.


After trying out the 11 algorithms I found the max PSNR I could get was 34.031. This was accomplished using what I call the ‘direct averaging algorithm’. The results of this algorithm can be seen in figure 3.


Direct Averaging Algorithm

  • Start at the beginning of an odd-numbered line.
  • Choose a black pixel.
  • Take the color values of the pixels directly above and below this black pixel.
  • Average the two color values.
  • Replace the black pixel’s color value with this averaged value.
  • Repeat for every black pixel on an odd-numbered line.
Average Algorithm Example



For comparison, the results of another algorithm I call the ‘direct biggest algorithm’ can be seen in figure 4. The direct biggest algorithm gives a PSNR of 29.368 and is similar to the direct averaging algorithm but instead of using the averaged color value the biggest of the two color values is used to replace the black pixel.


The direct averaging algorithm was one of the first I tried and I was happy at the time. But I am slightly disappointed I did not achieve a higher PSNR. I feel there must be a way to to get a higher PSNR and I’m frustrated I haven’t thought of how. But the only reason I think there must be a better algorithm is because the algorithm I described seems too simple. I searched a litle on google to see if I could find a way to calculate a realistic practical optimum but I didn’t find anything. So for now I just have to live with a feeling that I can do better. That’s not such a bad thing though. :)


If you have any ideas how to achieve a higher PSNR I’d like to hear them!






Figure 1 – Original Image
Lena - Black & White


Figure 2 – Original Image Missing Odd-Numbered Lines
Lena - Missing Odd Lines


Figure 3 – Resulting Image of the Direct Average Algorithm
Direct Average Algorithm


Figure 4 – Resulting Image of the Direct Highest Algorithm
Direct Highest Algorithm






Table 1 – Algorithms and Resulting PSNRs (higher is better)


Original Image With Blacked Out Lines
PSNR = 10.1865


Direct Averaging Algorithm
PSNR = 34.0313


Hybrid1 – Threshold = 10
If the difference between the top and bottom pixels is less than the threshold choose the highest color, otherwise blend colors.
PSNR = 33.7630


Take Median of 3 Pixels Above and 3 Below
PSNR = 33.3099


Average 3 Pixels Above and 3 Below
PSNR = 31.7530


Take All Even Lines As One Image, Resize To Original Size
PSNR = 31.0262


Hybrid2 – Threshold = 10
If the difference between the top and bottom pixels is less than the threshold blend colors, otherwise choose highest color.
PSNR = 29.4632


Take Highest From Above and Below
PSNR = 29.3676


Take Lowest From Above and Below
PSNR = 29.3251


Replace Odd Lines With Even Lines
PSNR = 29.2886


Look At Bottom 3 Pixels, Find Closest to Top Pixel, Average Pixels
PSNR = 26.5942


Take Highest From 3 Above and 3 Below
PSNR = 23.5104

Posted in Imaging, Programming | Tagged: , , , , , , , , , , , | Leave a Comment »