原始图片:
水印图片:
嵌入水印后的图片:
提取出来的水印图片:
工具类:ImageUtil.java MathTool.java
package com.zeph.watermark.util; public class MathTool { public static double[][] intToDoubleMatrix(int[][] input) { int height = input.length; int width = input[0].length; double[][] output = new double[height][width]; for (int i = 0; i < height; i++) { // 列 for (int j = 0; j < width; j++) { // 行 output[i][j] = Double.valueOf(String.valueOf(input[i][j])); System.out.print(output[i][j]); } System.out.println(); } return output; } public static double[] intToDoubleArray(int[] input) { int length = input.length; double[] output = new double[length]; for (int i = 0; i < length; i++) output[i] = Double.valueOf(String.valueOf(input[i])); return output; } public static void main(String[] args) { int[][] test = { { 4, 5, 6 }, { 1, 2, 3 } }; MathTool.intToDoubleMatrix(test); } }
package com.zeph.watermark.util; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; public class ImageUtil { /** * 获取图片 * * @param filepath * @return */ public static BufferedImage getImage(String filepath) { BufferedImage image = null; File file = new File(filepath); try { image = ImageIO.read(file); } catch (IOException e) { e.printStackTrace(); } return image; } /** * 获取图像文件的像素(图片转换为像素) * * @param filepath * @param format */ public int[] getImagePixels(String filepath) { BufferedImage image = null; File file = new File(filepath); try { image = ImageIO.read(file); } catch (IOException e) { e.printStackTrace(); } WritableRaster raster = image.getRaster(); // 得到图像的宽度 int width = raster.getWidth(); // 得到图像的高度 int height = raster.getHeight(); // RGB格式图像文件每一个点的颜色由红、绿、兰三种颜色构成,即实际图像可为3层, // 分别为R,G,B层,因此分解后的文件象素是实际坐标高度和宽度的三倍。 int[] pixels = new int[3 * width * height]; // 读取坐标的范围是从(0,0)坐标开始宽width,高height raster.getPixels(0, 0, width, height, pixels); return pixels; } /** * 像素转换成图像文件 * * @param result * @param width * @param height * @param filepath * @param format */ public static void setImage(double[] result, int width, int height, String filepath, String format, int type) { BufferedImage outImage = new BufferedImage(width, height, type); WritableRaster outRaster = outImage.getRaster(); outRaster.setPixels(0, 0, width, height, result); // 图像文件的写入 File outFile = new File(filepath); try { ImageIO.write(outImage, format, outFile); } catch (IOException e) { e.printStackTrace(); } } /** * 一维数组转为二维数组 * * @param m * @param width * @param height * @return */ public static int[][] arrayToMatrix(int[] m, int width, int height) { int[][] result = new int[height][width]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int p = j * height + i; result[i][j] = m[p]; } } return result; } /** * 一维数组转换为三维数组 * * @param pixels * @param width * @param height * @return */ public static int[][][] getRGBArrayToMatrix(int[] pixels, int width, int height) { // 已知有3个二维数组组成分别代表RGB int[][][] result = new int[3][height][width]; int[][] temp = new int[3][width * height]; for (int i = 0; i < pixels.length; i++) { int m = i / 3; int n = i % 3; temp[n][m] = pixels[i]; } result[0] = arrayToMatrix(temp[0], width, height); result[1] = arrayToMatrix(temp[1], width, height); result[2] = arrayToMatrix(temp[2], width, height); return result; } /** * 二维数组转为一维数组 * * @param m * @return */ public static double[] matrixToArray(double[][] m) { int p = m.length * m[0].length; double[] result = new double[p]; for (int i = 0; i < m.length; i++) { for (int j = 0; j < m[i].length; j++) { int q = j * m.length + i; result[q] = m[i][j]; } } return result; } /** * 三维数组转为一维数组 * * @param m * @return */ public static double[] getRGBMatrixToArray(double[][][] m) { int width = m[0].length; int height = m[0][0].length; int len = width * height; double[] result = new double[3 * len]; double[][] temp = new double[3][len]; temp[0] = matrixToArray(m[0]); temp[1] = matrixToArray(m[1]); temp[2] = matrixToArray(m[2]); for (int i = 0; i < 3; i++) { for (int j = 0; j < temp[i].length; j++) result[3 * j + i] = temp[i][j]; } return result; } }
package com.zeph.watermark.fdct; public interface Dct { static double C1 = 0.98078528, C2 = 0.923879532, C3 = 0.831469612, C4 = 0.707106781, C5 = 0.555570233, C6 = 0.382683432, C7 = 0.195090322; }
package com.zeph.watermark.fdct; public class FDct implements Dct { public static double[][] fDctTransform(double[][] ablk) { double[][] blk = new double[8][8]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { blk[i][j] = ablk[i][j]; } } // 对行变换 for (int i = 0; i <= 7; i++) { double S07, S16, S25, S34, S0734, S1625; double D07, D16, D25, D34, D0734, D1625; S07 = blk[i][0] + blk[i][7]; S16 = blk[i][1] + blk[i][6]; S25 = blk[i][2] + blk[i][5]; S34 = blk[i][3] + blk[i][4]; S0734 = S07 + S34; S1625 = S16 + S25; D07 = blk[i][0] - blk[i][7]; D16 = blk[i][1] - blk[i][6]; D25 = blk[i][2] - blk[i][5]; D34 = blk[i][3] - blk[i][4]; D0734 = S07 - S34; D1625 = S16 - S25; blk[i][0] = 0.5 * (C4 * (S0734 + S1625)); blk[i][1] = 0.5 * (C1 * D07 + C3 * D16 + C5 * D25 + C7 * D34); blk[i][2] = 0.5 * (C2 * D0734 + C6 * D1625); blk[i][3] = 0.5 * (C3 * D07 - C7 * D16 - C1 * D25 - C5 * D34); blk[i][4] = 0.5 * (C4 * (S0734 - S1625)); blk[i][5] = 0.5 * (C5 * D07 - C1 * D16 + C7 * D25 + C3 * D34); blk[i][6] = 0.5 * (C6 * D0734 - C2 * D1625); blk[i][7] = 0.5 * (C7 * D07 - C5 * D16 + C3 * D25 - C1 * D34); } // 对列变换 for (int j = 0; j <= 7; j++) { double S07, S16, S25, S34, S0734, S1625; double D07, D16, D25, D34, D0734, D1625; S07 = blk[0][j] + blk[7][j]; S16 = blk[1][j] + blk[6][j]; S25 = blk[2][j] + blk[5][j]; S34 = blk[3][j] + blk[4][j]; S0734 = S07 + S34; S1625 = S16 + S25; D07 = blk[0][j] - blk[7][j]; D16 = blk[1][j] - blk[6][j]; D25 = blk[2][j] - blk[5][j]; D34 = blk[3][j] - blk[4][j]; D0734 = S07 - S34; D1625 = S16 - S25; blk[0][j] = 0.5 * (C4 * (S0734 + S1625)); blk[1][j] = 0.5 * (C1 * D07 + C3 * D16 + C5 * D25 + C7 * D34); blk[2][j] = 0.5 * (C2 * D0734 + C6 * D1625); blk[3][j] = 0.5 * (C3 * D07 - C7 * D16 - C1 * D25 - C5 * D34); blk[4][j] = 0.5 * (C4 * (S0734 - S1625)); blk[5][j] = 0.5 * (C5 * D07 - C1 * D16 + C7 * D25 + C3 * D34); blk[6][j] = 0.5 * (C6 * D0734 - C2 * D1625); blk[7][j] = 0.5 * (C7 * D07 - C5 * D16 + C3 * D25 - C1 * D34); } return blk; } }
package com.zeph.watermark.fdct; public class IFDct implements Dct { public static double[][] iFDctTransform(double[][] ablk) { double[][] blk = new double[8][8]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { blk[i][j] = ablk[i][j]; } } // 对列做IDCT for (int j = 0; j <= 7; j++) { double[] tmp = new double[16]; // first step tmp[0] = blk[0][j] * C4 + blk[2][j] * C2; tmp[1] = blk[4][j] * C4 + blk[6][j] * C6; tmp[2] = blk[0][j] * C4 + blk[2][j] * C6; tmp[3] = -blk[4][j] * C4 - blk[6][j] * C2; tmp[4] = blk[0][j] * C4 - blk[2][j] * C6; tmp[5] = -blk[4][j] * C4 + blk[6][j] * C2; tmp[6] = blk[0][j] * C4 - blk[2][j] * C2; tmp[7] = blk[4][j] * C4 - blk[6][j] * C6; tmp[8] = blk[1][j] * C7 - blk[3][j] * C5; tmp[9] = blk[5][j] * C3 - blk[7][j] * C1; tmp[10] = blk[1][j] * C5 - blk[3][j] * C1; tmp[11] = blk[5][j] * C7 + blk[7][j] * C3; tmp[12] = blk[1][j] * C3 - blk[3][j] * C7; tmp[13] = -blk[5][j] * C1 - blk[7][j] * C5; tmp[14] = blk[1][j] * C1 + blk[3][j] * C3; tmp[15] = blk[5][j] * C5 + blk[7][j] * C7; // second step tmp[0] = 0.5 * (tmp[0] + tmp[1]); tmp[1] = 0.5 * (tmp[2] + tmp[3]); tmp[2] = 0.5 * (tmp[4] + tmp[5]); tmp[3] = 0.5 * (tmp[6] + tmp[7]); tmp[4] = 0.5 * (tmp[8] + tmp[9]); tmp[5] = 0.5 * (tmp[10] + tmp[11]); tmp[6] = 0.5 * (tmp[12] + tmp[13]); tmp[7] = 0.5 * (tmp[14] + tmp[15]); // third step blk[0][j] = tmp[0] + tmp[7]; blk[1][j] = tmp[1] + tmp[6]; blk[2][j] = tmp[2] + tmp[5]; blk[3][j] = tmp[3] + tmp[4]; blk[4][j] = tmp[3] - tmp[4]; blk[5][j] = tmp[2] - tmp[5]; blk[6][j] = tmp[1] - tmp[6]; blk[7][j] = tmp[0] - tmp[7]; } // 对行做IDCT for (int i = 0; i <= 7; i++) { double[] tmp = new double[16]; // first step tmp[0] = blk[i][0] * C4 + blk[i][2] * C2; tmp[1] = blk[i][4] * C4 + blk[i][6] * C6; tmp[2] = blk[i][0] * C4 + blk[i][2] * C6; tmp[3] = -blk[i][4] * C4 - blk[i][6] * C2; tmp[4] = blk[i][0] * C4 - blk[i][2] * C6; tmp[5] = -blk[i][4] * C4 + blk[i][6] * C2; tmp[6] = blk[i][0] * C4 - blk[i][2] * C2; tmp[7] = blk[i][4] * C4 - blk[i][6] * C6; tmp[8] = blk[i][1] * C7 - blk[i][3] * C5; tmp[9] = blk[i][5] * C3 - blk[i][7] * C1; tmp[10] = blk[i][1] * C5 - blk[i][3] * C1; tmp[11] = blk[i][5] * C7 + blk[i][7] * C3; tmp[12] = blk[i][1] * C3 - blk[i][3] * C7; tmp[13] = -blk[i][5] * C1 - blk[i][7] * C5; tmp[14] = blk[i][1] * C1 + blk[i][3] * C3; tmp[15] = blk[i][5] * C5 + blk[i][7] * C7; // second step tmp[0] = 0.5 * (tmp[0] + tmp[1]); tmp[1] = 0.5 * (tmp[2] + tmp[3]); tmp[2] = 0.5 * (tmp[4] + tmp[5]); tmp[3] = 0.5 * (tmp[6] + tmp[7]); tmp[4] = 0.5 * (tmp[8] + tmp[9]); tmp[5] = 0.5 * (tmp[10] + tmp[11]); tmp[6] = 0.5 * (tmp[12] + tmp[13]); tmp[7] = 0.5 * (tmp[14] + tmp[15]); // third step blk[i][0] = tmp[0] + tmp[7]; blk[i][1] = tmp[1] + tmp[6]; blk[i][2] = tmp[2] + tmp[5]; blk[i][3] = tmp[3] + tmp[4]; blk[i][4] = tmp[3] - tmp[4]; blk[i][5] = tmp[2] - tmp[5]; blk[i][6] = tmp[1] - tmp[6]; blk[i][7] = tmp[0] - tmp[7]; } return blk; } }
package com.zeph.watermark.fdct; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import com.zeph.watermark.util.ImageUtil; import com.zeph.watermark.util.MathTool; public class AddWatermark { private static final int d = 5; public static void main(String[] args) { AddWatermark embed = new AddWatermark(); embed.start(); } public void start() { BufferedImage oImage = ImageUtil.getImage("D://lena.jpg"); BufferedImage wImage = ImageUtil.getImage("D://zhong.bmp"); int type = oImage.getType(); WritableRaster oRaster = oImage.getRaster(); WritableRaster wRaster = wImage.getRaster(); int oWidth = oRaster.getWidth(); int oHeight = oRaster.getHeight(); int wWidth = wRaster.getWidth(); int wHeight = wRaster.getHeight(); int[] oPixels = new int[3 * oWidth * oHeight]; int[] wPixels = new int[wWidth * wHeight]; oRaster.getPixels(0, 0, oWidth, oHeight, oPixels); wRaster.getPixels(0, 0, wWidth, wHeight, wPixels); int[][][] RGBPixels = ImageUtil.getRGBArrayToMatrix(oPixels, oWidth, oHeight); // 得到RGB图像的三层矩阵表示 double[][] rPixels = MathTool.intToDoubleMatrix(RGBPixels[2]); int[][] wDMatrix = ImageUtil.arrayToMatrix(wPixels, wWidth, wHeight); double[][] result = rPixels; // 嵌入算法 for (int i = 0; i < wWidth; i++) { for (int j = 0; j < wHeight; j++) { double[][] blk = new double[8][8]; // 对原始图像8 * 8 分块 for (int m = 0; m < 8; m++) { for (int n = 0; n < 8; n++) { blk[m][n] = rPixels[8 * i + m][8 * j + n]; } } double[][] dBlk = FDct.fDctTransform(blk); if (wDMatrix[i][j] == 0) { dBlk[3][3] = dBlk[3][3] - d; dBlk[3][4] = dBlk[3][4] - d; dBlk[3][5] = dBlk[3][5] - d; dBlk[4][3] = dBlk[4][3] - d; dBlk[5][3] = dBlk[5][3] - d; } else { dBlk[3][3] = dBlk[3][3] + d; dBlk[3][4] = dBlk[3][4] + d; dBlk[3][5] = dBlk[3][5] + d; dBlk[4][3] = dBlk[4][3] + d; dBlk[5][3] = dBlk[5][3] + d; } blk = IFDct.iFDctTransform(dBlk); for (int m = 0; m < 8; m++) { for (int n = 0; n < 8; n++) { result[8 * i + m][8 * j + n] = blk[m][n]; } } } } double[][][] temp = new double[3][oWidth][oHeight]; temp[0] = MathTool.intToDoubleMatrix(RGBPixels[0]); temp[1] = MathTool.intToDoubleMatrix(RGBPixels[1]); temp[2] = result; double[] rgbResult = ImageUtil.getRGBMatrixToArray(temp); // 将BufferedImage对象写入磁盘 ImageUtil.setImage(rgbResult, oWidth, oHeight, "D://result.bmp", "bmp", type); } }
package com.zeph.watermark.fdct; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import com.zeph.watermark.util.ImageUtil; import com.zeph.watermark.util.MathTool; public class ExtractWatermark { public static void main(String[] args) { ExtractWatermark distill = new ExtractWatermark(); distill.start(32, 32); } public void start(int wWidth, int wHeight) { // mImage是嵌入水印后的图像 BufferedImage mImage = ImageUtil.getImage("D://result.bmp"); // 原始图像 BufferedImage oImage = ImageUtil.getImage("D://lena.jpg"); WritableRaster oRaster = oImage.getRaster(); WritableRaster mRaster = mImage.getRaster(); int oWidth = oRaster.getWidth(); int oHeight = oRaster.getHeight(); int[] oPixels = new int[3 * oWidth * oHeight]; int[] mPixels = new int[3 * oWidth * oHeight]; oRaster.getPixels(0, 0, oWidth, oHeight, oPixels); mRaster.getPixels(0, 0, oWidth, oHeight, mPixels); // 得rgb图像三层矩阵,mRgbPixels[0]表示b层分量 int[][][] mRgbPixels = ImageUtil.getRGBArrayToMatrix(mPixels, oWidth, oHeight); int[][][] oRgbPixels = ImageUtil.getRGBArrayToMatrix(oPixels, oWidth, oHeight); double[][] oDPixels = MathTool.intToDoubleMatrix(mRgbPixels[2]); double[][] mDPixels = MathTool.intToDoubleMatrix(oRgbPixels[2]); double[][] result = new double[wWidth][wHeight]; for (int i = 0; i < wWidth; i++) { for (int j = 0; j < wHeight; j++) { double[][] oBlk = new double[8][8]; double[][] mBlk = new double[8][8]; int d = 0; int f = 0; for (int m = 0; m < 8; m++) { for (int n = 0; n < 8; n++) { oBlk[m][n] = oDPixels[8 * i + m][8 * j + n]; mBlk[m][n] = mDPixels[8 * i + m][8 * j + n]; } } double[][] dOBlk = FDct.fDctTransform(oBlk); double[][] dMBlk = FDct.fDctTransform(mBlk); if (dOBlk[3][3] > dMBlk[3][3]) { d++; } else { f++; } if (dOBlk[3][4] > dMBlk[3][4]) { d++; } else { f++; } if (dOBlk[3][5] > dMBlk[3][5]) { d++; } else { f++; } if (dOBlk[4][3] > dMBlk[4][3]) { d++; } else { f++; } if (dOBlk[5][3] > dMBlk[5][3]) { d++; } else { f++; } if (d < f) { result[i][j] = 0; } else { result[i][j] = 1; } } } double[] outResult = ImageUtil.matrixToArray(result); // 把嵌入水印的结果写到BufferedImage对象 ImageUtil.setImage(outResult, wWidth, wHeight, "D://mark.bmp", "bmp", BufferedImage.TYPE_BYTE_BINARY); } }
程序的实现参考:刘剑鸣 著 《图像数字水印的JAVA实现》
书中没有给出完整的程序示例,在这里,我将它补充和修改,将程序调试成功。程序算法是针对指定的图片,也就是非盲性图片的水印算法。