Procházet zdrojové kódy

优化档案文件检测功能

lvy před 6 dny
rodič
revize
98e121d93c

+ 10 - 5
blade-service/blade-archive/pom.xml

@@ -23,7 +23,7 @@
     </repositories>
     <properties>
         <javacv.version>1.5.8</javacv.version>
-        <javacv.platform>linux-x86_64</javacv.platform>
+        <javacv.platform>windows-x86_64</javacv.platform>
     </properties>
 
     <dependencies>
@@ -31,13 +31,18 @@
             <groupId>org.bytedeco</groupId>
             <artifactId>javacv</artifactId>
             <version>${javacv.version}</version>
-            <!--            <classifier>${javacv.platform}</classifier>-->
         </dependency>
         <dependency>
             <groupId>org.bytedeco</groupId>
-            <artifactId>javacpp</artifactId>
-            <version>${javacv.version}</version>
-            <!--            <classifier>${javacv.platform}</classifier>-->
+            <artifactId>opencv</artifactId>
+            <version>4.6.0-${javacv.version}</version>
+            <classifier>windows-x86_64</classifier>
+        </dependency>
+        <dependency>
+            <groupId>org.bytedeco</groupId>
+            <artifactId>openblas</artifactId>
+            <version>0.3.21-${javacv.version}</version>
+            <classifier>windows-x86_64</classifier>
         </dependency>
         <!--        iText生成中文-->
         <dependency>

+ 23 - 23
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchivesAutoServiceImpl.java

@@ -39,9 +39,15 @@ import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
 
+import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.io.MemoryUsageSetting;
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.graphics.PDXObject;
+import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
+import org.bytedeco.javacv.Java2DFrameConverter;
+import org.bytedeco.javacv.OpenCVFrameConverter;
 import org.springblade.archive.dto.*;
 import org.springblade.archive.entity.*;
 import org.springblade.archive.mapper.ArchiveConclusionMapper;
