使用Java OpenCV实现文档扫描仪时透视变换后输出全灰图像问题
问题:Java OpenCV文档扫描仪透视变换后输出全灰色图像
我用Java OpenCV构建文档扫描仪,流程是图像预处理(灰度化/归一化)→自动检测角点→透视变换将梯形文档转为矩形,但变换完成后得到全灰色的输出图像,已确认检测到的角点是正确的。
代码实现
SkewHandler类
@Component public class SkewHandler implements PreprocessingHandler{ private ShapeDetector shapeDetector; @Autowired SkewHandler(ShapeDetector shapeDetector) { this.shapeDetector = shapeDetector; } @Override public Mat doPreprocessing(Mat inputImage) { System.out.println("skew"); Mat greyImage = new GreyScaleHandler().doPreprocessing(inputImage); Mat normalizeImage = new NormalizeHandler().doPreprocessing(greyImage); Mat threseholdImage = new ThresholdHandler().doPreprocessing(normalizeImage); MatOfPoint corners = shapeDetector.getCorners(threseholdImage); MatOfPoint polygonCorners = shapeDetector.getApproximatePolygonCorners(corners); MatOfPoint frontPerspectiveCorners = shapeDetector.getFrontPerspectiveCorners(corners); Size frontPerspectiveSize = shapeDetector.getFrontPerspectiveSize(corners); Mat perspectiveTransformMatrix = Imgproc.getPerspectiveTransform( convertPoints(polygonCorners), convertPoints(frontPerspectiveCorners)); Mat outputImage = new Mat(inputImage.size(), inputImage.type()); Imgproc.warpPerspective(inputImage, outputImage, perspectiveTransformMatrix,frontPerspectiveSize); return outputImage; } private static MatOfPoint2f convertPoints(MatOfPoint points) { MatOfPoint2f points2f = new MatOfPoint2f(); points.convertTo(points2f, CvType.CV_32FC1); return points2f; } }
ShapeDetector类
@Component public class ShapeDetector { public MatOfPoint getCorners(Mat inputImage) { List<MatOfPoint> corners = new ArrayList<>(); Imgproc.findContours(inputImage,corners,new Mat(), Imgproc.RETR_EXTERNAL,Imgproc.CHAIN_APPROX_TC89_L1); return corners.stream() .max(Comparator.comparing(Imgproc::contourArea)) .orElse(null); } public Double getCornersPerimeter(MatOfPoint2f corners) { return Imgproc.arcLength(corners, true); } public MatOfPoint getApproximatePolygonCorners(MatOfPoint corners) { MatOfPoint2f corners2f = new MatOfPoint2f(); corners.convertTo(corners2f, CvType.CV_32FC2); MatOfPoint2f polygonCorners2f = new MatOfPoint2f(); Imgproc.approxPolyDP(corners2f,polygonCorners2f, 0.015*getCornersPerimeter(corners2f),true); MatOfPoint polygonCorners = new MatOfPoint(); polygonCorners2f.convertTo(polygonCorners, CvType.CV_32S); return polygonCorners; } public MatOfPoint getFrontPerspectiveCorners(MatOfPoint corners) { double maxX = corners.toList().stream() .mapToDouble(point -> point.x).max().getAsDouble(); double minX = corners.toList().stream() .mapToDouble(point -> point.x).min().getAsDouble(); double maxY = corners.toList().stream() .mapToDouble(point -> point.y).max().getAsDouble(); double minY = corners.toList().stream(). mapToDouble(point -> point.y).min().getAsDouble(); double maxWidth = maxX - minX; double maxHeight = maxY -minY; return new MatOfPoint( new Point(0,0), new Point(maxWidth, 0), new Point(maxWidth, maxHeight), new Point(maxHeight, 0)); } public Size getFrontPerspectiveSize(MatOfPoint corners) { double maxX = corners.toList().stream() .mapToDouble(point -> point.x).max().getAsDouble(); double minX = corners.toList().stream() .mapToDouble(point -> point.x).min().getAsDouble(); double maxY = corners.toList().stream() .mapToDouble(point -> point.y).max().getAsDouble(); double minY = corners.toList().stream(). mapToDouble(point -> point.y).min().getAsDouble(); double maxWidth = maxX - minX; double maxHeight = maxY -minY; return new Size(maxWidth, maxHeight); } }
问题根源与修复方案
1. 目标角点坐标错误
getFrontPerspectiveCorners方法最后一个点写错,导致目标区域完全错位:
修复代码:
public MatOfPoint getFrontPerspectiveCorners(MatOfPoint corners) { double maxX = corners.toList().stream() .mapToDouble(point -> point.x).max().getAsDouble(); double minX = corners.toList().stream() .mapToDouble(point -> point.x).min().getAsDouble(); double maxY = corners.toList().stream() .mapToDouble(point -> point.y).max().getAsDouble(); double minY = corners.toList().stream() .mapToDouble(point -> point.y).min().getAsDouble(); double maxWidth = maxX - minX; double maxHeight = maxY - minY; // 修正为左上、右上、右下、左下的正确顺序 return new MatOfPoint( new Point(0, 0), new Point(maxWidth, 0), new Point(maxWidth, maxHeight), new Point(0, maxHeight)); }
2. 点类型转换错误
convertPoints方法使用CV_32FC1错误,应该用CV_32FC2对应点的x/y双分量:
修复代码:
private static MatOfPoint2f convertPoints(MatOfPoint points) { MatOfPoint2f points2f = new MatOfPoint2f(); points.convertTo(points2f, CvType.CV_32FC2); return points2f; }
3. 源角点与目标角点顺序不匹配
approxPolyDP返回的角点顺序随机,需按固定顺序排序:
添加角点排序方法到ShapeDetector:
public MatOfPoint sortCorners(MatOfPoint corners) { List<Point> pointList = corners.toList(); // 左上:x+y最小 Point topLeft = pointList.stream().min(Comparator.comparingDouble(p -> p.x + p.y)).get(); // 右上:x-y最小 Point topRight = pointList.stream().min(Comparator.comparingDouble(p -> p.x - p.y)).get(); // 右下:x+y最大 Point bottomRight = pointList.stream().max(Comparator.comparingDouble(p -> p.x + p.y)).get(); // 左下:x-y最大 Point bottomLeft = pointList.stream().max(Comparator.comparingDouble(p -> p.x - p.y)).get(); return new MatOfPoint(topLeft, topRight, bottomRight, bottomLeft); }
在SkewHandler中调用排序:
MatOfPoint polygonCorners = shapeDetector.getApproximatePolygonCorners(corners); polygonCorners = shapeDetector.sortCorners(polygonCorners);
4. 优化输出Mat创建
warpPerspective会自动根据目标尺寸创建Mat,无需提前指定原尺寸:
// 移除原语句,替换为 Mat outputImage = new Mat(); Imgproc.warpPerspective(inputImage, outputImage, perspectiveTransformMatrix, frontPerspectiveSize);
内容的提问来源于stack exchange,提问作者Cosmin Ştefan
相关产品推荐
相关产品推荐

