You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenCV透视校正非线性畸变:图像X轴区域拉伸收缩异常求助

透视校正后图像左右拉伸/收缩异常的解决方法

问题描述

使用OpenCV进行透视校正时,输出图像出现左侧(小X坐标区域)拉伸、右侧(大X坐标区域)收缩的问题。正常情况下,原图中心的垂直绿线应位于输出图像X轴中间,但实际偏离明显,变换效果不符合预期。

原图(标注转换区域):
Original

输出图像:
result

问题代码

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

public class ImagePerspective {

        static { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); }


        /**
         * Transforms an image using four points, adjusting the size of the output image to be
         * as large as possible based on the points and the aspect ratio.
         *
         * @param fileIn      Path to the input image.
         * @param points      Four points in the original image defined in the following order:
         *                    - (x1, y1): top-left
         *                    - (x2, y2): top-right
         *                    - (x3, y3): bottom-right
         *                    - (x4, y4): bottom-left
         * @param fileOut     Path where the transformed image will be saved.
         * @param aspectRatio Width/height ratio of the output image. <=0 to maintain the original ratio.
         */
        public static void transformImage(String fileIn, Point[] points, String fileOut, int finalWidth, int finalHeight) {

            // load original image
            Mat imageOrig = Imgcodecs.imread(fileIn);
            if (imageOrig.empty()) {
                throw new IllegalArgumentException("Can not read image: " + fileIn);
            }

            log("Original size: " + imageOrig.width() + "x" + imageOrig.height());
            log("Final size   : " + finalWidth + "x" + finalHeight);
            
            // Define the destination points
            MatOfPoint2f pointsDest = new MatOfPoint2f(
                new Point(0, 0),                            // top-left
                new Point(finalWidth - 1, 0),               // top-right
                new Point(finalWidth - 1, finalHeight - 1), // bottom-right
                new Point(0, finalHeight - 1)               // bottom-left
            );

            // Define the original points
            MatOfPoint2f pointsOrig = new MatOfPoint2f(points);

            // Calculate the perspective transformation matrix
            Mat transformationMat = Imgproc.getPerspectiveTransform(pointsOrig, pointsDest);

            // Create the transformed image with white background
            Mat imageTransformed = new Mat(finalHeight, finalWidth, imageOrig.type(), new Scalar(255, 255, 255));

            // Apply transformation
            Imgproc.warpPerspective( imageOrig, imageTransformed, transformationMat
                                   , new Size(finalWidth, finalHeight), Imgproc.INTER_LINEAR
                                   , Core.BORDER_CONSTANT, new Scalar(255, 255, 255)
                                   );

            // Save final image
            boolean saved = Imgcodecs.imwrite(fileOut, imageTransformed);
            if (saved) {
                log("Image saved to " + fileOut);
            } else {
                log("Error saving image " + fileOut);
            }
        }
        
        private static void log(String msg) {
            System.out.println(msg);
        }

        public static void main(String[] args) {
           
            
            String fileIn = "original.png";
            String fileOut = "resul.png";

            Point[] points = {
                    new Point(12, 0),    // top-left
                    new Point(173, 13),  // top-right
                    new Point(174, 141), // bottom-right
                    new Point(0, 64)     // bottom-left
            };

            // Transforms the image
            transformImage(fileIn, points, fileOut, 200, 100);
        }
    }

问题原因

  1. 原始四点映射逻辑错误:输入的待校正区域四点中,bottom-left点(0,64)的X坐标小于top-left点(12,0),导致透视变换的横向映射关系混乱,直接引发左右区域的拉伸/收缩异常。
  2. 输出尺寸宽高比不匹配:强制指定输出尺寸为200x100(宽高比2:1),与原始待校正区域的实际宽高比不符,进一步放大了变形问题。

解决方案

1. 修正原始四点的顺序与映射关系

确保输入的四点严格按照top-left → top-right → bottom-right → bottom-left的顺序,且每个点对应待校正区域的真实角点。同时确认四点构成的区域符合透视变换的矩形逻辑。

2. 匹配输出图像与原始区域的宽高比