@@ -5570,12 +5576,13 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 	private void checkArchiveFile(List<ArchiveFile> list) {
 		Pattern datePattern = Pattern.compile("[0-9]{4}.?[0-9]{2}.?[0-9]{2}");
 		for (ArchiveFile file : list) {
-			String status = "";
 			if (file.getFileTime() == null || !datePattern.matcher(file.getFileTime()).find()) {
-				status += "1";
+				file.setCheckStatus(1);
+				continue;
 			}
 			if (file.getDutyUser() ==  null || file.getDutyUser().trim().isEmpty() || file.getDutyUser().trim().equals("null")) {
-				status += "2";
+				file.setCheckStatus(2);
+				continue;
 			}
 			String url = "";
 			if (file.getPdfFileUrl() != null && file.getPdfFileUrl().contains("http")) {
@@ -5584,11 +5591,7 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 				url = file.getPdfFileUrl();
 			}
 			if (url == null || !url.endsWith(".pdf")) {
-				status += "3";
-				file.setCheckStatus(Integer.parseInt(status));
-				continue;
-			}
-			if (!status.isEmpty()) {
+				file.setCheckStatus(3);
 				continue;
 			}
 			int lastIndexOf = url.lastIndexOf("/");
@@ -5596,25 +5599,22 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 			String suffix = url.substring(lastIndexOf + 1);
 			try {
 				url = prefix + URLEncoder.encode(suffix, "UTF-8");
-				// 使用内存限制设置 10MB 主内存
-				MemoryUsageSetting memUsage = MemoryUsageSetting.setupMixed(10_000_000);
-				try (InputStream ossIs = CommonUtil.getOSSInputStream(url); PDDocument document = PDDocument.load(ossIs, memUsage);) {
-					PDPage page = document.getPage(0);
-					// 获取pdf 的dpi信息
-					int dpi = (int) (page.getCropBox().getWidth() / page.getTrimBox().getWidth() * 72);
-					if (dpi < 300) {
-						status = "";
-					}
-					// todo 检测pdf是否有遮挡或者污渍
-					if (status.isEmpty()) {
-						file.setCheckStatus(-1);
+				try (InputStream ossIs = CommonUtil.getOSSInputStream(url); ) {
+					Map<String, Object> map = ImageQualityDetectorUtils.checkImageQuality(ossIs, 96);
+					Object checkDPI = map.get("checkDPI");
+					if (checkDPI != null && !"true".equals(checkDPI.toString())) {
+						file.setCheckStatus(3);
 					} else {
-						file.setCheckStatus(Integer.parseInt( status));
+						Object hasStains = map.get("hasStains");
+						if (hasStains != null && "true".equals(hasStains.toString())) {
+							file.setCheckStatus(4);
+						} else {
+							file.setCheckStatus(-1);
+						}
 					}
 				}
 			} catch (Exception e) {
-				status += "3";
-				file.setCheckStatus(Integer.parseInt(status));
+				file.setCheckStatus(3);
 			}
 		}
 	}

+ 401 - 42
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/ImageQualityDetectorUtils.java

@@ -1,24 +1,33 @@
 package org.springblade.archive.utils;
 
+import org.apache.pdfbox.contentstream.PDFGraphicsStreamEngine;
+import org.apache.pdfbox.io.MemoryUsageSetting;
 import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.graphics.image.PDImage;
+import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
 import org.apache.pdfbox.rendering.PDFRenderer;
-import org.bytedeco.javacpp.BytePointer;
+import org.apache.pdfbox.text.PDFTextStripper;
+import org.apache.pdfbox.util.Matrix;
 import org.bytedeco.javacv.Frame;
 import org.bytedeco.javacv.Java2DFrameConverter;
 import org.bytedeco.javacv.OpenCVFrameConverter;
+import org.bytedeco.opencv.global.opencv_imgproc;
 import org.bytedeco.opencv.opencv_core.*;
-import org.bytedeco.opencv.opencv_imgproc.*;
+
 import static org.bytedeco.opencv.global.opencv_core.*;
-import org.bytedeco.opencv.global.opencv_imgcodecs;
-import org.opencv.core.CvType;
-import org.springblade.common.utils.CommonUtil;
 
 import java.awt.image.BufferedImage;
-import java.io.ByteArrayOutputStream;
 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.*;
 import static org.bytedeco.opencv.global.opencv_imgproc.*;
 
 /**
@@ -27,42 +36,360 @@ import static org.bytedeco.opencv.global.opencv_imgproc.*;
 public class ImageQualityDetectorUtils {
 
 //    public static void main(String[] args) throws IOException {
-//        try (PDDocument document = PDDocument.load(new File("C:\\Users\\泓创02\\Downloads\\73ef17c2f24ea5b83747a1ac5c2c3f0f.pdf"));
-//             Java2DFrameConverter converter1 = new Java2DFrameConverter();
-//             OpenCVFrameConverter.ToMat converter2 = new OpenCVFrameConverter.ToMat();) {
-//            if (document.isEncrypted()) {
-//                throw new RuntimeException("PDF文件已加密,请先解密");
-//            }
-//            // 创建PDF渲染器
-//            PDFRenderer pdfRenderer = new PDFRenderer(document);
-//            // 获取总页数
-//            int pages = document.getNumberOfPages();
-//            for (int i = 0; i < pages; i++) {
-//                Frame frame = converter1.convert(pdfRenderer.renderImageWithDPI(i, 300));
-//                Mat image = converter2.convert(frame);
-//                if (image.empty()) {
-//                    System.out.println("无法加载图像");
-//                    return;
-//                }
-//                // 检测各种质量问题
-//                boolean hasStains = detectLargeStains(image);
-//                boolean isObstructed = detectTextObstruction(image);
-//                boolean hasShadows = detectShadows(image);
-//
-//                System.out.println("检测结果:");
-//                System.out.println("大面积污渍: " + (hasStains ? "是" : "否"));
-//                System.out.println("文字遮挡: " + (isObstructed ? "是" : "否"));
-//                System.out.println("黑影/阴影: " + (hasShadows ? "是" : "否"));
-//
-//                if (hasStains || isObstructed || hasShadows) {
-//                    System.out.println("图像质量不佳,建议重新扫描");
-//                } else {
-//                    System.out.println("图像质量良好");
-//                }
-//            }
+//        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);
+//            Map<String, Object> map = checkImageQuality(is, 96);
+//            System.out.println(map);
 //        }
 //    }
 
+    public static Map<String, Object> checkImageQuality(InputStream is, int dpi) throws IOException {
+        Map<String, Object> result = new HashMap<>();
+        MemoryUsageSetting memUsage = MemoryUsageSetting.setupMixed(10_000_000);
+        try (PDDocument document = PDDocument.load(is, memUsage);
+             Java2DFrameConverter converter1 = new Java2DFrameConverter();
+             OpenCVFrameConverter.ToMat converter2 = new OpenCVFrameConverter.ToMat()) {
+            boolean checkDPI = true;
+            int pages = document.getNumberOfPages();
+            for (int i = 0; i < pages; i++) {
+                PDPage page = document.getPage(i);
+                PageImageCollectorEngine engine = new PageImageCollectorEngine(page);
+                engine.processPage(page);
+                if (dpi > 0 && !(checkDPI = engine.checkDpiForImages(dpi, 5))) {
+                    break;
+                }
+                if (engine.hasNonImagePainting) {
+                    break;
+                }
+                for (BufferedImage bi : engine.images) {
+                    Frame f = converter1.convert(bi);
+                    Mat img = converter2.convert(f);
+                    if (img == null || img.empty()) {
+                        result.put("message", "无法加载图像");
+                        return result;
+                    }
+                    boolean stains = detectLargeStains(img);
+//                    boolean blurry = isBlurry(img);
+//                    boolean shadows = detectShadows(img);
+//                    boolean exposureBad = isExposureBad(img);
+                    if (stains) {
+                        result.put("hasStains", true);
+                        result.put("page", i + 1);
+                        return result;
+                    }
+//                    if (blurry) {
+//                        result.put("isBlurry", true);
+//                        result.put("page", i + 1);
+//                        return result;
+//                    }
+//                    if (shadows) {
+//                        result.put("hasShadows", true);
+//                        result.put("page", i + 1);
+//                        return result;
+//                    }
+//                    if (exposureBad) {
+//                        result.put("badExposure", true);
+//                        result.put("page", i + 1);
+//                        return result;
+//                    }
+                }
+            }
+            if (!checkDPI) {
+                result.put("checkDPI", false);
+            }
+            return result;
+        }
+    }
+
+    public static java.util.List<Map<String, Object>> evaluatePdfPages(InputStream is, int dpi) throws IOException {
+        java.util.List<Map<String, Object>> list = new java.util.ArrayList<>();
+        MemoryUsageSetting memUsage = MemoryUsageSetting.setupMixed(10_000_000);
+        try (PDDocument document = PDDocument.load(is, memUsage);
+             Java2DFrameConverter converter1 = new Java2DFrameConverter();
+             OpenCVFrameConverter.ToMat converter2 = new OpenCVFrameConverter.ToMat()) {
+            PDFRenderer renderer = new PDFRenderer(document);
+            PDFTextStripper stripper = new PDFTextStripper();
+            int pages = document.getNumberOfPages();
+            for (int i = 0; i < pages; i++) {
+                PDPage page = document.getPage(i);
+                PageImageCollectorEngine engine = new PageImageCollectorEngine(page);
+                engine.processPage(page);
+                boolean dpiOk = dpi <= 0 || engine.checkDpiForImages(dpi, 5);
+                boolean isImageOnlyPage = !engine.hasNonImagePainting;
+                boolean hasText = false;
+                stripper.setStartPage(i + 1);
+                stripper.setEndPage(i + 1);
+                String text = stripper.getText(document);
+                if (text != null && !text.trim().isEmpty()) {
+                    hasText = true;
+                }
+
+                double imageBlurAvg = -1.0;
+                boolean badExposure = false;
+                boolean hasShadows = false;
+                boolean hasStains = false;
+                if (!engine.images.isEmpty()) {
+                    double sum = 0.0;
+                    int cnt = 0;
+                    for (BufferedImage bi : engine.images) {
+                        Frame f = converter1.convert(bi);
+                        Mat img = converter2.convert(f);
+                        if (img != null && !img.empty()) {
+                            double bs = blurScore(img);
+                            sum += bs;
+                            cnt++;
+                            if (!badExposure) badExposure = isExposureBad(img);
+                            if (!hasShadows) hasShadows = detectShadows(img);
+                            if (!hasStains) hasStains = detectLargeStains(img);
+                        }
+                    }
+                    if (cnt > 0) {
+                        imageBlurAvg = sum / cnt;
+                    }
+                }
+
+                double textBlurScore = -1.0;
+                if (!isImageOnlyPage || hasText) {
+                    BufferedImage pageImg = renderer.renderImageWithDPI(i, 300);
+                    Frame pf = converter1.convert(pageImg);
+                    Mat pmat = converter2.convert(pf);
+                    if (pmat != null && !pmat.empty()) {
+                        textBlurScore = blurScore(pmat);
+                    }
+                }
+
+                double score = 100.0;
+                if (!dpiOk) {
+                    score -= 20.0;
+                }
+                if (imageBlurAvg >= 0) {
+                    score = Math.min(score, imageBlurAvg);
+                }
+                if (textBlurScore >= 0) {
+                    score = Math.min(score, textBlurScore);
+                }
+                if (badExposure) {
+                    score -= 15.0;
+                }
+                if (hasShadows) {
+                    score -= 10.0;
+                }
+                if (hasStains) {
+                    score -= 10.0;
+                }
+                if (score < 0) score = 0;
+                if (score > 100) score = 100;
+
+                Map<String, Object> pageResult = new HashMap<>();
+                pageResult.put("page", i + 1);
+                pageResult.put("score", Math.round(score));
+                pageResult.put("isImageOnlyPage", isImageOnlyPage);
+                pageResult.put("hasText", hasText);
+                pageResult.put("checkDPI", dpiOk);
+                if (imageBlurAvg >= 0) pageResult.put("imageBlurScore", Math.round(imageBlurAvg));
+                if (textBlurScore >= 0) pageResult.put("textBlurScore", Math.round(textBlurScore));
+                pageResult.put("badExposure", badExposure);
+                pageResult.put("hasShadows", hasShadows);
+                pageResult.put("hasStains", hasStains);
+
+                list.add(pageResult);
+            }
+        }
+        return list;
+    }
+
+
+    private static class PageImageCollectorEngine extends PDFGraphicsStreamEngine {
+        final PDPage page;
+        final java.util.List<BufferedImage> images = new java.util.ArrayList<>();
+        boolean hasNonImagePainting = false;
+
+        protected PageImageCollectorEngine(PDPage page) {
+            super(page);
+            this.page = page;
+        }
+
+        @Override
+        public void drawImage(PDImage pdImage) throws IOException {
+            Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
+            if (pdImage instanceof PDImageXObject) {
+                BufferedImage bi = ((PDImageXObject) pdImage).getImage();
+                if (bi != null) {
+                    images.add(bi);
+                }
+            }
+        }
+
+        @Override
+        public void appendRectangle(java.awt.geom.Point2D p0, java.awt.geom.Point2D p1, java.awt.geom.Point2D p2, java.awt.geom.Point2D p3) throws IOException {
+            hasNonImagePainting = true;
+        }
+
+        @Override
+        public void clip(int windingRule) throws IOException {
+            hasNonImagePainting = true;
+        }
+
+        @Override
+        public void moveTo(float x, float y) throws IOException {
+            hasNonImagePainting = true;
+        }
+
+        @Override
+        public void lineTo(float x, float y) throws IOException {
+            hasNonImagePainting = true;
+        }
+
+        @Override
+        public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException {
+            hasNonImagePainting = true;
+        }
+
+        @Override
+        public java.awt.geom.Point2D getCurrentPoint() throws IOException {
+            return new java.awt.geom.Point2D.Float(0, 0);
+        }
+
+        @Override
+        public void closePath() throws IOException {
+            hasNonImagePainting = true;
+        }
+
+        @Override
+        public void endPath() throws IOException {
+            hasNonImagePainting = true;
+        }
+
+        @Override
+        public void strokePath() throws IOException {
+            hasNonImagePainting = true;
+        }
+
+        @Override
+        public void fillPath(int windingRule) throws IOException {
+            hasNonImagePainting = true;
+        }
+
+        @Override
+        public void fillAndStrokePath(int windingRule) throws IOException {
+            hasNonImagePainting = true;
+        }
+
+        @Override
+        public void shadingFill(org.apache.pdfbox.cos.COSName shadingName) throws IOException {
+            hasNonImagePainting = true;
+        }
+
+        boolean checkDpiForImages(int requiredDpi, int tolerance) throws IOException {
+            final AtomicBoolean allOk = new AtomicBoolean(true);
+            PDFGraphicsStreamEngine dpiEngine = new PDFGraphicsStreamEngine(page) {
+                @Override
+                public void drawImage(PDImage pdImage) throws IOException {
+                    Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
+                    int imageWidth = pdImage.getWidth();
+                    int imageHeight = pdImage.getHeight();
+                    float renderedWidthInPoints = ctm.getScalingFactorX();
+                    float renderedHeightInPoints = ctm.getScalingFactorY();
+                    float renderedWidthInInches = renderedWidthInPoints / 72f;
+                    float renderedHeightInInches = renderedHeightInPoints / 72f;
+                    if (renderedWidthInInches == 0 || renderedHeightInInches == 0) {
+                        return;
+                    }
+                    long dpiX = Math.round(imageWidth / renderedWidthInInches);
+                    long dpiY = Math.round(imageHeight / renderedHeightInInches);
+                    if (dpiX < requiredDpi - tolerance || dpiY < requiredDpi - tolerance) {
+                        allOk.set(false);
+                    }
+                }
+                @Override public void appendRectangle(java.awt.geom.Point2D p0, java.awt.geom.Point2D p1, java.awt.geom.Point2D p2, java.awt.geom.Point2D p3) throws IOException {}
+                @Override public void clip(int windingRule) throws IOException {}
+                @Override public void moveTo(float x, float y) throws IOException {}
+                @Override public void lineTo(float x, float y) throws IOException {}
+                @Override public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException {}
+                @Override public java.awt.geom.Point2D getCurrentPoint() throws IOException { return new java.awt.geom.Point2D.Float(0, 0); }
+                @Override public void closePath() throws IOException {}
+                @Override public void endPath() throws IOException {}
+                @Override public void strokePath() throws IOException {}
+                @Override public void fillPath(int windingRule) throws IOException {}
+                @Override public void fillAndStrokePath(int windingRule) throws IOException {}
+                @Override public void shadingFill(org.apache.pdfbox.cos.COSName shadingName) throws IOException {}
+            };
+            dpiEngine.processPage(page);
+            return allOk.get();
+        }
+    }
+
+    /**
+     * 检测图像的模糊质量
+     * @param image 输入图像
+     * @return 模糊质量检测结果
+     */
+    public static boolean isBlurry(Mat image) {
+        Mat gray = new Mat();
+        cvtColor(image, gray, COLOR_BGR2GRAY);
+        Mat lap = new Mat();
+        Laplacian(gray, lap, CV_16S);
+        Mat lapAbs = new Mat();
+        convertScaleAbs(lap, lapAbs);
+        Scalar m = mean(lapAbs);
+        double val = m.get(0);
+        return val < 10.0;
+    }
+
+    /**
+     * 检测图像的曝光质量
+     * @param image 输入图像
+     * @return 曝光质量检测结果
+     */
+    public static boolean isExposureBad(Mat image) {
+        Mat hsv = new Mat();
+        cvtColor(image, hsv, COLOR_BGR2HSV);
+        Mat[] channels = new Mat[3];
+        channels[0] = new Mat();
+        channels[1] = new Mat();
+        channels[2] = new Mat();
+        split(hsv, new MatVector(channels));
+        Mat v = channels[2];
+        Mat low = new Mat();
+        Mat high = new Mat();
+        threshold(v, low, 30, 255, THRESH_BINARY_INV);
+        threshold(v, high, 220, 255, THRESH_BINARY);
+        double total = v.rows() * v.cols();
+        double lowRatio = countNonZero(low) / total;
+        double highRatio = countNonZero(high) / total;
+        return lowRatio > 0.25 || highRatio > 0.25;
+    }
+
+    private static double blurScore(Mat image) {
+        Mat gray = new Mat();
+        // 在调用cvtColor之前,先检查并转换图像通道数
+        cvtColor(image, gray, COLOR_BGR2GRAY);
+        Mat lap = new Mat();
+        Laplacian(gray, lap, CV_16S);
+        Mat lapAbs = new Mat();
+        convertScaleAbs(lap, lapAbs);
+        Scalar m = mean(lapAbs);
+        double val = m.get(0);
+        double score = (val - 5.0) * (100.0 / (25.0 - 5.0));
+        if (score < 0) score = 0;
+        if (score > 100) score = 100;
+        return score;
+    }
+
+    private static void cvtColor(Mat image, Mat gray, int conversionCode) {
+        if (image.channels() == 1) {
+            // 将单通道图像转换为三通道BGR图像
+            Mat multiChannelImage = new Mat();
+            opencv_imgproc.cvtColor(image, multiChannelImage, opencv_imgproc.COLOR_GRAY2BGR);
+            // 使用转换后的图像进行后续处理
+            opencv_imgproc.cvtColor(multiChannelImage, gray, conversionCode);
+        } else {
+            // 直接使用原图像
+            opencv_imgproc.cvtColor(image, gray, conversionCode);
+        }
+    }
+
     /**
      * 检测图像中是否存在大面积污渍
      * @param image 输入图像
@@ -88,16 +415,48 @@ public class ImageQualityDetectorUtils {
         // 检查是否有大面积的污渍
         double imageArea = image.rows() * image.cols();
         for (int i = 0; i < contours.size(); i++) {
-            double area = contourArea(contours.get(i));
+            Mat contour = contours.get(i);
+            double area = contourArea(contour);
             // 如果污渍面积超过图像面积的5%,则认为存在大面积污渍
             if (area > imageArea * 0.05) {
-                return true;
+                // 增加额外条件:排除规则形状(可能是背景)
+                if (!isRegularShape(contour) && hasSignificantColorVariation(image, contour)) {
+                    return true;
+                }
             }
         }
 
         return false;
     }
 
+    private static boolean isRegularShape(Mat contour) {
+        // 检查轮廓是否为规则形状
+        double perimeter = arcLength(contour, true);
+        Mat approx = new Mat();
+        approxPolyDP(contour, approx, 0.02 * perimeter, true);
+        return approx.rows() <= 4; // 简单多边形可能是背景
+    }
+
+    private static boolean hasSignificantColorVariation(Mat image, Mat contour) {
+        // 检查区域内是否有显著的颜色变化
+        Rect boundingRect = boundingRect(contour);
+        Mat roi = new Mat(image, boundingRect);
+        Mat hsv = new Mat();
+        cvtColor(roi, hsv, COLOR_BGR2HSV);
+
+        Mat stdDev = new Mat();
+        meanStdDev(hsv, new Mat(), stdDev);
+
+        // 获取标准差值
+        double stdDev0 = stdDev.ptr(0).get(0);
+        double stdDev1 = stdDev.ptr(1).get(0);
+        double stdDev2 = stdDev.ptr(2).get(0);
+
+        // 如果颜色变化较小,则可能是统一的背景色
+        return stdDev0 > 15 || stdDev1 > 15 || stdDev2 > 15;
+
+    }
+
     /**
      * 检测图像中文字是否被遮挡
      * @param image 输入图像