1 | initial version |
@Arijit Datta I think that what you are trying to achieve is called steganography. In this approach you are just discarding the bits of a pixel that they do not contain that much information and you are replacing them with the information that you want to hide/mark. It can be applied both straight to spatial domain (actual image pixels) or in the frequency domain (using dft for example). It a nice subject and area to play with. Some time ago I made the following example which you can use, it is only in the spatial domain but you can play/search a bit more and port it to the frequency domain. In the following example I am just hiding an image within another image but you can hide text or whatever you want and then recover it again applying the inverse operation. This is the example:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
// functions applies steganography in spatial domain
void steganograph(Mat &aFrontImage, Mat &aHiddenImage)
{
// check if two images are the same type and size
CV_Assert(aFrontImage.type() == aHiddenImage.type() && aFrontImage.size() == aHiddenImage.size());
// create our stego image, where we are gonna store the final result
Mat aStegedImage(aFrontImage.rows, aFrontImage.cols, aFrontImage.type());
// create some temp images that we are gonna need later for the process
Mat tFront_image, tHidden_image;
if(aFrontImage.channels() == 3) // check if we are dealing with color images
{
// populate mask matrices with the value 0xF0 or 11110000 in binary
Mat front_mask(aFrontImage.rows, aFrontImage.cols, aFrontImage.type(), Scalar(0xF0, 0xF0, 0xF0));
Mat hidden_mask(aHiddenImage.rows, aHiddenImage.cols, aHiddenImage.type(), Scalar(0xF0, 0xF0, 0xF0));
// perform bitwise ANDing of two matrices and store the result in a third matrix.
// What we achieved with this operation? Well, now the resulting tFront_image and
// tHidden_image matrices contains only the first four important bits of each pixel
// in aFrontImage. The remaining four bits are zero padded
bitwise_and(aFrontImage, front_mask, tFront_image);
bitwise_and(aHiddenImage, hidden_mask, tHidden_image);
for(int j = 0; j < aHiddenImage.rows; j++)
for(int i = 0; i < aHiddenImage.cols; i++)
{
// right-shift the pixel components of the tHidden_image matrix by 4 bits,
// and hence the first four bits are zero padded.
tHidden_image.at<Vec3b>(j,i)[0] = tHidden_image.at<Vec3b>(j,i)[0] >> 4;
tHidden_image.at<Vec3b>(j,i)[1] = tHidden_image.at<Vec3b>(j,i)[1] >> 4;
tHidden_image.at<Vec3b>(j,i)[2] = tHidden_image.at<Vec3b>(j,i)[2] >> 4;
}
}else if(aFrontImage.channels() == 1){ // check if we are dealing with grayscale images
Mat front_mask(aFrontImage.rows, aFrontImage.cols, aFrontImage.type(), Scalar(0xF0));
Mat hidden_mask(aHiddenImage.rows, aHiddenImage.cols, aHiddenImage.type(), Scalar(0xF0));
bitwise_and(aFrontImage, front_mask, tFront_image);
bitwise_and(aHiddenImage, hidden_mask, tHidden_image);
for(int j = 0; j < aHiddenImage.rows; j++)
for(int i = 0; i < aHiddenImage.cols; i++)
{
tHidden_image.at<uchar>(j,i) = tHidden_image.at<uchar>(j,i) >> 4;
}
}
// Finally, perform the bitwise addition of the tFront_image and tHidden_image matrices
// to obtain aStegedImage, which is our steganograph image
bitwise_or(tFront_image, tHidden_image, aStegedImage);
// save and show the stego image
imwrite("stegedImg.png", aStegedImage);
imshow("aStegedImg", aStegedImage);
}
// function to desteganograph an image
void deSteganograph(Mat &aStegedImage)
{
// create matrices to store the results
Mat aFrontImage(aStegedImage.rows, aStegedImage.cols, aStegedImage.type());
Mat aHiddenImage(aStegedImage.rows, aStegedImage.cols, aStegedImage.type());
// Mat tFront_image, tHidden_image;
if(aFrontImage.channels() == 3) // check if we are dealing with color images
{
// populate again the mask matrices with the values 0xF0 or 11110000 in binary
// and 0x0F or 00001111 in binary depending which image we want to retrieve
Mat front_mask(aStegedImage.rows, aStegedImage.cols, aStegedImage.type(), Scalar(0xF0, 0xF0, 0xF0));
Mat hidden_mask(aStegedImage.rows, aStegedImage.cols, aStegedImage.type(), Scalar(0x0F, 0x0F, 0x0F));
// apply again bitwise_ANDing to retrieve the images
bitwise_and(aStegedImage, front_mask, aFrontImage);
bitwise_and(aStegedImage, hidden_mask, aHiddenImage);
for(int j = 0; j < aHiddenImage.rows; j++)
for(int i = 0; i < aHiddenImage.cols; i++)
{
// left-shift the pixel components of aHidden_image by 4 bits, because
// the first four bits are zero padded and the actual information is stored
// in the last 4 bits
aHiddenImage.at<Vec3b>(j,i)[0] = aHiddenImage.at<Vec3b>(j,i)[0] << 4;
aHiddenImage.at<Vec3b>(j,i)[1] = aHiddenImage.at<Vec3b>(j,i)[1] << 4;
aHiddenImage.at<Vec3b>(j,i)[2] = aHiddenImage.at<Vec3b>(j,i)[2] << 4;
}
}else if(aFrontImage.channels() == 1){ // check if we are dealing with grayscale images
Mat front_mask(aStegedImage.rows, aStegedImage.cols, aStegedImage.type(), Scalar(0xF0));
Mat hidden_mask(aStegedImage.rows, aStegedImage.cols, aStegedImage.type(), Scalar(0x0F));
bitwise_and(aStegedImage, front_mask, aFrontImage);
bitwise_and(aStegedImage, hidden_mask, aHiddenImage);
for(int j = 0; j < aHiddenImage.rows; j++)
for(int i = 0; i < aHiddenImage.cols; i++)
{
aHiddenImage.at<uchar>(j,i) = aHiddenImage.at<uchar>(j,i) << 4;
}
}
// final images
imshow("front", aFrontImage);
imshow("hidden", aHiddenImage);
}
int main()
{
Mat img1 = imread("../lena.png");
Mat img2 = imread("../baboon.png");
if(img1.empty() || !img1.data)
{
cerr << "Problem loading first image!" << endl;
return -1;
}
if(img2.empty() || !img2.data)
{
cerr << "Problem loading second image!" << endl;
return -1;
}
steganograph(img1, img2);
Mat img3 = imread("stegedImg.png");
deSteganograph(img3);
imshow("image1", img1);
imshow("image2", img2);
waitKey();
return 0;
}
And the result images.
Input images:
Steganografied image (if you notice really carefully you will see some characteristics of the baboon withing the lena's image):
and recovered images:
enjoy!!!