先计算原始待校正区域的实际宽高比,再基于该比例设置输出尺寸,避免强制缩放导致的变形:

// 计算原始区域的实际宽度和高度
double widthTop = Math.hypot(points[1].x - points[0].x, points[1].y - points[0].y);
double widthBottom = Math.hypot(points[2].x - points[3].x, points[2].y - points[3].y);
double actualWidth = Math.max(widthTop, widthBottom);

double heightLeft = Math.hypot(points[3].x - points[0].x, points[3].y - points[0].y);
double heightRight = Math.hypot(points[2].x - points[1].x, points[2].y - points[1].y);
double actualHeight = Math.max(heightLeft, heightRight);

// 基于原始宽高比设置输出尺寸(固定宽度为200,高度按比例计算)
int finalWidth = 200;
int finalHeight = (int) Math.round(finalWidth * actualHeight / actualWidth);

3. 完整修正后的代码

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

public class ImagePerspective {

    static { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); }

    public static void transformImage(String fileIn, Point[] points, String fileOut, int finalWidth, int finalHeight) {
        Mat imageOrig = Imgcodecs.imread(fileIn);
        if (imageOrig.empty()) {
            throw new IllegalArgumentException("无法读取图像: " + fileIn);
        }

        log("原始尺寸: " + imageOrig.width() + "x" + imageOrig.height());
        log("输出尺寸: " + finalWidth + "x" + finalHeight);
        
        MatOfPoint2f pointsDest = new MatOfPoint2f(
            new Point(0, 0),                            // top-left
            new Point(finalWidth - 1, 0),               // top-right
            new Point(finalWidth - 1, finalHeight - 1), // bottom-right
            new Point(0, finalHeight - 1)               // bottom-left
        );

        MatOfPoint2f pointsOrig = new MatOfPoint2f(points);
        Mat transformationMat = Imgproc.getPerspectiveTransform(pointsOrig, pointsDest);

        Mat imageTransformed = new Mat(finalHeight, finalWidth, imageOrig.type(), new Scalar(255, 255, 255));
        Imgproc.warpPerspective( imageOrig, imageTransformed, transformationMat
                               , new Size(finalWidth, finalHeight), Imgproc.INTER_LINEAR
                               , Core.BORDER_CONSTANT, new Scalar(255, 255, 255)
                               );

        boolean saved = Imgcodecs.imwrite(fileOut, imageTransformed);
        if (saved) {
            log("图像已保存至 " + fileOut);
        } else {
            log("保存图像失败: " + fileOut);
        }
    }
    
    private static void log(String msg) {
        System.out.println(msg);
    }

    public static void main(String[] args) {
        String fileIn = "original.png";
        String fileOut = "result.png";

        // 确认四点顺序:top-left → top-right → bottom-right → bottom-left
        Point[] points = {
                new Point(12, 0),    // top-left
                new Point(173, 13),  // top-right
                new Point(174, 141), // bottom-right
                new Point(0, 64)     // bottom-left
        };

        // 计算原始区域实际宽高比
        double widthTop = Math.hypot(points[1].x - points[0].x, points[1].y - points[0].y);
        double widthBottom = Math.hypot(points[2].x - points[3].x, points[2].y - points[3].y);
        double actualWidth = Math.max(widthTop, widthBottom);

        double heightLeft = Math.hypot(points[3].x - points[0].x, points[3].y - points[0].y);
        double heightRight = Math.hypot(points[2].x - points[1].x, points[2].y - points[1].y);
        double actualHeight = Math.max(heightLeft, heightRight);

        // 按原始比例设置输出尺寸
        int finalWidth = 200;
        int finalHeight = (int) Math.round(finalWidth * actualHeight / actualWidth);

        transformImage(fileIn, points, fileOut, finalWidth, finalHeight);
    }
}

关键说明

  • 透视变换的四点对应关系必须严格一致,任何一点的顺序或位置错误都会导致严重变形。
  • 保持输出图像与原始区域的宽高比一致,是避免拉伸/收缩异常的核心前提。若必须使用固定尺寸,建议先按比例缩放原始区域再执行透视变换。

内容的提问来源于stack exchange,提问作者Gegomu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:50:04