|
|
@@ -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 输入图像
|