|
|
@@ -18,27 +18,27 @@ import org.bytedeco.opencv.opencv_core.*;
|
|
|
import static org.bytedeco.opencv.global.opencv_core.*;
|
|
|
|
|
|
import java.awt.image.BufferedImage;
|
|
|
-import java.io.File;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
-import java.nio.file.Files;
|
|
|
-import java.nio.file.Paths;
|
|
|
import java.util.HashMap;
|
|
|
-import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
|
|
+import static org.bytedeco.opencv.global.opencv_imgcodecs.imwrite;
|
|
|
import static org.bytedeco.opencv.global.opencv_imgproc.*;
|
|
|
|
|
|
/**
|
|
|
* 简单检测图片质量
|
|
|
*/
|
|
|
public class ImageQualityDetectorUtils {
|
|
|
+ private static boolean isDebug = false;
|
|
|
+ private static final String DEBUG_OUT_PATH = "/mnt/sdc/Users/hongchuangyanfa/Desktop/debug/";
|
|
|
|
|
|
// public static void main(String[] args) throws IOException {
|
|
|
// try (InputStream is = Files.newInputStream(Paths.get("C:\\Users\\泓创02\\Downloads\\test1.pdf"));) {
|
|
|
//// List<Map<String, Object>> mapList = evaluatePdfPages(is, 96);
|
|
|
//// System.out.println(mapList);
|
|
|
+// isDebug = true;
|
|
|
// Map<String, Object> map = checkImageQuality(is, 96);
|
|
|
// System.out.println(map);
|
|
|
// }
|
|
|
@@ -59,9 +59,9 @@ public class ImageQualityDetectorUtils {
|
|
|
if (dpi > 0 && !(checkDPI = engine.checkDpiForImages(dpi, 5))) {
|
|
|
break;
|
|
|
}
|
|
|
- if (engine.hasNonImagePainting) {
|
|
|
- break;
|
|
|
- }
|
|
|
+// if (engine.hasNonImagePainting) {
|
|
|
+// break;
|
|
|
+// }
|
|
|
for (BufferedImage bi : engine.images) {
|
|
|
Frame f = converter1.convert(bi);
|
|
|
Mat img = converter2.convert(f);
|
|
|
@@ -69,7 +69,12 @@ public class ImageQualityDetectorUtils {
|
|
|
result.put("message", "无法加载图像");
|
|
|
return result;
|
|
|
}
|
|
|
- boolean stains = detectLargeStains(img);
|
|
|
+ boolean stains;
|
|
|
+ if (isDebug) {
|
|
|
+ stains = detectLargeStainsOfDebug(img);
|
|
|
+ } else {
|
|
|
+ stains = detectLargeStains(img);
|
|
|
+ }
|
|
|
// boolean blurry = isBlurry(img);
|
|
|
// boolean shadows = detectShadows(img);
|
|
|
// boolean exposureBad = isExposureBad(img);
|
|
|
@@ -298,6 +303,10 @@ public class ImageQualityDetectorUtils {
|
|
|
}
|
|
|
long dpiX = Math.round(imageWidth / renderedWidthInInches);
|
|
|
long dpiY = Math.round(imageHeight / renderedHeightInInches);
|
|
|
+ if (isDebug) {
|
|
|
+ System.out.printf("图片DPI: %d x %d, 渲染尺寸: %.2f\" x %.2f\"%n",
|
|
|
+ dpiX, dpiY, renderedWidthInInches, renderedHeightInInches);
|
|
|
+ }
|
|
|
if (dpiX < requiredDpi - tolerance || dpiY < requiredDpi - tolerance) {
|
|
|
allOk.set(false);
|
|
|
}
|
|
|
@@ -429,12 +438,87 @@ public class ImageQualityDetectorUtils {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 检查轮廓是否为规则形状( 基于凸包的检测)
|
|
|
+ * @param contour 轮廓
|
|
|
+ * @return 是否为规则形状
|
|
|
+ */
|
|
|
private static boolean isRegularShape(Mat contour) {
|
|
|
// 检查轮廓是否为规则形状
|
|
|
double perimeter = arcLength(contour, true);
|
|
|
+ Mat approx = new Mat();
|
|
|
+ // todo 基于perimeter长度决定 scaleFactor 的大小, epsilon 越小,则越精确
|
|
|
+ double scaleFactor = 0.015;
|
|
|
+ approxPolyDP(contour, approx, scaleFactor * perimeter, true);
|
|
|
+
|
|
|
+ // 计算凸包
|
|
|
+ Mat hull = new Mat();
|
|
|
+ convexHull(contour, hull);
|
|
|
+ double hullArea = contourArea(hull);
|
|
|
+ double contourArea = contourArea(contour);
|
|
|
+
|
|
|
+ // 计算凸度(solidity)
|
|
|
+ double solidity = contourArea / hullArea;
|
|
|
+
|
|
|
+ // 计算边界矩形相关特征
|
|
|
+ Rect boundingRect = boundingRect(contour);
|
|
|
+ double rectArea = boundingRect.width() * boundingRect.height();
|
|
|
+ double extent = contourArea / rectArea;
|
|
|
+
|
|
|
+ if (isDebug) {
|
|
|
+ System.out.println("Approx points: " + approx.rows());
|
|
|
+ System.out.println("Solidity: " + solidity);
|
|
|
+ System.out.println("Extent: " + extent);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更严格的规则形状判定
|
|
|
+ boolean simplePolygon = approx.rows() <= 4;
|
|
|
+ boolean highSolidity = solidity > 0.8; // 凸度高表示形状规整
|
|
|
+ boolean highExtent = extent > 0.7; // 与边界矩形重合度高
|
|
|
+
|
|
|
+ return simplePolygon || (highSolidity && highExtent);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查轮廓是否为规则形状(结合多种几何特征)
|
|
|
+ * @param contour 轮廓
|
|
|
+ * @return 是否为规则形状
|
|
|
+ */
|
|
|
+ private static boolean isRegularShape1(Mat contour) {
|
|
|
+ // 检查轮廓是否为规则形状
|
|
|
+ double perimeter = arcLength(contour, true);
|
|
|
+ if (perimeter == 0) return false;
|
|
|
+
|
|
|
Mat approx = new Mat();
|
|
|
approxPolyDP(contour, approx, 0.02 * perimeter, true);
|
|
|
- return approx.rows() <= 4; // 简单多边形可能是背景
|
|
|
+
|
|
|
+ // 计算面积和边界矩形
|
|
|
+ double area = contourArea(contour);
|
|
|
+ Rect boundingRect = boundingRect(contour);
|
|
|
+ double rectArea = boundingRect.width() * boundingRect.height();
|
|
|
+ double areaRatio = area / rectArea;
|
|
|
+
|
|
|
+ // 计算长宽比
|
|
|
+ double aspectRatio = (double) boundingRect.width() / boundingRect.height();
|
|
|
+ if (aspectRatio < 1) aspectRatio = 1 / aspectRatio;
|
|
|
+
|
|
|
+ // 检查是否接近圆形(通过周长和面积的关系)
|
|
|
+ double circularity = 4 * Math.PI * area / (perimeter * perimeter);
|
|
|
+
|
|
|
+ if (isDebug) {
|
|
|
+ System.out.println("Approx points: " + approx.rows());
|
|
|
+ System.out.println("Area ratio: " + areaRatio);
|
|
|
+ System.out.println("Aspect ratio: " + aspectRatio);
|
|
|
+ System.out.println("Circularity: " + circularity);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 综合判断:顶点少、面积比高、长宽比合理、接近圆形之一即可判定为规则形状
|
|
|
+ boolean isPolygon = approx.rows() <= 6;
|
|
|
+ boolean isRectangular = areaRatio > 0.7;
|
|
|
+ boolean isProportional = aspectRatio < 3.0;
|
|
|
+ boolean isCircular = circularity > 0.7;
|
|
|
+
|
|
|
+ return isPolygon || isRectangular || isCircular || isProportional;
|
|
|
}
|
|
|
|
|
|
private static boolean hasSignificantColorVariation(Mat image, Mat contour) {
|
|
|
@@ -449,9 +533,20 @@ public class ImageQualityDetectorUtils {
|
|
|
|
|
|
// 获取标准差值
|
|
|
double stdDev0 = stdDev.ptr(0).get(0);
|
|
|
+ if (stdDev0 < 0) {
|
|
|
+ stdDev0 = -stdDev0;
|
|
|
+ }
|
|
|
double stdDev1 = stdDev.ptr(1).get(0);
|
|
|
+ if (stdDev1 < 0) {
|
|
|
+ stdDev1 = -stdDev1;
|
|
|
+ }
|
|
|
double stdDev2 = stdDev.ptr(2).get(0);
|
|
|
-
|
|
|
+ if (stdDev2 < 0) {
|
|
|
+ stdDev2 = -stdDev2;
|
|
|
+ }
|
|
|
+ if (isDebug) {
|
|
|
+ System.out.println("Color std dev: H=" + stdDev0 + ", S=" + stdDev1 + ", V=" + stdDev2);
|
|
|
+ }
|
|
|
// 如果颜色变化较小,则可能是统一的背景色
|
|
|
return stdDev0 > 15 || stdDev1 > 15 || stdDev2 > 15;
|
|
|
|
|
|
@@ -539,4 +634,76 @@ public class ImageQualityDetectorUtils {
|
|
|
return shadowRatio > 0.1;
|
|
|
}
|
|
|
|
|
|
+ public static boolean detectLargeStainsOfDebug(Mat image) {
|
|
|
+ // 创建用于调试的图像副本
|
|
|
+ Mat debugImage = image.clone();
|
|
|
+
|
|
|
+ // 转换为灰度图
|
|
|
+ Mat gray = new Mat();
|
|
|
+ cvtColor(image, gray, COLOR_BGR2GRAY);
|
|
|
+
|
|
|
+ // 应用阈值处理,突出污渍区域
|
|
|
+ Mat thresh = new Mat();
|
|
|
+ threshold(gray, thresh, 0, 255, THRESH_BINARY_INV + THRESH_OTSU);
|
|
|
+
|
|
|
+ // 形态学操作去除噪声
|
|
|
+ Mat kernel = getStructuringElement(MORPH_ELLIPSE, new Size(5, 5));
|
|
|
+ morphologyEx(thresh, thresh, MORPH_OPEN, kernel);
|
|
|
+
|
|
|
+ // 查找轮廓
|
|
|
+ MatVector contours = new MatVector();
|
|
|
+ findContours(thresh, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
|
|
|
+
|
|
|
+ // 检查是否有大面积的污渍
|
|
|
+ double imageArea = image.rows() * image.cols();
|
|
|
+ boolean foundStain = false;
|
|
|
+
|
|
|
+ for (int i = 0; i < contours.size(); i++) {
|
|
|
+ Mat contour = contours.get(i);
|
|
|
+ double area = contourArea(contour);
|
|
|
+
|
|
|
+ // 在调试图像上绘制所有检测到的轮廓
|
|
|
+ Scalar color = new Scalar(0, 255, 0, 0); // 默认绿色
|
|
|
+ if (area > imageArea * 0.05) {
|
|
|
+ color = new Scalar(255, 0, 0, 0); // 大面积区域用蓝色标记
|
|
|
+ }
|
|
|
+ drawContours(debugImage, new MatVector(contour), -1, color, 2, LINE_8, null, 0, null);
|
|
|
+
|
|
|
+ // 如果污渍面积超过图像面积的5%
|
|
|
+ if (area > imageArea * 0.05) {
|
|
|
+ // 增加额外条件:排除规则形状(可能是背景)
|
|
|
+ boolean regularShape = isRegularShape(contour);
|
|
|
+ boolean significantColorVar = hasSignificantColorVariation(image, contour);
|
|
|
+
|
|
|
+ // 在图像上添加文本标注
|
|
|
+ Point textPoint = new Point(10, 30 + i*20);
|
|
|
+ String label = String.format("Area:%.0f(%.1f%%) Reg:%b Col:%b",
|
|
|
+ area, (area/imageArea)*100, regularShape, significantColorVar);
|
|
|
+ putText(debugImage, label, textPoint, FONT_HERSHEY_SIMPLEX, 0.5,
|
|
|
+ new Scalar(0, 0, 255, 0), 1, LINE_AA, false);
|
|
|
+
|
|
|
+ if (!regularShape && significantColorVar) {
|
|
|
+ foundStain = true;
|
|
|
+ // 用红色标记最终判定为污渍的区域
|
|
|
+ drawContours(debugImage, new MatVector(contour), -1, new Scalar(0, 0, 255, 0), 3, LINE_8, null, 0, null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存调试图像
|
|
|
+ saveDebugImage(debugImage, DEBUG_OUT_PATH + System.nanoTime() + ".jpg");
|
|
|
+
|
|
|
+ return foundStain;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加辅助方法保存调试图像
|
|
|
+ private static void saveDebugImage(Mat image, String filename) {
|
|
|
+ try {
|
|
|
+ // 保存图像到文件
|
|
|
+ imwrite(filename, image);
|
|
|
+ System.out.println("Debug image saved: " + filename);
|
|
|
+ } catch (Exception e) {
|
|
|
+ System.err.println("Failed to save debug image: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|