Преглед на файлове

优化档案pdf文件检测方法

lvy преди 6 дни
родител
ревизия
49cc0f19e2
променени са 1 файла, в които са добавени 177 реда и са изтрити 10 реда
  1. 177 10
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/ImageQualityDetectorUtils.java

+ 177 - 10
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/ImageQualityDetectorUtils.java

@@ -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());
+        }
+    }
 }