图像变换之透视变换

62次阅读
没有评论

WarpPerspective

对图像进行透视变换

void cvWarpPerspective( const CvArr* src, CvArr* dst,const CvMat* map_matrix,

                       int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,

                       CvScalar fillval=cvScalarAll(0) );

src

输入图像.

dst

输出图像.

map_matrix

3×3 变换矩阵

flags

插值方法和以下开关选项的组合:

·       CV_WARP_FILL_OUTLIERS- 填充所有缩小图像的象素。如果部分象素落在输入图像的边界外,那么它们的值设定为 fillval.

·       CV_WARP_INVERSE_MAP- 指定 matrix 是输出图像到输入图像的反变换,因此可以直接用来做象素插值。否则, 函数从 map_matrix 得到反变换。

fillval

用来填充边界外面的值

函数 cvWarpPerspective 利用下面指定矩阵变换输入图像:

如果没有指定 CV_WARP_INVERSE_MAP , 
否则, 
要变换稀疏矩阵,使用 cxcore 中的函数 cvTransform 。

GetPerspectiveTransform

由四对点计算透射变换

CvMat* cvGetPerspectiveTransform( const CvPoint2D32f*src, const CvPoint2D32f* dst,

                                  CvMat*map_matrix );

#define cvWarpPerspectiveQMatrixcvGetPerspectiveTransform

src

输入图像的四边形顶点坐标。

dst

输出图像的相应的四边形顶点坐标。

map_matrix

指向3×3输出矩阵的指针。

函数cvGetPerspectiveTransform计算满足以下关系的透射变换矩阵:

这里,dst(i)= (x'i,y'i),src(i)= (xi,yi),i = 0..3.

#include <highgui.h>
#include <cv.h>

int main(int argc, char** argv)
{
    CvPoint2D32f srcTri[4], dstTri[4]; //二维坐标下的点,类型为浮点
    //CvMat* rot_mat = cvCreateMat( 2, 3, CV_32FC1 );  //多通道矩阵
    CvMat* warp_mat = cvCreateMat( 3, 3, CV_32FC1 );
    IplImage *src, *dst;

    if( argc == 2 && ( ( src = cvLoadImage( argv[1], 1 ) ) != 0 ) )
    {
        dst = cvCloneImage( src );  //制作图像的完整拷贝
        dst ->origin = src ->origin;  
        /*
        int origin; /* 0 - 顶—左结构,
        1 - 底—左结构 (Windows bitmaps 风格) 
        */
        cvZero( dst );  //清空数组

        //计算矩阵仿射变换
        srcTri[0].x = 0;
        srcTri[0].y = 0;
        srcTri[1].x = src -> width - 1;  //缩小一个像素
        srcTri[1].y = 0;
        srcTri[2].x = 0;
        srcTri[2].y = src -> height - 1;
        srcTri[3].x = src -> width - 1;  //bot right
        srcTri[3].y = src -> height - 1;

        //改变目标图像大小
        dstTri[0].x = src -> width * 0.05;
        dstTri[0].y = src -> height * 0.33;
        dstTri[1].x = src -> width * 0.9;
        dstTri[1].y = src -> height * 0.25;
        dstTri[2].x = src -> width * 0.2;
        dstTri[2].y = src -> height * 0.7;
        dstTri[3].x = src -> width * 0.8;
        dstTri[3].y = src -> height * 0.9;

        cvGetPerspectiveTransform( srcTri, dstTri, warp_mat );  //由三对点计算仿射变换 
        cvWarpPerspective( src, dst, warp_mat );  //对图像做仿射变换

        //输出
        cvNamedWindow( "Perspective Warp", 1 );
        cvShowImage( "Perspective Warp", dst );  //最终是输出dst 
        cvWaitKey();
    }
    cvReleaseImage( &dst );
    cvReleaseMat( &warp_mat );

    return 0;
}

python查找最大外包矩形并校正

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import numpy as np
import cv2
import imutils

"""
x小y小rect[0]  x大y小rect[1]       
    -------------
    |           |
    |           |
    -------------
x小y大rect[3]  x大y大rect[2]
"""
def order_points(pts):
    # 初始化矩形4个顶点的坐标
    rect = np.zeros((4, 2), dtype='float32')
    # 坐标点求和 x+y
    s = pts.sum(axis = 1)
    # np.argmin(s) 返回最小值在s中的序号
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]
    # diff就是后一个元素减去前一个元素  y-x
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
    # 返回矩形有序的4个坐标点
    return rect

def perTran(image, pts):
    rect = order_points(pts)
    tl, tr, br, bl = rect
    # 计算宽度
    widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
    maxWidth = max(int(widthA), int(widthB))
    # 计算高度
    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    maxHeight = max(int(heightA), int(heightB))
    # 定义变换后新图像的尺寸
    dst = np.array([[0, 0], [maxWidth-1, 0], [maxWidth-1, maxHeight-1],
                   [0, maxHeight-1]], dtype='float32')
    # 变换矩阵
    M = cv2.getPerspectiveTransform(rect, dst)
    # 透视变换
    warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
    return warped

def main():
    image = cv2.imread('./1.png')
    output = image.copy()
    # 转换成灰度图像
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 双边滤波器能够做到平滑去噪的同时还能够很好的保存边缘
    gray = cv2.bilateralFilter(gray, 11, 17, 17)
    # 检测边缘
    edged = cv2.Canny(gray, 30, 200)
    #cv2.imshow('Canny', edged)
    # 查找轮廓
    cnts = cv2.findContours(edged.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)
    # 获取前3个最大的轮廓
    cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:3]

    screenCnt = None
    for c in cnts:
        # 轮廓周长
        peri = cv2.arcLength(c, True)
        print('arcLength : {:.3f}'.format(peri))
        # approxPolyDP主要功能是把一个连续光滑曲线折线化,对图像轮廓点进行多边形拟合。
        # 近似轮廓的多边形曲线, 近似精度为轮廓周长的1.5%
        approx = cv2.approxPolyDP(c, 0.015 * peri, True)
        # 矩形边框具有4个点, 将其他的剔除
        if len(approx) == 4:
            screenCnt = approx
            break
    # 绘制轮廓矩形边框
    cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 3)
    # 调整为x,y 坐标点矩阵
    pts = screenCnt.reshape(4, 2)
    #print('screenCnt.reshape:\n{}'.format(pts))
    # 透视变换
    warped = perTran(output, pts)

    cv2.imshow('image', image)
    cv2.imshow('output', output)
    cv2.imshow('warped', warped)

    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()