+ * 添加图片文字水印
+ *
+ * @author Yangxiaohui
+ * @date 2018-10-25 19:16
+ * @param image 图片对象
+ * @param watermarkText 水印文字
+ */
+ public static Mat addImageWatermarkWithText(Mat image, String watermarkText){
+ Mat complexImage = new Mat();
+ //优化图像的尺寸
+ //Mat padded = optimizeImageDim(com.lmc.image);
+ Mat padded = splitSrc(image);
+ padded.convertTo(padded, CvType.CV_32F);
+ planes.add(padded);
+ planes.add(Mat.zeros(padded.size(), CvType.CV_32F));
+ Core.merge(planes, complexImage);
+ // dft
+ Core.dft(complexImage, complexImage);
+ // 添加文本水印
+ Scalar scalar = new Scalar(0, 0, 0);
+ Point point = new Point(40, 40);
+ Imgproc.putText(complexImage, watermarkText, point, Imgproc.FONT_HERSHEY_DUPLEX, 1D, scalar);
+ Core.flip(complexImage, complexImage, -1);
+ Imgproc.putText(complexImage, watermarkText, point, Imgproc.FONT_HERSHEY_DUPLEX, 1D, scalar);
+ Core.flip(complexImage, complexImage, -1);
+ return antitransformImage(complexImage, allPlanes);
+ }
+ /**
+ *
+ * 获取图片水印
+ *
+ * @author Yangxiaohui
+ * @date 2018-10-25 19:58
+ * @param image
+ */
+ public static Mat getImageWatermarkWithText(Mat image){
+ List planes = new ArrayList();
+ Mat complexImage = new Mat();
+ Mat padded = splitSrc(image);
+ padded.convertTo(padded, CvType.CV_32F);
+ planes.add(padded);
+ planes.add(Mat.zeros(padded.size(), CvType.CV_32F));
+ Core.merge(planes, complexImage);
+ // dft
+ Core.dft(complexImage, complexImage);
+ Mat magnitude = createOptimizedMagnitude(complexImage);
+ planes.clear();
+ return magnitude;
+ }
+
+ private static Mat splitSrc(Mat mat) {
+ mat = optimizeImageDim(mat);
+ Core.split(mat, allPlanes);
+ Mat padded = new Mat();
+ if (allPlanes.size() > 1) {
+ for (int i = 0; i < allPlanes.size(); i++) {
+ if (i == 0) {
+ padded = allPlanes.get(i);
+ break;
+ }
+ }
+ } else {
+ padded = mat;
+ }
+ return padded;
+ }
+ private static Mat antitransformImage(Mat complexImage, List allPlanes) {
+ Mat invDFT = new Mat();
+ Core.idft(complexImage, invDFT, Core.DFT_SCALE | Core.DFT_REAL_OUTPUT, 0);
+ Mat restoredImage = new Mat();
+ invDFT.convertTo(restoredImage, CvType.CV_8U);
+ if (allPlanes.size() == 0) {
+ allPlanes.add(restoredImage);
+ } else {
+ allPlanes.set(0, restoredImage);
+ }
+ Mat lastImage = new Mat();
+ Core.merge(allPlanes, lastImage);
+ return lastImage;
+ }
+ /**
+ *
+ * 为加快傅里叶变换的速度,对要处理的图片尺寸进行优化
+ *
+ * @author Yangxiaohui
+ * @date 2018-10-25 19:33
+ * @param image
+ * @return
+ */
+ private static Mat optimizeImageDim(Mat image) {
+ Mat padded = new Mat();
+ int addPixelRows = Core.getOptimalDFTSize(image.rows());
+ int addPixelCols = Core.getOptimalDFTSize(image.cols());
+ Core.copyMakeBorder(image, padded, 0, addPixelRows - image.rows(), 0, addPixelCols - image.cols(),
+ Core.BORDER_CONSTANT, Scalar.all(0));
+
+ return padded;
+ }
+ private static Mat createOptimizedMagnitude(Mat complexImage) {
+ List newPlanes = new ArrayList();
+ Mat mag = new Mat();
+ Core.split(complexImage, newPlanes);
+ Core.magnitude(newPlanes.get(0), newPlanes.get(1), mag);
+ Core.add(Mat.ones(mag.size(), CvType.CV_32F), mag, mag);
+ Core.log(mag, mag);
+ shiftDFT(mag);
+ mag.convertTo(mag, CvType.CV_8UC1);
+ Core.normalize(mag, mag, 0, 255, Core.NORM_MINMAX, CvType.CV_8UC1);
+ return mag;
+ }
+ private static void shiftDFT(Mat image) {
+ image = image.submat(new Rect(0, 0, image.cols() & -2, image.rows() & -2));
+ int cx = image.cols() / 2;
+ int cy = image.rows() / 2;
+
+ Mat q0 = new Mat(image, new Rect(0, 0, cx, cy));
+ Mat q1 = new Mat(image, new Rect(cx, 0, cx, cy));
+ Mat q2 = new Mat(image, new Rect(0, cy, cx, cy));
+ Mat q3 = new Mat(image, new Rect(cx, cy, cx, cy));
+ Mat tmp = new Mat();
+ q0.copyTo(tmp);
+ q3.copyTo(q0);
+ tmp.copyTo(q3);
+ q1.copyTo(tmp);
+ q2.copyTo(q1);
+ tmp.copyTo(q2);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/lmc/shuiyin/ShuiYinUtils.java b/src/main/java/com/lmc/shuiyin/ShuiYinUtils.java
new file mode 100644
index 0000000..7ea3f8a
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/ShuiYinUtils.java
@@ -0,0 +1,19 @@
+package com.lmc.shuiyin;
+
+import org.opencv.core.Size;
+
+import static org.opencv.imgproc.Imgproc.FONT_HERSHEY_COMPLEX;
+import static org.opencv.imgproc.Imgproc.getTextSize;
+
+public class ShuiYinUtils {
+ public static double getFontScale(Size maxSize, String watermark,int fontFace,int tickness){
+ double fontScale=0.5;
+ while (true){
+ Size size = getTextSize(watermark,fontFace,fontScale,tickness,null);
+ if(size.height>=maxSize.height || size.width>= maxSize.width)
+ break;
+ fontScale=fontScale+0.2;
+ }
+ return fontScale;
+ }
+}
diff --git a/src/main/java/com/lmc/shuiyin/one/Test.java b/src/main/java/com/lmc/shuiyin/one/Test.java
new file mode 100644
index 0000000..f67138f
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/one/Test.java
@@ -0,0 +1,20 @@
+package com.lmc.shuiyin.one;
+
+import com.lmc.shuiyin.one.converter.Converter;
+import com.lmc.shuiyin.one.converter.DftConverter;
+import com.lmc.shuiyin.one.dencoder.Decoder;
+import com.lmc.shuiyin.one.dencoder.Encoder;
+import com.lmc.shuiyin.one.dencoder.TextEncoder;
+import org.opencv.core.Core;
+
+public class Test {
+ public static void main(String[] args) {
+ System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
+ Converter converter = new DftConverter();
+ Encoder encoder = new TextEncoder(converter);
+ Decoder decoder = new Decoder(converter);
+ encoder.encode("C:\\tools\\yinhua.png", "CheShenWeiWu", "C:\\tools\\yinhua2.png");
+ decoder.decode("C:\\tools\\yinhua2.png", "C:\\tools\\yinhua3.png");
+ decoder.decode("C:\\tools\\444.jpg", "C:\\tools\\555.jpg");
+ }
+}
diff --git a/src/main/java/com/lmc/shuiyin/one/converter/Converter.java b/src/main/java/com/lmc/shuiyin/one/converter/Converter.java
new file mode 100644
index 0000000..86b8fef
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/one/converter/Converter.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020 ww23(https://github.com/ww23/BlindWatermark).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.lmc.shuiyin.one.converter;
+
+import org.opencv.core.Mat;
+
+/**
+ * @author ww23
+ */
+public interface Converter {
+ Mat start(Mat src);
+ void inverse(Mat com);
+ void addTextWatermark(Mat com, String watermark);
+ void addImageWatermark(Mat com, Mat watermark);
+ Mat showWatermark(Mat src);
+}
diff --git a/src/main/java/com/lmc/shuiyin/one/converter/DctConverter.java b/src/main/java/com/lmc/shuiyin/one/converter/DctConverter.java
new file mode 100644
index 0000000..c047a8e
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/one/converter/DctConverter.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020 ww23(https://github.com/ww23/BlindWatermark).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.lmc.shuiyin.one.converter;
+
+import com.lmc.shuiyin.one.util.Utils;
+import org.opencv.core.Mat;
+import org.opencv.core.Point;
+import org.opencv.core.Scalar;
+
+import static org.opencv.core.Core.*;
+import static org.opencv.core.CvType.CV_32F;
+import static org.opencv.core.CvType.CV_8UC1;
+import static org.opencv.imgproc.Imgproc.*;
+
+/**
+ * @author ww23
+ */
+public class DctConverter implements Converter {
+
+ @Override
+ public Mat start(Mat src) {
+ if ((src.cols() & 1) != 0) {
+ copyMakeBorder(src, src, 0, 0, 0, 1, BORDER_CONSTANT, Scalar.all(0));
+ }
+ if ((src.rows() & 1) != 0) {
+ copyMakeBorder(src, src, 0, 1, 0, 0, BORDER_CONSTANT, Scalar.all(0));
+ }
+ src.convertTo(src, CV_32F);
+ dct(src, src);
+ return src;
+ }
+
+ @Override
+ public void inverse(Mat com) {
+ idct(com, com);
+ }
+
+ @Override
+ public void addTextWatermark(Mat com, String watermark) {
+ putText(com, watermark,
+ new Point(com.cols() >> 2, com.rows() >> 2),
+ FONT_HERSHEY_COMPLEX, 2.0,
+ new Scalar(2, 2, 2, 0), 2, 8, false);
+ }
+
+ @Override
+ public void addImageWatermark(Mat com, Mat watermark) {
+ Mat mask = new Mat();
+ inRange(watermark, new Scalar(0, 0, 0, 0), new Scalar(0, 0, 0, 0), mask);
+ Mat i2 = new Mat(watermark.size(), watermark.type(), new Scalar(2, 2, 2, 0));
+ i2.copyTo(watermark, mask);
+ watermark.convertTo(watermark, CV_32F);
+ int row = (com.rows() - watermark.rows()) >> 1;
+ int col = (com.cols() - watermark.cols()) >> 1;
+ copyMakeBorder(watermark, watermark, row, row, col, col, BORDER_CONSTANT, Scalar.all(0));
+ Utils.fixSize(watermark, com);
+ addWeighted(watermark, 0.03, com, 1, 0.0, com);
+ }
+
+ @Override
+ public Mat showWatermark(Mat src) {
+ src.convertTo(src, COLOR_RGB2HSV);
+ inRange(src, new Scalar(0, 0, 0, 0), new Scalar(16, 16, 16, 0), src);
+ normalize(src, src, 0, 255, NORM_MINMAX, CV_8UC1);
+ return src;
+ }
+}
diff --git a/src/main/java/com/lmc/shuiyin/one/converter/DftConverter.java b/src/main/java/com/lmc/shuiyin/one/converter/DftConverter.java
new file mode 100644
index 0000000..40fe06e
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/one/converter/DftConverter.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2020 ww23(https://github.com/ww23/BlindWatermark).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.lmc.shuiyin.one.converter;
+
+import com.lmc.shuiyin.ShuiYinUtils;
+import com.lmc.shuiyin.one.util.Utils;
+import org.opencv.core.Mat;
+import org.opencv.core.Point;
+import org.opencv.core.Scalar;
+import org.opencv.core.Size;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.opencv.core.Core.*;
+import static org.opencv.core.CvType.*;
+import static org.opencv.imgproc.Imgproc.*;
+
+
+/**
+ * @author ww23
+ */
+@Deprecated
+public class DftConverter implements Converter {
+
+ @Override
+ public Mat start(Mat src) {
+ src.convertTo(src, CV_32F);
+ List planes = new ArrayList<>(2);
+ Mat com = new Mat();
+ planes.add(0, src);
+ planes.add(1, Mat.zeros(src.size(), CV_32F));
+ merge(planes, com);
+ dft(com, com);
+ return com;
+ }
+
+ @Override
+ public void inverse(Mat com) {
+ List planes = new ArrayList<>(2);
+ idft(com, com);
+ split(com, planes);
+ normalize(planes.get(0), com, 0, 255, NORM_MINMAX, CV_8UC3);
+ }
+
+ @Override
+ public void addTextWatermark(Mat com, String watermark) {
+ Scalar s = new Scalar(255, 255,255, 0);
+ Point p = new Point(0, com.rows() / 3);
+ int tickness=3;
+ double fontScale = ShuiYinUtils.getFontScale(new Size(com.cols(),com.rows()/2),watermark,FONT_HERSHEY_COMPLEX,tickness);
+ putText(com, watermark, p, FONT_HERSHEY_COMPLEX , fontScale, s, tickness,8,false);
+ flip(com, com, -1);
+ putText(com, watermark, p, FONT_HERSHEY_COMPLEX , fontScale, s, tickness,8,false);
+ flip(com, com, -1);
+ }
+
+ @Override
+ public void addImageWatermark(Mat com, Mat watermark) {
+ List planes = new ArrayList<>(2);
+ List newPlanes = new ArrayList<>(2);
+ Mat temp = new Mat();
+ int col = (com.cols() - watermark.cols()) >> 1;
+ int row = ((com.rows() >> 1) - watermark.rows()) >> 1;
+ watermark.convertTo(watermark, CV_32F);
+ copyMakeBorder(watermark, watermark, row, row, col, col, BORDER_CONSTANT, Scalar.all(0));
+ planes.add(0, watermark);
+ flip(watermark, temp, -1);
+ planes.add(1, temp);
+ vconcat(planes, watermark);
+
+ newPlanes.add(0, watermark);
+ newPlanes.add(1, watermark);
+ merge(newPlanes, watermark);
+ Utils.fixSize(watermark, com);
+ addWeighted(watermark, 8, com, 1, 0.0, com);
+
+ split(com, planes);
+ }
+
+ @Override
+ public Mat showWatermark(Mat src) {
+ List newPlanes = new ArrayList<>(2);
+ Mat mag = new Mat();
+ split(src, newPlanes);
+ magnitude(newPlanes.get(0), newPlanes.get(1), mag);
+ add(Mat.ones(mag.size(), CV_32F), mag, mag);
+ log(mag, mag);
+ mag.convertTo(mag, CV_8UC1);
+ normalize(mag, mag, 0, 255, NORM_MINMAX, CV_8UC1);
+ return mag;
+ }
+}
diff --git a/src/main/java/com/lmc/shuiyin/one/converter/DwtConverter.java b/src/main/java/com/lmc/shuiyin/one/converter/DwtConverter.java
new file mode 100644
index 0000000..5f1197a
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/one/converter/DwtConverter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020 ww23(https://github.com/ww23/BlindWatermark).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.lmc.shuiyin.one.converter;
+
+import org.opencv.core.Mat;
+
+//TODO
+public class DwtConverter implements Converter {
+ @Override
+ public Mat start(Mat src) {
+ return null;
+ }
+
+ @Override
+ public void inverse(Mat com) {
+
+ }
+
+ @Override
+ public void addTextWatermark(Mat com, String watermark) {
+
+ }
+
+ @Override
+ public void addImageWatermark(Mat com, Mat watermark) {
+
+ }
+
+ @Override
+ public Mat showWatermark(Mat src) {
+ return null;
+ }
+}
diff --git a/src/main/java/com/lmc/shuiyin/one/dencoder/Decoder.java b/src/main/java/com/lmc/shuiyin/one/dencoder/Decoder.java
new file mode 100644
index 0000000..da69989
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/one/dencoder/Decoder.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020 ww23(https://github.com/ww23/BlindWatermark).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.lmc.shuiyin.one.dencoder;
+
+import com.lmc.shuiyin.one.converter.Converter;
+import com.lmc.shuiyin.one.util.Utils;
+
+import static org.opencv.core.CvType.CV_8U;
+import static org.opencv.imgcodecs.Imgcodecs.imwrite;
+
+/**
+ * @author ww23
+ */
+public class Decoder {
+
+ private Converter converter;
+
+ public Decoder(Converter converter) {
+ this.converter = converter;
+ }
+
+ public Converter getConverter() {
+ return converter;
+ }
+
+ public void setConverter(Converter converter) {
+ this.converter = converter;
+ }
+
+ public void decode(String image, String output) {
+ imwrite(output, this.converter.showWatermark(this.converter.start(Utils.read(image, CV_8U))));
+ }
+}
diff --git a/src/main/java/com/lmc/shuiyin/one/dencoder/Encoder.java b/src/main/java/com/lmc/shuiyin/one/dencoder/Encoder.java
new file mode 100644
index 0000000..aba8570
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/one/dencoder/Encoder.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020 ww23(https://github.com/ww23/BlindWatermark).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.lmc.shuiyin.one.dencoder;
+
+import com.lmc.shuiyin.one.converter.Converter;
+import com.lmc.shuiyin.one.util.Utils;
+import org.opencv.core.Mat;
+import org.opencv.core.Rect;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.opencv.core.Core.merge;
+import static org.opencv.core.Core.split;
+import static org.opencv.core.CvType.CV_8S;
+import static org.opencv.imgcodecs.Imgcodecs.imwrite;
+
+/**
+ * @author ww23
+ */
+public abstract class Encoder {
+
+ Converter converter;
+
+ Encoder(Converter converter) {
+ this.converter = converter;
+ }
+
+ public Converter getConverter() {
+ return converter;
+ }
+
+ public void setConverter(Converter converter) {
+ this.converter = converter;
+ }
+
+ public void encode(String image, String watermark, String output) {
+ Mat src = Utils.read(image, CV_8S);
+
+ List channel = new ArrayList<>(3);
+ List newChannel = new ArrayList<>(3);
+ split(src, channel);
+
+ for (int i = 0; i < 3; i++) {
+ Mat com = this.converter.start(channel.get(i)).clone();
+ this.addWatermark(com, watermark);
+ this.converter.inverse(com);
+ newChannel.add(i, com);
+ }
+
+ Mat res = new Mat();
+ merge(newChannel, res);
+
+ if (res.rows() != src.rows() || res.cols() != src.cols()) {
+ res = new Mat(res, new Rect(0, 0, src.width(), src.height()));
+ }
+
+ imwrite(output, res);
+ }
+
+ public abstract void addWatermark(Mat com, String watermark);
+}
diff --git a/src/main/java/com/lmc/shuiyin/one/dencoder/ImageEncoder.java b/src/main/java/com/lmc/shuiyin/one/dencoder/ImageEncoder.java
new file mode 100644
index 0000000..3d1375b
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/one/dencoder/ImageEncoder.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2020 ww23(https://github.com/ww23/BlindWatermark).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.lmc.shuiyin.one.dencoder;
+
+import com.lmc.shuiyin.one.converter.Converter;
+import com.lmc.shuiyin.one.util.Utils;
+import org.opencv.core.Mat;
+
+import static org.opencv.core.CvType.CV_8U;
+
+/**
+ * @author ww23
+ */
+public class ImageEncoder extends Encoder {
+
+ public ImageEncoder(Converter converter) {
+ super(converter);
+ }
+
+ @Override
+ public void addWatermark(Mat com, String watermark) {
+ this.converter.addImageWatermark(com, Utils.read(watermark, CV_8U));
+ }
+}
diff --git a/src/main/java/com/lmc/shuiyin/one/dencoder/TextEncoder.java b/src/main/java/com/lmc/shuiyin/one/dencoder/TextEncoder.java
new file mode 100644
index 0000000..d10accb
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/one/dencoder/TextEncoder.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020 ww23(https://github.com/ww23/BlindWatermark).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.lmc.shuiyin.one.dencoder;
+
+import com.lmc.shuiyin.one.converter.Converter;
+import com.lmc.shuiyin.one.util.Utils;
+import org.opencv.core.Mat;
+
+/**
+ * @author ww23
+ */
+public class TextEncoder extends Encoder {
+
+ public TextEncoder(Converter converter) {
+ super(converter);
+ }
+
+ @Override
+ public void addWatermark(Mat com, String watermark) {
+ //this.converter.addTextWatermark(com, watermark);
+ if (Utils.isAscii(watermark)) {
+ this.converter.addTextWatermark(com, watermark);
+ } else {
+ //Mat mat = Utils.read("C:\\tools\\test.png",CV_8U);//
+ this.converter.addImageWatermark(com,Utils.drawNonAscii(watermark));
+ }
+ }
+}
diff --git a/src/main/java/com/lmc/shuiyin/one/util/Utils.java b/src/main/java/com/lmc/shuiyin/one/util/Utils.java
new file mode 100644
index 0000000..651c687
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/one/util/Utils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020 ww23(https://github.com/ww23/BlindWatermark).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.lmc.shuiyin.one.util;
+
+import org.apache.commons.lang.CharUtils;
+import org.opencv.core.Mat;
+import org.opencv.core.Scalar;
+import org.opencv.imgcodecs.Imgcodecs;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferByte;
+
+import static org.opencv.core.Core.*;
+import static org.opencv.core.CvType.CV_8U;
+import static org.opencv.highgui.HighGui.imshow;
+import static org.opencv.highgui.HighGui.waitKey;
+import static org.opencv.imgcodecs.Imgcodecs.imread;
+
+/**
+ * @author ww23
+ */
+public class Utils {
+
+ public static Mat read(String image, int type) {
+ Mat src = imread(image, type);
+ if (src.empty()) {
+ System.out.println("File not found!");
+ System.exit(-1);
+ }
+ return src;
+ }
+
+ public static void show(Mat mat) {
+ imshow(Utils.class.toString(), mat);
+ waitKey(0);
+ }
+
+ public static Mat optimalDft(Mat srcImg) {
+ Mat padded = new Mat();
+ int opRows = getOptimalDFTSize(srcImg.rows());
+ int opCols = getOptimalDFTSize(srcImg.cols());
+ copyMakeBorder(srcImg, padded, 0, opRows - srcImg.rows(),
+ 0, opCols - srcImg.cols(), BORDER_CONSTANT, Scalar.all(0));
+ return padded;
+ }
+
+ public static boolean isAscii(String str) {
+ for(char c : str.toCharArray()){
+ if(!CharUtils.isAscii(c)){
+ return false;
+ }
+ }
+ return true;
+ //return "^[ -~]+$".matches(str);
+ }
+
+ public static Mat drawNonAscii(String watermark) {
+ Font font = new Font("Default", Font.PLAIN, 64);
+ FontMetrics metrics = new Canvas().getFontMetrics(font);
+ int width = metrics.stringWidth(watermark);
+ int height = metrics.getHeight();
+ BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
+ Graphics2D graphics = bufferedImage.createGraphics();
+ graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
+ graphics.setFont(font);
+ graphics.setColor(Color.WHITE);
+ graphics.drawString(watermark, 0, metrics.getAscent());
+ graphics.dispose();
+ byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
+ Mat res = new Mat(bufferedImage.getHeight(), bufferedImage.getWidth(), CV_8U);
+ res.put(0, 0, pixels);
+ Imgcodecs.imwrite("C:\\tools\\shuiyin.png", res);
+ return res;
+ }
+
+ public static void fixSize(Mat src, Mat mirror) {
+ if (src.rows() != mirror.rows()) {
+ copyMakeBorder(src, src, 0, mirror.rows() - src.rows(),
+ 0, 0, BORDER_CONSTANT, Scalar.all(0));
+ }
+ if (src.cols() != mirror.cols()) {
+ copyMakeBorder(src, src, 0, 0,
+ 0, mirror.cols() - src.cols(), BORDER_CONSTANT, Scalar.all(0));
+ }
+ }
+
+}
diff --git a/src/main/java/com/lmc/shuiyin/two/utils/QRCodeUtils.java b/src/main/java/com/lmc/shuiyin/two/utils/QRCodeUtils.java
new file mode 100644
index 0000000..10fc28a
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/two/utils/QRCodeUtils.java
@@ -0,0 +1,139 @@
+package com.lmc.shuiyin.two.utils;
+
+import com.google.zxing.*;
+import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.CharacterSetECI;
+import com.google.zxing.common.HybridBinarizer;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+public class QRCodeUtils {
+
+ // 二维码的宽度
+ private int width;
+ // 二维码的高度
+ private int height;
+ // 二维码的格式
+ private String format;
+ // 二维码参数
+ private final Map paramMap;
+
+
+ public QRCodeUtils() {
+ // 1.默认尺寸
+ this.width = 100;
+ this.height = 100;
+ // 2.默认格式
+ this.format = "png";
+ // 3,默认参数
+ // 定义二维码的参数
+ this.paramMap = new HashMap<>();
+ // 设置二维码字符编码
+ paramMap.put(EncodeHintType.CHARACTER_SET, CharacterSetECI.UTF8);
+ // 设置二维码纠错等级
+ paramMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
+ // 设置二维码边距
+ paramMap.put(EncodeHintType.MARGIN, 1);
+
+ }
+
+ public void setFormat(String format) {
+ this.format = format;
+ }
+
+ public void setSize(int width, int heigth) {
+ this.width = width;
+ this.height = heigth;
+ }
+
+ public void setParam(EncodeHintType type, Object param) {
+ paramMap.put(type,param);
+ }
+
+ /**
+ * 生成二维码(写入文件)
+ *
+ * @param content 二维码内容
+ * @param outPutFile 二维码输出路径
+ */
+ public void QREncode(String content,String outPutFile) throws Exception {
+ // 开始生成二维码
+ MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
+ BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, width, height, paramMap);
+ // 导出到指定目录
+ Path path = new File(outPutFile).toPath();
+ MatrixToImageWriter.writeToPath(bitMatrix, format, path);
+ }
+
+
+ /**
+ * 生成二维码(内存中)
+ *
+ * @param contents 二维码的内容
+ * @return BufferedImage
+ */
+ public BufferedImage QREncode(String contents) throws Exception {
+ // 开始生成二维码
+ MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
+ BitMatrix bitMatrix = multiFormatWriter.encode(contents,BarcodeFormat.QR_CODE, width, height, paramMap);
+ // 写入内存
+ int width = bitMatrix.getWidth();
+ int height = bitMatrix.getHeight();
+ BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? 1 : 0);
+ }
+ }
+ return bufferedImage;
+ }
+
+ /**
+ * @param bufferedImage 二维码图片
+ * @return 文本内容
+ */
+ private Result getResult(BufferedImage bufferedImage) throws NotFoundException {
+ BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
+ HybridBinarizer binarizer = new HybridBinarizer(source);
+ BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
+ // 二维码参数
+ Map hints = new HashMap<>();
+ hints.put(EncodeHintType.CHARACTER_SET, CharacterSetECI.UTF8);
+ MultiFormatReader formatReader = new MultiFormatReader();
+ return formatReader.decode(binaryBitmap, hints);
+ }
+
+ /**
+ * 解析二维码
+ *
+ * @param filePath 待解析的二维码路径
+ * @return 二维码内容
+ */
+ public String QRReader(String filePath) throws Exception {
+ File file = new File(filePath);
+ // 读取指定的二维码文件
+ BufferedImage bufferedImage = ImageIO.read(file);
+ Result result = getResult(bufferedImage);
+ bufferedImage.flush();
+ return result.getText();
+ }
+
+ /**
+ * 解析二维码
+ *
+ * @return 二维码内容
+ */
+ public String QRReader(BufferedImage bufferedImage) throws Exception {
+ Result result = getResult(bufferedImage);
+ bufferedImage.flush();
+ return result.getText();
+ }
+}
diff --git a/src/main/java/com/lmc/shuiyin/two/utils/TwoMain.java b/src/main/java/com/lmc/shuiyin/two/utils/TwoMain.java
new file mode 100644
index 0000000..c73733f
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/two/utils/TwoMain.java
@@ -0,0 +1,18 @@
+package com.lmc.shuiyin.two.utils;
+
+
+import org.opencv.core.Core;
+
+public class TwoMain {
+ static{
+ //加载opencv动态库
+ System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
+ }
+ public static void main(String[] args){
+ WaterMarkDFT.embed("C:\\tools\\meinv.png","huafu","C:\\tools\\meinv2.png");//保存加过水印的图片
+ //读取图片水印
+ WaterMarkDFT.extract("C:\\tools\\meinv2.png", "C:\\tools\\meinv3.png");
+ //部分截图
+ WaterMarkDFT.extract("C:\\tools\\444.png", "C:\\tools\\555.png");
+ }
+}
diff --git a/src/main/java/com/lmc/shuiyin/two/utils/WaterMarkDCT.java b/src/main/java/com/lmc/shuiyin/two/utils/WaterMarkDCT.java
new file mode 100644
index 0000000..dd2ad1c
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/two/utils/WaterMarkDCT.java
@@ -0,0 +1,180 @@
+package com.lmc.shuiyin.two.utils;
+
+import org.opencv.core.Core;
+import org.opencv.core.CvType;
+import org.opencv.core.Mat;
+import org.opencv.imgcodecs.Imgcodecs;
+import org.opencv.imgproc.Imgproc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.opencv.imgcodecs.Imgcodecs.imread;
+import static org.opencv.imgcodecs.Imgcodecs.imwrite;
+
+/**
+ * 1、DCT分块大小为8*8
+ * 2、二值图大小为100*100
+ * 3、所以原图需要800*800尺寸以上(长或宽不能低于800)
+ */
+public class WaterMarkDCT {
+
+ /**
+ * 水印强度
+ */
+ private static final double P = 65;
+
+ /**
+ * @param imagePath 图片路径
+ * @param watermarkPath 水印路径
+ * @param outputPath 输出路径
+ */
+ public static void embed(String imagePath, String watermarkPath, String outputPath) {
+ // 读取原始图像
+ Mat originaleImage = imread(imagePath);
+ // 分离通道
+ List allPlanes = new ArrayList<>();
+ Core.split(originaleImage, allPlanes);
+ Mat YMat = allPlanes.get(0);
+ // 获取水印的二值矩阵
+ int[][] watermark = imageToMatrix(watermarkPath);
+ // 开始将水印嵌入
+ int length = 8; // DCT变换 分块的大小
+ for (int i = 0; i < watermark.length; i++) {
+ for (int j = 0; j < watermark[0].length; j++) {
+ // 提取每个分块
+ // block表示分块 而且为8*8的方阵
+ Mat block = getImageValue(YMat, i, j, length);
+
+ int x1 = 1, y1 = 2;
+ int x2 = 2, y2 = 1;
+
+ double[] a = block.get(x1, y1);
+ double[] c = block.get(x2, y2);
+ //对分块进行DCT变换
+ Core.dct(block, block);
+ a = block.get(x1, y1);
+ c = block.get(x2, y2);
+ if (watermark[i][j] == 1) {
+ block.put(x1, y1, P);
+ block.put(x2, y2, -P);
+ }
+ if (watermark[i][j] == 0) {
+ block.put(x1, y1, -P);
+ block.put(x2, y2, P);
+ }
+ //对上面分块进行IDCT变换
+ Core.idct(block, block);
+ for (int m = 0; m < length; m++) {
+ for (int t = 0; t < length; t++) {
+ double[] e = block.get(m, t);
+ YMat.put(i * length + m, j * length + t, e);
+ }
+ }
+ }
+
+ }
+ Mat imageOut = new Mat();
+ Core.merge(allPlanes, imageOut);
+ imwrite(outputPath,imageOut);
+ }
+
+ /**
+ * @param targetImage 带水印的目标图片
+ * @param outputWatermark 输出水印
+ */
+ public static void extract(String targetImage, String outputWatermark) {
+ Mat image = imread(targetImage);
+ List allPlanes = new ArrayList();
+ Core.split(image, allPlanes);
+ Mat YMat = allPlanes.get(0);
+ // 注意 rows和cols 应该与之前加的水印尺寸一致
+ int rows = 100;
+ int cols = 100;
+ int length = 8;
+ int[][] watermark = new int[rows][cols];
+ // 提取每块嵌入的水印信息
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ //提取每个分块,block表示分块 而且为8*8的方阵
+ Mat block = getImageValue(YMat, i, j, length);
+ //对分块进行DCT变换
+ Core.dct(block, block);
+ //用于容纳DCT系数
+ int x1 = 1, y1 = 2;
+ int x2 = 2, y2 = 1;
+ double[] a = block.get(x1, y1);
+ double[] c = block.get(x2, y2);
+ if (a[0] >= c[0]){
+ watermark[i][j] = 1;
+ }
+ }
+ }
+ matrixToImage(watermark,outputWatermark);
+ }
+
+ /**
+ * 提取每个分块
+ *
+ * @param YMat:原分块
+ * @param x:x与y联合表示第几个块
+ * @param y:x与y联合表示第几个块
+ * @param length:每个块的长度
+ * @return mat
+ */
+ private static Mat getImageValue(Mat YMat, int x, int y, int length) {
+ Mat mat = new Mat(length, length, CvType.CV_32F);
+ for (int i = 0; i < length; i++) {
+ for (int j = 0; j < length; j++) {
+ double[] temp = YMat.get(x * length + i, y * length + j);
+ mat.put(i, j, temp);
+ }
+ }
+ return mat;
+ }
+
+ /**
+ * 将二维数组转换为一张图片
+ *
+ * @param watermark 水印信息二维数组
+ * @param dstPath 图片路径
+ */
+ private static void matrixToImage(int[][] watermark, String dstPath) {
+ int rows = watermark.length;
+ int columns = watermark[0].length;
+ Mat image = new Mat(rows, columns, Imgproc.THRESH_BINARY);
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < columns; j++) {
+ if (watermark[i][j] == 1) {
+ image.put(i, j, 255);
+ } else {
+ image.put(i, j, 0);
+ }
+ }
+ }
+ Imgcodecs.imwrite(dstPath, image);
+ }
+
+
+ /**
+ * @param srcPath 水印图片(二维码)的路径
+ * @return 二维数组
+ */
+ private static int[][] imageToMatrix(String srcPath) {
+ Mat mat = imread(srcPath, Imgproc.THRESH_BINARY);
+ int rows = mat.rows();
+ int columns = mat.cols();
+ int[][] waterMark = new int[rows][columns];
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < columns; j++) {
+ double[] doubles = mat.get(i, j);
+ if ((int) doubles[0] == 255) {
+ waterMark[i][j] = 1;
+ } else {
+ waterMark[i][j] = 0;
+ }
+ }
+ }
+ return waterMark;
+ }
+}
diff --git a/src/main/java/com/lmc/shuiyin/two/utils/WaterMarkDFT.java b/src/main/java/com/lmc/shuiyin/two/utils/WaterMarkDFT.java
new file mode 100644
index 0000000..3029abf
--- /dev/null
+++ b/src/main/java/com/lmc/shuiyin/two/utils/WaterMarkDFT.java
@@ -0,0 +1,108 @@
+package com.lmc.shuiyin.two.utils;
+
+import org.opencv.core.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.opencv.core.Core.*;
+import static org.opencv.imgcodecs.Imgcodecs.imread;
+import static org.opencv.imgcodecs.Imgcodecs.imwrite;
+import static org.opencv.imgproc.Imgproc.FONT_HERSHEY_COMPLEX;
+import static org.opencv.imgproc.Imgproc.putText;
+
+public class WaterMarkDFT {
+ public static void embed(String imagePath, String watermark, String outputPath) {
+ Mat image = imread(imagePath);
+ // 1.优化图像的尺寸 加快处理速度
+// Mat padded = optimizeImageDimensions(originaleImage);
+ // 分离通道, allPlanes[0]
+ List allPlanes = new ArrayList<>();
+ Core.split(image, allPlanes);
+ image = allPlanes.get(0);
+ image.convertTo(image, CvType.CV_32F);
+ List planes = new ArrayList<>();
+ planes.add(image);
+ planes.add(Mat.zeros(image.size(), CvType.CV_32F));
+ Mat complexImage = new Mat();
+ Core.merge(planes, complexImage);
+ // 2.进行dft变换
+ Core.dft(complexImage, complexImage);
+ // 3.添加文本水印
+ Scalar scalar = new Scalar(2, 2, 2);
+ Point point = new Point(40, 40);
+ putText(complexImage, watermark, point, FONT_HERSHEY_COMPLEX, 1.5, scalar,3, 20);
+ flip(complexImage, complexImage, -1);
+ putText(complexImage, watermark, point, FONT_HERSHEY_COMPLEX, 1.5, scalar,3, 20);
+ flip(complexImage, complexImage, -1);
+ // 3.idft逆变换成图片
+ Mat invDFT = new Mat();
+ Core.idft(complexImage, invDFT, Core.DFT_SCALE | Core.DFT_REAL_OUTPUT, 0);
+ // 将得到的invDFT类型转换
+ invDFT.convertTo(invDFT, CvType.CV_8U);
+ // 将转换后的放入数组
+ allPlanes.set(0, invDFT);
+ // 将allPlanes数组合并成一个多通道的mat
+ Core.merge(allPlanes, invDFT);
+ // 4.保存图片
+ imwrite(outputPath, invDFT);
+ }
+
+ public static void extract(String targetImage, String outputWatermark) {
+ Mat image = imread(targetImage);
+ // 1.优化图像的尺寸 加快处理速度
+// Mat padded = optimizeImageDimensions(com.lmc.image);
+ // 2.分离通道, allPlanes[0]
+ List allPlanes = new ArrayList<>();
+ Core.split(image, allPlanes);
+ image = allPlanes.get(0);
+ image.convertTo(image, CvType.CV_32F);
+ // 3.Mat 数组,第一个为扩展后的图像,一个为空图像,
+ List planes = new ArrayList<>();
+ planes.add(image);
+ planes.add(Mat.zeros(image.size(), CvType.CV_32F));
+ // 将planes数组组合合并成一个多通道的数组complexImage
+ Mat complexImage = new Mat();
+ Core.merge(planes, complexImage);
+ // 4.进行dft
+ Core.dft(complexImage, complexImage);
+ // 5、进行对数尺度(logarithmic scale)缩放
+ List newPlanes = new ArrayList<>();
+ Mat mag = new Mat();
+ Core.split(complexImage, newPlanes);
+ Core.magnitude(newPlanes.get(0), newPlanes.get(1), mag);
+ Core.add(Mat.ones(mag.size(), CvType.CV_32F), mag, mag);
+ Core.log(mag, mag);
+
+ // 6、剪切和重分布图像限
+ mag = mag.submat(new Rect(0, 0, mag.cols() & -2, mag.rows() & -2));
+ int cx = mag.cols() / 2;
+ int cy = mag.rows() / 2;
+
+ Mat q0 = new Mat(mag, new Rect(0, 0, cx, cy));
+ Mat q1 = new Mat(mag, new Rect(cx, 0, cx, cy));
+ Mat q2 = new Mat(mag, new Rect(0, cy, cx, cy));
+ Mat q3 = new Mat(mag, new Rect(cx, cy, cx, cy));
+ Mat tmp = new Mat();
+ q0.copyTo(tmp);
+ q3.copyTo(q0);
+ tmp.copyTo(q3);
+ q1.copyTo(tmp);
+ q2.copyTo(q1);
+ tmp.copyTo(q2);
+ // 7、归一化,用0到1之间的浮点值将矩阵变换为可视的图像格式
+ mag.convertTo(mag, CvType.CV_8UC1);
+ Core.normalize(mag, mag, 0, 255, Core.NORM_MINMAX, CvType.CV_8UC1);
+ // 8.保存图片
+ imwrite(outputWatermark,mag);
+ }
+
+ private static Mat optimizeImageDimensions(Mat image) {
+ Mat padded = new Mat();
+ int addPixelRows = Core.getOptimalDFTSize(image.rows());
+ int addPixelCols = Core.getOptimalDFTSize(image.cols());
+ copyMakeBorder(image, padded, 0, addPixelRows - image.rows(),
+ 0, addPixelCols - image.cols(), BORDER_CONSTANT, Scalar.all(0));
+ return padded;
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..b8aac8b
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,23 @@
+# application
+spring.application.name=shuiyin
+# profile
+pring.main.allow-bean-definition-overriding=true
+bsf.web.exception.enabled=false
+bsf.web.response.enabled=false
+#֧
+bsf.web.cors3.enabled=true
+#֧cookieĿʹ
+bsf.web.cors3.cookie.enabled=true
+##### web.config####
+#admin=;admin,123;
+#bsf.configmanagerconnectstring=type=sqlserver;pooltype=druid;server=192.168.1.101:1433;Database=dyd_bs_configmanager;username=sa;password=12345;
+#####freemarker####
+#
+
+
+# server
+server.port=8082
+server.servlet.context-path=/
+# db
+spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
+spring.jackson.time-zone=GMT+8
diff --git a/src/main/resources/lib/opencv-460.jar b/src/main/resources/lib/opencv-460.jar
new file mode 100644
index 0000000000000000000000000000000000000000..0862f45845d5e1b9d80405f85c9fce26897a4a33
GIT binary patch
literal 724548
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