I am performing steganography on JPEG images. I am using DCT
coefficients to hide data. I do the following steps:
- Read input image into
BufferedImage
- Get an 8 x 8 block from
BufferedImage
- Apply forward DCT to get DCT coefficients
- Embed message bits in DCT coefficients
- Apply inverse DCT and write block back to
BufferedImage
Steps 2-5 are repeated until no message bits are remaining at which point I write BufferedImage
to a JPEG file named output.jpg
. It is worth mentioning that I do not apply DCT
on the entire image but only on the block where I embed message bits. I am using this source for DCT and this for steganography
The Problem: I end up with random lines on ouput.jpg
which signifies that pixel data changes dramatically. Following is code for image write:
static int[][] DCT_coefficients = new int[8][8];
static double[][] DCT_matrix = new double[8][8];
static int[][] Dequantized_m = new int[8][8];
static int[][] YBlock = new int[8][8];
static double[][] resultant = new double[8][8];
static int[][] red = new int[8][8];
static int[][] green = new int[8][8];
static int[][] blue = new int[8][8];
static int[][] Y = new int[8][8];
static int[][] Cb = new int[8][8];
static int[][] Cr = new int[8][8];
static int x = 0, y = 0;
public static void main(String args[]) {
//I retrieve a pixel block from **BufferedImage** and convert it to YCbCr
double temp1, temp2, temp3, temp4;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
Color c = new Color(input_image.getRGB(y, x));
red = c.getRed();
green = c.getGreen();
blue = c.getBlue();
YCbCr = YCrCb_conversion(red, green, blue);
YBlock[i][j] = YCbCr[0];
Cb[i][j] = YCbCr[1];
Cr[i][j] = YCbCr[2];
y++;
}
x++;
}
//calculating DCT matrix
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
if (i == 0) {
DCT_matrix[i][j] = 1 / Math.sqrt(8.0);
}
if (i > 0) {
temp1 = 0.5;
Apfloat Atemp1 = new Apfloat(temp1, 15);
temp3 = ((2 * j) + 1) * i * java.lang.Math.PI;
temp2 = temp3 / 16;
Apfloat Atemp2 = new Apfloat(temp2, 15);
temp4 = Math.cos(java.lang.Math.toRadians(temp2));
Apfloat Atemp4 = ApfloatMath.cos(Atemp2);
Apfloat Atemp = Atemp4.multiply(Atemp1);
DCT_matrix[i][j] = Atemp.doubleValue();
}
}
}
//Leveling off Yblock
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
leveledoff[i][j] = pixel_block[i][j] - 128;
}
}
// multiplying DCT matrix and leveled off YBlock
resultant = matrix_multiply(DCT_matrix, leveledoff);
// taking transpose of DCT matrix
transpose = obj.transpose(DCT_matrix);
//multiplying transpose with resultant to get DCT coefficient block
DCT_Coefficients = matrix_multiply(resultant, trans);
//quantizing DCT coefficients using 50 % quantization matrix
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
quantized_matrix[i][j] = (int) Math.round(DCT_Coefficients[i][j] / quantzation_matrix[i][j]);
}
}
//Applying zigzag encoding
ZigZag_encoded = zigzag.ZigzagEncode(quantised_m);
//Encode data
ZigZag_encoded = embed_data(ZigZag_encoded, data_buffer);
//start IDCT
IDCT(ZigZag_Encoded);
//end main
}
//Method definitions
//Method to change LSB of integer value
public static int changeBit(int pixel, char bit) {
String s = Integer.toBinaryString(pixel);
char[] c = s.toCharArray();
c[c.length - 1] = bit;
// converting binary to integer
String n = "";
for (int y = 0; y < c.length; y++) {
n += "" + c[y];
}
int j = 0;
for (int i = 0; i < n.length(); i++) {
if (n.charAt(i) == '1') {
j = j + pow(2, n.length() - 1 - i);
}
}
return j;
}
// method to embed data
public static int[] embed_data(int encoded[], char buffer[]) {
for (int i = 0; i < buffer.length; i++) {
int newVal = changeBit(encoded[i], buffer[i]);
encoded[i] = newVal;
}
return encoded;
}
// Method to perform IDCT
public static void IDCT(ZigZag_encoded) {
//converting 1D zigzag array to 2D array
quantised_matrix = Zigzag_Decode(ZigZag_encoded);
//Dequantizing
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
Dequantized[i][j] = quantised_matrix[i][j] * quantzation_matrix[i][j];
}
}
// multiplying transpose of DCT matrix with Dequantized matrix
resultant = matrix_multiply(transpose, Dequantized);
//multiplying resultant with DCT matrix
resultant = matrix_multiply(resultant, dct_mat);
//adding 128 to each resultant entry and rounding off
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
block[i][j] = (int) Math.round(resultant[i][j] + 128);
}
}
//Converting Yblock to RGB block
YCrCbToRGB(block, Cb, Cr);
//writing RGB block to BufferedImage
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
newcolor = new Color(red[i][j], green[i][j], blue[i][j]);
input_image.setRGB(y, x, newcolor.getRGB());
y++;
}
x++;
}
}
//method to convert RGB block to YCbCr
public int[] YCrCb_conversion(int red, int green, int blue) {
int[] YCbCr = new int[3];
//Y = 0.257R+ 0.504G + 0.098B + 16
YCbCr[0] = (int)(0.257 * red + 0.504 * green + 0.098 * blue) + 16;
//Cb=–0.148R – 0.291G+ 0.439B + 128
YCbCr[1] = (int)((-0.148 * red - 0.291 * green + 0.439 * blue) + 128);
//Cr = 0.439R – 0.368G – 0.071B + 128
YCbCr[2] = (int)((0.539 * red - 0.368 * green - 0.071 * blue) + 128);
return value
// index 0 holds values of Y 1 holds Cb values and 2 holds Cr vlaues
return YCbCr;
}
//Method to convert YCbCr block to RGB block
public static void YCrCbToRGB(int y[][], int Cb[][], int Cr[][]) {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
//R = 1.164(Y – 16) + 1.596(Cr – 128)
red[i][j] = (int)(1.164 * (y[i][j] - 16) + (1.596 * (Cr[i][j] - 128)));
//G = 1.164(Y – 16) – 0.813(Cr – 128) – 0.391(Cb – 128)
green[i][j] = (int)(1.164 * (y[i][j] - 16) - (0.813 * (Cr[i][j] - 128)) - (0.391 * (Cb[i][j] - 128)));
//B = 1.164(Y – 16) + 2.018(Cb – 128)
blue[i][j] = (int)(1.164 * (y[i][j] - 16) + (2.018 * (Cb[i][j] - 128)));
}
}
}
Sample input and output:
pixel block in Y space as input to DCT
223 224 226 227 229 229 230 230
226 227 227 227 228 229 229 230
228 228 228 228 228 229 230 231
228 228 228 228 229 231 233 234
227 228 229 230 231 233 234 234
226 227 229 230 232 233 233 233
227 228 230 232 232 231 229 228
228 230 232 234 232 229 226 223
DCT coefficient matrix after quantization and before embedding data:
51 -1 0 0 0 0 0 0
-1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
Quantized DCT coefficients after embedding data:
50 -1 0 1 0 0 0 0
-1 0 1 0 0 0 0 0
1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
Pixels block in Y space after embedding data and applying IDCT:
232 227 223 223 227 231 232 231
230 226 222 223 226 230 230 229
228 224 221 222 226 229 229 227
226 223 221 223 227 229 228 225
226 224 223 225 229 230 228 225
227 225 225 228 232 233 230 226
228 227 228 231 235 236 232 228
230 229 230 234 237 238 233 229
I thought that it was due to lossy nature of JPEG so I tried PNG but still got the same problem.