精华内容
下载资源
问答
  • 仿射变换代码
    2021-04-21 13:10:22

    1图像配准在利用显微照相机拍摄生物组织的序列切片图像时,由于人为将切片放置在载玻片上,会产生不同程度的偏移、旋转,使相邻切片的图像不匹配,无法进行三维重建。图1和图2是黄瓜茎横截面的两个相邻切片在显微照相机下成的图像。其中,最外面轮廓是黄瓜茎的表皮,中心是9个维管束,必须以图1为参考图像对图2进行平移、旋转甚至缩放等操作使它和图1匹配,只有所有相邻切片都配准了,才可以进行三维重建。图1参考图像图2待配准图像图像配准是指寻找联系两幅图像的几何变换,使得两幅图像上的对应点达到空间上的一致,常用的方法主要有相似变换、刚性变换和仿射变换。相似变换是局部到整体在各个方向上的等比例变换,而仿射变换是局部到整体在不同方向上的不等比例变换。从直觉上看,相似变换可以放大或者缩小甚至旋转,但不变形;而仿射变换可能会变形,相似变换是仿射变换的一个特例[1]。在相邻切片图像之间多少是有些变形的,因为在制作切片时,由于有外力作用,不能保证组织不变形,所以不能用相似变换来实现配准,可以用仿射变换来实现。刘哲星等[2]采用刚性变换实现了小鼠胚胎序列组织切片的图像配准。刚性变换(rigidtransforamtion)与仿射变换的最大区别是:刚性变换只有平移和旋转,没有缩放;仿射变换既有平移、旋转又有缩放。在获取切片的图像时,为了使效果最好,显微照相机通常要进行调焦操作,这样照出来的图像会有缩放,因此本文采用仿射变换来实现配准。两幅图像的配准可以看成是两个特征点集的配准,对图2的所有点进行仿射变换可以实现两幅图像的配准[3]。仿射变换有6个参数,通过3对特征点就可以求出来,然而在选取特征点时,人工选择会产生误差,为了降低误差,本文选取多对特征点,利用最小二乘法,求出最优仿射变换参数。配准后的图像利用VTK提供的步进立方体算法抽取等值面,实现了基于切片图像的黄瓜茎的三维建模和显示。2二维仿射变换2.1二维仿射变换的数学表达式二维仿射变换的数学表达式为:x=ax+by+ey=cx+dy+f(1)其中,代表仿射变换,x和y是变换前像素的坐标值,x和y是变换后像素的坐标值;a,b,c,d,e,f是仿射变换系数。2.2二维仿射变换的几何特征1)仿射变换的逆变换,仍是仿射变换。2)仿射变换是线性变换,直线段仿射变换后仍然是直线段,并且保持线段上点的定比关系不变。3)两条平行直线经过仿射变换后,仍可保持其平行性。4)任意平面图形经仿射变换后,其面积将发生变化,为变化前的(ad-bc)倍。只有当(ad-bc)=1时,面积在仿射变换前后才不变。3利用二维仿射变换实现图像配准3.1最小二乘法求取最优一组仿射变换参数由于是人工选取特征点,在选取时难免会有误差,为了使误差降到最低,采用最小二乘法估计参数,就是要选择参数,使观测值与相应函数值的离差平方和达到最小。分别在参考图像和待配准图像上选取多于3个对应的特征点(控制点),如5个点,分别记为Pi,Qi,i=1,2,3,4,5。由于每3对特征点确定一组仿射变换参数,因此可以由C35=10组特征点集确定10组仿射变换参数。例如,由(P1,P2,P3)和(Q1,Q2,Q3)确定了待配准图像到参考图像的仿射变换AT1,求取点Q4经该仿射变换后得到的点Q4与P4之间的欧式距离作为误差d1,X方向误差为X=x-(ax+by+e),Y方向误差为Y=y-(cx+dy+f),则距离误差为d1=X2+Y2,同理求取Q5与P5之间的欧式距离作为误差d2。设离差平方和U=d2=X2+Y2。这样,用10组特征点得到10组U值,选取最小的U值对应的特征点组,从而得到最优化的仿射

    更多相关内容
  • matlab仿射变换代码这个存储库(将)提供几种地图同步算法的实现,这些算法在一组形状中计算地图。 该代码是在 MIT 许可下发布的,可以用于任何目的并具有适当的署名。 该代码随附以下论文,应在使用所提供模块的...
  • 仿射变换过程,(x,y)表示原图像中的坐标,(x’,y’)表示目标图像的坐标 ↑ 三. 仿射变换——图像平移 算法: 仿射变换—图像平移算法,其中tx为在横轴上移动的距离,ty为在纵轴上移动的距离 ↑ 四. python实现...
  • 图像的仿射变换代码以及双线性插值代码理解 因为前段时间看了一下STN网络和可变形卷积,然后想研究一下代码实现部分,发现真的是一点都看不懂,还好看了一篇大佬的博客,写这篇文章的目的是细说一下大佬博客中的代码...

    图像的仿射变换代码以及双线性插值代码理解

    因为前段时间看了一下STN网络和可变形卷积,然后想研究一下代码实现部分,发现真的是一点都看不懂,还好看了一篇大佬的博客,写这篇文章的目的是细说一下大佬博客中的代码细节
    这里附上原博客链接:https://blog.csdn.net/u011974639/article/details/79675408

    图像的仿射变换

    def affine_grid_generator(height,width,M):
        num_batch = M.shape[0]
        
        #创建棋盘grid,平分整个图片
        x = np.linspace(-1,1,width)
        y = np.linspace(-1,1,height)
        
       # x2=np.linspace(0,400,400)
       # y2=np.linspace(0,400,400)
        x_t,y_t = np.meshgrid(x,y)
        
        #制作原始坐标点的列向量
        prod = np.prod(x_t.shape)
        #print(prod)
        ones = np.ones(prod)
        #print(ones)
        sampling_grid = np.vstack([x_t.flatten(), y_t.flatten(), ones])
        sampling_grid = np.resize(sampling_grid,(num_batch,3,height*width))
        #print(sampling_grid)
        batch_grids = np.matmul(M,sampling_grid)
        #batch_grids (Batch,2,height*width)
        
        #将仿射结果中H,W拆开
        batch_grids = batch_grids.reshape(num_batch,2,height,width)
        batch_grids = np.moveaxis(batch_grids,1,-1) #num_batch,height,width,2
        
        print("Transformation Matrices: {}".format(M.shape))
        print("Sampling Grid: {}".format(sampling_grid.shape))
        print("Batch Grids: {}".format(batch_grids.shape))
    
        return batch_grids
    

    作者的代码是用numpy实现的,因为tensorflow跟numpy差不多,这并不影响我们理解tensorflow代码。下面我将逐句解读代码。

    1. num_batch是获取当前epoch下该batch需要进行仿射变换的图片数量。

    2. 创建棋盘grid。这里作者的意思是说,根据原图片中的宽和高,生成一个一模一样的坐标点集合。形象的叫做棋盘grid。注意这里叫棋盘的原因我觉得很形象,因为这里面只有坐标(棋盘),而没有坐标点的像素值(棋子)。

        x = np.linspace(-1,1,width)
        y = np.linspace(-1,1,height)
        
       # x2=np.linspace(0,400,400)
       # y2=np.linspace(0,400,400)
        x_t,y_t = np.meshgrid(x,y)
    

    3.拿到原图中每一个坐标点后,我们要开始映射出仿射变换中的坐标点。核心思想就是拿原坐标点去乘以仿射变换的参数矩阵。得到仿射变换后的坐标点。那么原坐标点首先要换成{x,y,1}转置的格式。才能与反射变换相乘,同时因为有num_batch个图片,所以将T{x,y,1}resize到num_batch个T{x,y,1}

    	prod = np.prod(x_t.shape)
        #print(prod)
        ones = np.ones(prod)
        #print(ones)
        sampling_grid = np.vstack([x_t.flatten(), y_t.flatten(), ones])   #T([x,y,1])
        sampling_grid = np.resize(sampling_grid,(num_batch,3,height*width))
    

    4.最终将M也即变换参数矩阵与T(x,y,1)相乘得到经过仿射变换后的对应的x,y坐标

    双线性插值

    def bilinear_sampler(input_img,x,y):
        B,H,W,C = input_img.shape
        x=((x+1.)*W)*0.5
        y=((y+1.)*H)*0.5
        
        #获取相邻的四个坐标
        x0 = np.floor(x).astype(np.int64)
        x1 = x0+1
        y0 = np.floor(y).astype(np.int64)
        y1 = y0+1
        
        x0 = np.clip(x0,0,W-1)
        x1 = np.clip(x1,0,W-1)
        y0 = np.clip(y0,0,H-1)
        y1 = np.clip(y1,0,H-1)
        
        Ia = input_img[np.arange(B)[:,None,None], y0, x0]
        Ib = input_img[np.arange(B)[:,None,None], y1, x0]
        Ic = input_img[np.arange(B)[:,None,None], y0, x1]
        Id = input_img[np.arange(B)[:,None,None], y1, x1]
        
        wa = (x1-x) * (y1-y)
        wb = (x1-x) * (y-y0)
        wc = (x-x0) * (y1-y)
        wd = (x-x0) * (y-y0)
        
        wa = np.expand_dims(wa, axis=3)
        wb = np.expand_dims(wb, axis=3)
        wc = np.expand_dims(wc, axis=3)
        wd = np.expand_dims(wd, axis=3)
        
        out = wa*Ia + wb*Ib + wc*Ic + wd*Id
    
        return out
    

    1.首先将我们刚才得到的坐标映射至H,W
    我们刚才设置的x,y是[-1,1]等距离初始化的,那么现在将其+1,则映射至[0,2]之间,然后x乘以W,则映射至[0,2W]之间,最后*0.5,则映射至[0,W]之间。y同理

    B,H,W,C = input_img.shape
        x=((x+1.)*W)*0.5
        y=((y+1.)*H)*0.5
    

    2.因为由仿射变换后的x,y坐标有可能是小数,那么将其转换为周围四个点的坐标,方便进行双线性插值的运算。同时应该保证转换后的坐标不能大于H,W,也不能小于0。

     #获取相邻的四个坐标
        x0 = np.floor(x).astype(np.int64)
        x1 = x0+1
        y0 = np.floor(y).astype(np.int64)
        y1 = y0+1
        
        x0 = np.clip(x0,0,W-1)
        x1 = np.clip(x1,0,W-1)
        y0 = np.clip(y0,0,H-1)
        y1 = np.clip(y1,0,H-1)
    

    3.获取四个周围点坐标后,在取出原图像中四个周围点像素值进行双线性插值运算。

    Ia = input_img[np.arange(B)[:,None,None], y0, x0]
        Ib = input_img[np.arange(B)[:,None,None], y1, x0]
        Ic = input_img[np.arange(B)[:,None,None], y0, x1]
        Id = input_img[np.arange(B)[:,None,None], y1, x1]
        
        wa = (x1-x) * (y1-y)
        wb = (x1-x) * (y-y0)
        wc = (x-x0) * (y1-y)
        wd = (x-x0) * (y-y0)
        
    
        wa = np.expand_dims(wa, axis=3)
        wb = np.expand_dims(wb, axis=3)
        wc = np.expand_dims(wc, axis=3)
        wd = np.expand_dims(wd, axis=3)
        
        out = wa*Ia + wb*Ib + wc*Ic + wd*Id
    
        return out
    
    展开全文
  • 仿射变换代码

    2013-11-22 12:17:30
    在几何上定义为两个向量空间之间的一个仿射变换或者仿射映射(来自拉丁语,affinis,“和。..相关”)由一个线性变换接上一个平移组成。
  • 输入的图像 OutputArray dst:输出的图像 InputArray M:透视变换的矩阵 Size dsize:输出图像的大小 int flags=INTER_LINEAR:输出图像的插值方法, int borderMode=BORDER_CONSTANT:图像边界的处理方式 (BORDER_...

    void warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())

    原理说明
    参数说明:

    • InputArray src:输入的图像
    • OutputArray dst:输出的图像
    • InputArray M:透视变换的矩阵
    • Size dsize:输出图像的大小
    • int flags=INTER_LINEAR:输出图像的插值方法,
    • int borderMode=BORDER_CONSTANT:图像边界的处理方式 (BORDER_CONSTANT用指定像素值填充边缘)
    • const Scalar& borderValue=Scalar():边界的颜色设置,一般默认是0
    // 该段代码来自:https://blog.csdn.net/fengbingchun/article/details/52004083
    // 亲测无误
    // fbc_cv is free software and uses the same licence as OpenCV
    // Email: fengbingchun@163.com
     
    #ifndef FBC_CV_WARP_PERSPECTIVE_HPP_
    #define FBC_CV_WARP_PERSPECTIVE_HPP_
     
    /* reference: include/opencv2/imgproc.hpp
                  modules/imgproc/src/imgwarp.cpp
    */
     
    #include <typeinfo>
    #include "core/mat.hpp"
    #include "core/invert.hpp"
    #include "imgproc.hpp"
    #include "remap.hpp"
     
    namespace fbc {
     
    // Calculates a perspective transform from four pairs of the corresponding points
    FBC_EXPORTS int getPerspectiveTransform(const Point2f src1[], const Point2f src2[], Mat_<double, 1>& dst);
     
    // Applies a perspective transformation to an image
    // The function cannot operate in - place
    // support type: uchar/float
    /* 
    \f[\texttt{ dst } (x, y) = \texttt{ src } \left(\frac{ M_{ 11 } x + M_{ 12 } y + M_{ 13 } }{M_{ 31 } x + M_{ 32 } y + M_{ 33 }},
    	\frac{ M_{ 21 } x + M_{ 22 } y + M_{ 23 } }{M_{ 31 } x + M_{ 32 } y + M_{ 33 }} \right)\f]
    */
    template<typename _Tp1, typename _Tp2, int chs1, int chs2>
    int warpPerspective(const Mat_<_Tp1, chs1>& src, Mat_<_Tp1, chs1>& dst, const Mat_<_Tp2, chs2>& M_,
    	int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar())
    {
    	FBC_Assert(src.data != NULL && dst.data != NULL && M_.data != NULL);
    	FBC_Assert(src.cols > 0 && src.rows > 0 && dst.cols > 0 && dst.rows > 0);
    	FBC_Assert(src.data != dst.data);
    	FBC_Assert(typeid(double).name() == typeid(_Tp2).name() && M_.rows == 3 && M_.cols == 3);
    	FBC_Assert((typeid(uchar).name() == typeid(_Tp1).name()) || (typeid(float).name() == typeid(_Tp1).name())); // uchar/float
     
    	double M[9];
    	Mat_<double, 1> matM(3, 3, M);
     
    	int interpolation = flags & INTER_MAX;
    	if (interpolation == INTER_AREA)
    		interpolation = INTER_LINEAR;
     
    	if (!(flags & WARP_INVERSE_MAP))
    		invert(M_, matM);
     
    	Range range(0, dst.rows);
     
    	const int BLOCK_SZ = 32; //===运行优化所需,划分成小块处理
    	short XY[BLOCK_SZ*BLOCK_SZ * 2], A[BLOCK_SZ*BLOCK_SZ];//=== XY dst每个像素对应src上的坐标;A在非最邻近差值模式下起作用,为运行优化处理块中的位置索引
    	int x, y, x1, y1, width = dst.cols, height = dst.rows;
     
    	int bh0 = std::min(BLOCK_SZ / 2, height);//=== 正常情况,XY对应的像素块是w*h=64*16
    	int bw0 = std::min(BLOCK_SZ*BLOCK_SZ / bh0, width);
    	bh0 = std::min(BLOCK_SZ*BLOCK_SZ / bw0, height);
     
    	for (y = range.start; y < range.end; y += bh0) {
    		for (x = 0; x < width; x += bw0) {
    			int bw = std::min(bw0, width - x);
    			int bh = std::min(bh0, range.end - y); // height
     
    			Mat_<short, 2> _XY(bh, bw, XY), matA;
    			Mat_<_Tp1, chs1> dpart;
    			dst.getROI(dpart, Rect(x, y, bw, bh));
     
    			for (y1 = 0; y1 < bh; y1++) {
    				short* xy = XY + y1*bw * 2;
    				double X0 = M[0] * x + M[1] * (y + y1) + M[2];
    				double Y0 = M[3] * x + M[4] * (y + y1) + M[5];
    				double W0 = M[6] * x + M[7] * (y + y1) + M[8];
     
    				if (interpolation == INTER_NEAREST) {
    					x1 = 0;
    					for (; x1 < bw; x1++) {
    						double W = W0 + M[6] * x1;
    						W = W ? 1. / W : 0;
    						double fX = std::max((double)INT_MIN, std::min((double)INT_MAX, (X0 + M[0] * x1)*W));
    						double fY = std::max((double)INT_MIN, std::min((double)INT_MAX, (Y0 + M[3] * x1)*W));
    						int X = saturate_cast<int>(fX);//===saturate_cast防止溢出
    						int Y = saturate_cast<int>(fY);
     
    						xy[x1 * 2] = saturate_cast<short>(X);
    						xy[x1 * 2 + 1] = saturate_cast<short>(Y);
    					}
    				} else {
    					short* alpha = A + y1*bw;
    					x1 = 0;
    					for (; x1 < bw; x1++) {
    						double W = W0 + M[6] * x1;
    						W = W ? INTER_TAB_SIZE / W : 0;
    						double fX = std::max((double)INT_MIN, std::min((double)INT_MAX, (X0 + M[0] * x1)*W));
    						double fY = std::max((double)INT_MIN, std::min((double)INT_MAX, (Y0 + M[3] * x1)*W));
    						int X = saturate_cast<int>(fX);
    						int Y = saturate_cast<int>(fY);
     
    						xy[x1 * 2] = saturate_cast<short>(X >> INTER_BITS);//===dst每个像素对应src上的坐标-x
    						xy[x1 * 2 + 1] = saturate_cast<short>(Y >> INTER_BITS);//===dst每个像素对应src上的坐标-y
    						alpha[x1] = (short)((Y & (INTER_TAB_SIZE - 1))*INTER_TAB_SIZE + (X & (INTER_TAB_SIZE - 1)));//===在非最邻近差值模式下起作用,为运行优化处理块中的位置索引
    					}
    				}
    			}
     
    			if (interpolation == INTER_NEAREST) {
    				remap(src, dpart, _XY, Mat_<float, 1>(), interpolation, borderMode, borderValue);
    			} else {
    				Mat_<ushort, 1> _matA(bh, bw, A);
    				remap(src, dpart, _XY, _matA, interpolation, borderMode, borderValue);
    			}
    		}
    	}
     
    	return 0;
    }
     
    } // namespace fbc
     
    #endif // FBC_CV_WARP_PERSPECTIVE_HPP_

    void remap( InputArray src, OutputArray dst,
                             InputArray map1, InputArray map2,
                             int interpolation, int borderMode = BORDER_CONSTANT,
                             const Scalar& borderValue = Scalar());

    一篇不错的参考

    参数说明

    • src,输入图像,即源图像,填灰度图或真彩图均可。
    • dst,输出图像,需要和源图像有一样的尺寸和类型。
    • map1,即 mapx。
    • map2,即 mapy 。
    • interpolation,插值方法。插值就是根据已知数据点(条件),来预测未知数据点值得方法。在重映射过程中,图像的大小可能发生改变。此时像素与像素之间的关系就不是一一对应关系,因此在重映射过程中,可能会涉及到像素值的插值计算。可选插值方式如下:

    INTER_NEAREST(最近邻差值)

    INTER_LINEAR(双线性插值,一般选择这种方式)

    INTER_CUBIC(双三次样条插值,超过 4×4 像素邻域内的双三次插值)

    INTER_LANCZOS4(Lanczos 插值,超过 8×8 像素邻域的 Lanczos 插值)

    • borderValue ,只有当 borderMode取值为 BORDER_CONSTANT 时,这个参数才会被使用,边界会被填充成 borderValue 指定的颜色。
    // 代码源链接:https://blog.csdn.net/fengbingchun/article/details/51872436
    // fbc_cv is free software and uses the same licence as OpenCV
    // Email: fengbingchun@163.com
     
    #ifndef FBC_CV_REMAP_HPP_
    #define FBC_CV_REMAP_HPP_
     
    /* reference: include/opencv2/imgproc.hpp
                  modules/imgproc/src/imgwarp.cpp
    */
     
    #include <typeinfo>
    #include "core/mat.hpp"
    #include "core/base.hpp"
    #include "core/core.hpp"
    #include "imgproc.hpp"
    #include "resize.hpp"
     
    namespace fbc {
     
    const int INTER_REMAP_COEF_BITS = 15;
    const int INTER_REMAP_COEF_SCALE = 1 << INTER_REMAP_COEF_BITS;
     
    static uchar NNDeltaTab_i[INTER_TAB_SIZE2][2];
     
    static float BilinearTab_f[INTER_TAB_SIZE2][2][2];
    static short BilinearTab_i[INTER_TAB_SIZE2][2][2];
     
    static float BicubicTab_f[INTER_TAB_SIZE2][4][4];
    static short BicubicTab_i[INTER_TAB_SIZE2][4][4];
     
    static float Lanczos4Tab_f[INTER_TAB_SIZE2][8][8];
    static short Lanczos4Tab_i[INTER_TAB_SIZE2][8][8];
     
    template<typename _Tp1, typename _Tp2, int chs1, int chs2> static int remap_nearest(const Mat_<_Tp1, chs1>& src, Mat_<_Tp1, chs1>& dst,
    	const Mat_<_Tp2, chs2>& map1, const Mat_<_Tp2, chs2>& map2, int borderMode, const Scalar& borderValue);
    template<typename _Tp1, typename _Tp2, int chs1, int chs2> static int remap_linear(const Mat_<_Tp1, chs1>& src, Mat_<_Tp1, chs1>& dst,
    	const Mat_<_Tp2, chs2>& map1, const Mat_<_Tp2, chs2>& map2, int borderMode, const Scalar& borderValue);
    template<typename _Tp1, typename _Tp2, int chs1, int chs2> static int remap_cubic(const Mat_<_Tp1, chs1>& src, Mat_<_Tp1, chs1>& dst,
    	const Mat_<_Tp2, chs2>& map1, const Mat_<_Tp2, chs2>& map2, int borderMode, const Scalar& borderValue);
    template<typename _Tp1, typename _Tp2, int chs1, int chs2> static int remap_lanczos4(const Mat_<_Tp1, chs1>& src, Mat_<_Tp1, chs1>& dst,
    	const Mat_<_Tp2, chs2>& map1, const Mat_<_Tp2, chs2>& map2, int borderMode, const Scalar& borderValue);
     
    // Applies a generic geometrical transformation to an image
    // transforms the source image using the specified map, this function cannot operate in-place
    // support type: uchar/float
    template<typename _Tp1, typename _Tp2, int chs1, int chs2>
    int remap(const Mat_<_Tp1, chs1>& src, Mat_<_Tp1, chs1>& dst, const Mat_<_Tp2, chs2>& map1, const Mat_<_Tp2, chs2>& map2,
    	int interpolation, int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar())
    {
    	FBC_Assert(map1.size().area() > 0 && map1.size() == map2.size());
    	FBC_Assert(map1.data != NULL && map2.data != NULL);
    	FBC_Assert(src.size() == map1.size() && src.size() == dst.size());
    	FBC_Assert(src.data != dst.data);
    	FBC_Assert(typeid(uchar).name() == typeid(_Tp1).name() || typeid(float).name() == typeid(_Tp1).name()); // uchar || float
    	FBC_Assert(typeid(float).name() == typeid(_Tp2).name());
    	FBC_Assert(chs2 == 1);
     
    	switch (interpolation) {
    		case 0: {
    			remap_nearest(src, dst, map1, map2, borderMode, borderValue);
    			break;
    		}
    		case 1:
    		case 3: {
    			remap_linear(src, dst, map1, map2, borderMode, borderValue);
    			break;
    		}
    		case 2: {
    			remap_cubic(src, dst, map1, map2, borderMode, borderValue);
    			break;
    		}
    		case 4: {
    			remap_lanczos4(src, dst, map1, map2, borderMode, borderValue);
    			break;
    		}
    		default:
    			return -1;
    	}
     
    	return 0;
    }
     
    template<typename _Tp>
    static inline void interpolateLinear(_Tp x, _Tp* coeffs)
    {
    	coeffs[0] = 1.f - x;
    	coeffs[1] = x;
    }
     
    template<typename _Tp>
    static void initInterTab1D(int method, float* tab, int tabsz)
    {
    	float scale = 1.f / tabsz;
    	if (method == INTER_LINEAR) {
    		for (int i = 0; i < tabsz; i++, tab += 2)
    			interpolateLinear<float>(i*scale, tab);
    	} else if (method == INTER_CUBIC) {
    		for (int i = 0; i < tabsz; i++, tab += 4)
    			interpolateCubic<float>(i*scale, tab);
    	} else if (method == INTER_LANCZOS4) {
    		for (int i = 0; i < tabsz; i++, tab += 8)
    			interpolateLanczos4<float>(i*scale, tab);
    	} else {
    		FBC_Error("Unknown interpolation method");
    	}
    }
     
    template<typename _Tp>
    static const void* initInterTab2D(int method, bool fixpt)
    {
    	static bool inittab[INTER_MAX + 1] = { false };
    	float* tab = 0;
    	short* itab = 0;
    	int ksize = 0;
    	if (method == INTER_LINEAR) {
    		tab = BilinearTab_f[0][0], itab = BilinearTab_i[0][0], ksize = 2;
    	} else if (method == INTER_CUBIC) {
    		tab = BicubicTab_f[0][0], itab = BicubicTab_i[0][0], ksize = 4;
    	} else if (method == INTER_LANCZOS4) {
    		tab = Lanczos4Tab_f[0][0], itab = Lanczos4Tab_i[0][0], ksize = 8;
    	} else {
    		FBC_Error("Unknown/unsupported interpolation type");
    	}
     
    	if (!inittab[method]) {
    		AutoBuffer<float> _tab(8 * INTER_TAB_SIZE);
    		int i, j, k1, k2;
    		initInterTab1D<float>(method, _tab, INTER_TAB_SIZE);
    		for (i = 0; i < INTER_TAB_SIZE; i++) {
    			for (j = 0; j < INTER_TAB_SIZE; j++, tab += ksize*ksize, itab += ksize*ksize) {
    				int isum = 0;
    				NNDeltaTab_i[i*INTER_TAB_SIZE + j][0] = j < INTER_TAB_SIZE / 2;
    				NNDeltaTab_i[i*INTER_TAB_SIZE + j][1] = i < INTER_TAB_SIZE / 2;
     
    				for (k1 = 0; k1 < ksize; k1++) {
    					float vy = _tab[i*ksize + k1];
    					for (k2 = 0; k2 < ksize; k2++) {
    						float v = vy*_tab[j*ksize + k2];
    						tab[k1*ksize + k2] = v;
    						isum += itab[k1*ksize + k2] = saturate_cast<short>(v*INTER_REMAP_COEF_SCALE);
    					}
    				}
     
    				if (isum != INTER_REMAP_COEF_SCALE) {
    					int diff = isum - INTER_REMAP_COEF_SCALE;
    					int ksize2 = ksize / 2, Mk1 = ksize2, Mk2 = ksize2, mk1 = ksize2, mk2 = ksize2;
    					for (k1 = ksize2; k1 < ksize2 + 2; k1++) {
    						for (k2 = ksize2; k2 < ksize2 + 2; k2++) {
    							if (itab[k1*ksize + k2] < itab[mk1*ksize + mk2])
    								mk1 = k1, mk2 = k2;
    							else if (itab[k1*ksize + k2] > itab[Mk1*ksize + Mk2])
    								Mk1 = k1, Mk2 = k2;
    						}
    					}
    					if (diff < 0)
    						itab[Mk1*ksize + Mk2] = (short)(itab[Mk1*ksize + Mk2] - diff);
    					else
    						itab[mk1*ksize + mk2] = (short)(itab[mk1*ksize + mk2] - diff);
    				}
    			}
    		}
    		tab -= INTER_TAB_SIZE2*ksize*ksize;
    		itab -= INTER_TAB_SIZE2*ksize*ksize;
    		inittab[method] = true;
    	}
     
    	return fixpt ? (const void*)itab : (const void*)tab;
    }
     
    template<typename _Tp>
    static bool initAllInterTab2D()
    {
    	return  initInterTab2D<uchar>(INTER_LINEAR, false) &&
    		initInterTab2D<uchar>(INTER_LINEAR, true) &&
    		initInterTab2D<uchar>(INTER_CUBIC, false) &&
    		initInterTab2D<uchar>(INTER_CUBIC, true) &&
    		initInterTab2D<uchar>(INTER_LANCZOS4, false) &&
    		initInterTab2D<uchar>(INTER_LANCZOS4, true);
    }
     
    static volatile bool doInitAllInterTab2D = initAllInterTab2D<uchar>();
     
    template<typename _Tp1, typename _Tp2, int chs1, int chs2>
    static void remapNearest(const Mat_<_Tp1, chs1>& _src, Mat_<_Tp1, chs1>& _dst, const Mat_<_Tp2, chs2>& _xy, int borderType, const Scalar& _borderValue)
    {
    	Size ssize = _src.size(), dsize = _dst.size();
    	int cn = _src.channels;
    	const _Tp1* S0 = (const _Tp1*)_src.ptr();
    	size_t sstep = _src.step / sizeof(S0[0]);
    	Scalar_<_Tp1> cval(saturate_cast<_Tp1>(_borderValue[0]), saturate_cast<_Tp1>(_borderValue[1]), saturate_cast<_Tp1>(_borderValue[2]), saturate_cast<_Tp1>(_borderValue[3]));
    	int dx, dy;
     
    	unsigned width1 = ssize.width, height1 = ssize.height;
     
    	for (dy = 0; dy < dsize.height; dy++) {
    		_Tp1* D = (_Tp1*)_dst.ptr(dy);
    		const short* XY = (const short*)_xy.ptr(dy);
     
    		if (cn == 1) {
    			for (dx = 0; dx < dsize.width; dx++) {
    				int sx = XY[dx * 2], sy = XY[dx * 2 + 1];
    				if ((unsigned)sx < width1 && (unsigned)sy < height1) {
    					D[dx] = S0[sy*sstep + sx];
    				} else {
    					if (borderType == BORDER_REPLICATE) {
    						sx = clip<int>(sx, 0, ssize.width);
    						sy = clip<int>(sy, 0, ssize.height);
    						D[dx] = S0[sy*sstep + sx];
    					} else if (borderType == BORDER_CONSTANT) {
    						D[dx] = cval[0];
    					} else if (borderType != BORDER_TRANSPARENT) {
    						sx = borderInterpolate(sx, ssize.width, borderType);
    						sy = borderInterpolate(sy, ssize.height, borderType);
    						D[dx] = S0[sy*sstep + sx];
    					}
    				}
    			}
    		} else {
    			for (dx = 0; dx < dsize.width; dx++, D += cn) {
    				int sx = XY[dx * 2], sy = XY[dx * 2 + 1], k;
    				const _Tp1 *S;
    				if ((unsigned)sx < width1 && (unsigned)sy < height1) {
    					if (cn == 3) {
    						S = S0 + sy*sstep + sx * 3;
    						D[0] = S[0], D[1] = S[1], D[2] = S[2];
    					} else if (cn == 4) {
    						S = S0 + sy*sstep + sx * 4;
    						D[0] = S[0], D[1] = S[1], D[2] = S[2], D[3] = S[3];
    					} else {
    						S = S0 + sy*sstep + sx*cn;
    						for (k = 0; k < cn; k++)
    							D[k] = S[k];
    					}
    				} else if (borderType != BORDER_TRANSPARENT) {
    					if (borderType == BORDER_REPLICATE) {
    						sx = clip<int>(sx, 0, ssize.width);
    						sy = clip<int>(sy, 0, ssize.height);
    						S = S0 + sy*sstep + sx*cn;
    					} else if (borderType == BORDER_CONSTANT) {
    						S = &cval[0];
    					} else {
    						sx = borderInterpolate(sx, ssize.width, borderType);
    						sy = borderInterpolate(sy, ssize.height, borderType);
    						S = S0 + sy*sstep + sx*cn;
    					}
    					for (k = 0; k < cn; k++)
    						D[k] = S[k];
    				}
    			}
    		}
    	}
    }
     
    template<class CastOp, typename AT, typename _Tp1, typename _Tp2, typename _Tp3, int chs1, int chs2, int chs3>
    static int remapBilinear(const Mat_<_Tp1, chs1>& _src, Mat_<_Tp1, chs1>& _dst,
    	const Mat_<_Tp2, chs2>& _xy, const Mat_<_Tp3, chs3>& _fxy, const void* _wtab, int borderType, const Scalar& _borderValue)
    {
    	typedef typename CastOp::rtype T;
    	typedef typename CastOp::type1 WT;
    	Size ssize = _src.size(), dsize = _dst.size();
    	int k, cn = _src.channels;
    	const AT* wtab = (const AT*)_wtab;
    	const T* S0 = (const T*)_src.ptr();
    	size_t sstep = _src.step / sizeof(S0[0]);
    	T cval[CV_CN_MAX];
    	int dx, dy;
    	CastOp castOp;
     
    	for (k = 0; k < cn; k++)
    		cval[k] = saturate_cast<T>(_borderValue[k & 3]);
     
    	unsigned width1 = std::max(ssize.width - 1, 0), height1 = std::max(ssize.height - 1, 0);
    	FBC_Assert(ssize.area() > 0);
     
    	for (dy = 0; dy < dsize.height; dy++) {
    		T* D = (T*)_dst.ptr(dy);
    		const short* XY = (const short*)_xy.ptr(dy);
    		const ushort* FXY = (const ushort*)_fxy.ptr(dy);
    		int X0 = 0;
    		bool prevInlier = false;
     
    		for (dx = 0; dx <= dsize.width; dx++) {
    			bool curInlier = dx < dsize.width ? (unsigned)XY[dx * 2] < width1 && (unsigned)XY[dx * 2 + 1] < height1 : !prevInlier;
    			if (curInlier == prevInlier)
    				continue;
     
    			int X1 = dx;
    			dx = X0;
    			X0 = X1;
    			prevInlier = curInlier;
     
    			if (!curInlier) {
    				int len = 0;
    				D += len*cn;
    				dx += len;
     
    				if (cn == 1) {
    					for (; dx < X1; dx++, D++) {
    						int sx = XY[dx * 2], sy = XY[dx * 2 + 1];
    						const AT* w = wtab + FXY[dx] * 4;
    						const T* S = S0 + sy*sstep + sx;
    						*D = castOp(WT(S[0] * w[0] + S[1] * w[1] + S[sstep] * w[2] + S[sstep + 1] * w[3]));
    					}
    				} else if (cn == 2) {
    					for (; dx < X1; dx++, D += 2) {
    						int sx = XY[dx * 2], sy = XY[dx * 2 + 1];
    						const AT* w = wtab + FXY[dx] * 4;
    						const T* S = S0 + sy*sstep + sx * 2;
    						WT t0 = S[0] * w[0] + S[2] * w[1] + S[sstep] * w[2] + S[sstep + 2] * w[3];
    						WT t1 = S[1] * w[0] + S[3] * w[1] + S[sstep + 1] * w[2] + S[sstep + 3] * w[3];
    						D[0] = castOp(t0); D[1] = castOp(t1);
    					}
    				} else if (cn == 3) {
    					for (; dx < X1; dx++, D += 3) {
    						int sx = XY[dx * 2], sy = XY[dx * 2 + 1];
    						const AT* w = wtab + FXY[dx] * 4;
    						const T* S = S0 + sy*sstep + sx * 3;
    						WT t0 = S[0] * w[0] + S[3] * w[1] + S[sstep] * w[2] + S[sstep + 3] * w[3];
    						WT t1 = S[1] * w[0] + S[4] * w[1] + S[sstep + 1] * w[2] + S[sstep + 4] * w[3];
    						WT t2 = S[2] * w[0] + S[5] * w[1] + S[sstep + 2] * w[2] + S[sstep + 5] * w[3];
    						D[0] = castOp(t0); D[1] = castOp(t1); D[2] = castOp(t2);
    					}
    				} else if (cn == 4) {
    					for (; dx < X1; dx++, D += 4) {
    						int sx = XY[dx * 2], sy = XY[dx * 2 + 1];
    						const AT* w = wtab + FXY[dx] * 4;
    						const T* S = S0 + sy*sstep + sx * 4;
    						WT t0 = S[0] * w[0] + S[4] * w[1] + S[sstep] * w[2] + S[sstep + 4] * w[3];
    						WT t1 = S[1] * w[0] + S[5] * w[1] + S[sstep + 1] * w[2] + S[sstep + 5] * w[3];
    						D[0] = castOp(t0); D[1] = castOp(t1);
    						t0 = S[2] * w[0] + S[6] * w[1] + S[sstep + 2] * w[2] + S[sstep + 6] * w[3];
    						t1 = S[3] * w[0] + S[7] * w[1] + S[sstep + 3] * w[2] + S[sstep + 7] * w[3];
    						D[2] = castOp(t0); D[3] = castOp(t1);
    					}
    				} else {
    					for (; dx < X1; dx++, D += cn) {
    						int sx = XY[dx * 2], sy = XY[dx * 2 + 1];
    						const AT* w = wtab + FXY[dx] * 4;
    						const T* S = S0 + sy*sstep + sx*cn;
    						for (k = 0; k < cn; k++) {
    							WT t0 = S[k] * w[0] + S[k + cn] * w[1] + S[sstep + k] * w[2] + S[sstep + k + cn] * w[3];
    							D[k] = castOp(t0);
    						}
    					}
    				}
    			} else {
    				if (borderType == BORDER_TRANSPARENT && cn != 3) {
    					D += (X1 - dx)*cn;
    					dx = X1;
    					continue;
    				}
     
    				if (cn == 1) {
    					for (; dx < X1; dx++, D++) {
    						int sx = XY[dx * 2], sy = XY[dx * 2 + 1];
    						//===BORDER_CONSTANT模式下,映射点超出dst范围,则跳过
    						if (borderType == BORDER_CONSTANT && (sx >= ssize.width || sx + 1 < 0 || sy >= ssize.height || sy + 1 < 0)) {
    							D[0] = cval[0];
    						} else {
    							int sx0, sx1, sy0, sy1;
    							T v0, v1, v2, v3;
    							const AT* w = wtab + FXY[dx] * 4;
    							if (borderType == BORDER_REPLICATE) {
    								sx0 = clip(sx, 0, ssize.width);
    								sx1 = clip(sx + 1, 0, ssize.width);
    								sy0 = clip(sy, 0, ssize.height);
    								sy1 = clip(sy + 1, 0, ssize.height);
    								v0 = S0[sy0*sstep + sx0];
    								v1 = S0[sy0*sstep + sx1];
    								v2 = S0[sy1*sstep + sx0];
    								v3 = S0[sy1*sstep + sx1];
    							} else {
    								sx0 = borderInterpolate(sx, ssize.width, borderType);//===此BORDER_CONSTANT模式下,超出图像范围外,borderInterpolate返回-1
    								sx1 = borderInterpolate(sx + 1, ssize.width, borderType);
    								sy0 = borderInterpolate(sy, ssize.height, borderType);
    								sy1 = borderInterpolate(sy + 1, ssize.height, borderType);
    								v0 = sx0 >= 0 && sy0 >= 0 ? S0[sy0*sstep + sx0] : cval[0];
    								v1 = sx1 >= 0 && sy0 >= 0 ? S0[sy0*sstep + sx1] : cval[0];
    								v2 = sx0 >= 0 && sy1 >= 0 ? S0[sy1*sstep + sx0] : cval[0];
    								v3 = sx1 >= 0 && sy1 >= 0 ? S0[sy1*sstep + sx1] : cval[0];
    							}
    							D[0] = castOp(WT(v0*w[0] + v1*w[1] + v2*w[2] + v3*w[3]));
    						}
    					}
    				} else {
    					for (; dx < X1; dx++, D += cn) {
    						int sx = XY[dx * 2], sy = XY[dx * 2 + 1];
    						if (borderType == BORDER_CONSTANT && (sx >= ssize.width || sx + 1 < 0 || sy >= ssize.height || sy + 1 < 0)) {
    							for (k = 0; k < cn; k++)
    								D[k] = cval[k];
    						} else {
    							int sx0, sx1, sy0, sy1;
    							const T *v0, *v1, *v2, *v3;
    							const AT* w = wtab + FXY[dx] * 4;
    							if (borderType == BORDER_REPLICATE) {
    								sx0 = clip(sx, 0, ssize.width);
    								sx1 = clip(sx + 1, 0, ssize.width);
    								sy0 = clip(sy, 0, ssize.height);
    								sy1 = clip(sy + 1, 0, ssize.height);
    								v0 = S0 + sy0*sstep + sx0*cn;
    								v1 = S0 + sy0*sstep + sx1*cn;
    								v2 = S0 + sy1*sstep + sx0*cn;
    								v3 = S0 + sy1*sstep + sx1*cn;
    							} else if (borderType == BORDER_TRANSPARENT && ((unsigned)sx >= (unsigned)(ssize.width - 1) || (unsigned)sy >= (unsigned)(ssize.height - 1))) {
    								continue;
    							} else {
    								sx0 = borderInterpolate(sx, ssize.width, borderType);
    								sx1 = borderInterpolate(sx + 1, ssize.width, borderType);
    								sy0 = borderInterpolate(sy, ssize.height, borderType);
    								sy1 = borderInterpolate(sy + 1, ssize.height, borderType);
    								v0 = sx0 >= 0 && sy0 >= 0 ? S0 + sy0*sstep + sx0*cn : &cval[0];
    								v1 = sx1 >= 0 && sy0 >= 0 ? S0 + sy0*sstep + sx1*cn : &cval[0];
    								v2 = sx0 >= 0 && sy1 >= 0 ? S0 + sy1*sstep + sx0*cn : &cval[0];
    								v3 = sx1 >= 0 && sy1 >= 0 ? S0 + sy1*sstep + sx1*cn : &cval[0];
    							}
    							for (k = 0; k < cn; k++)
    								D[k] = castOp(WT(v0[k] * w[0] + v1[k] * w[1] + v2[k] * w[2] + v3[k] * w[3]));
    						}
    					}
    				}
    			}
    		}
    	}
     
    	return 0;
    }
     
    template<class CastOp, typename AT, int ONE, typename _Tp1, typename _Tp2, typename _Tp3, int chs1, int chs2, int chs3>
    static int remapBicubic(const Mat_<_Tp1, chs1>& _src, Mat_<_Tp1, chs1>& _dst,
    	const Mat_<_Tp2, chs2>& _xy, const Mat_<_Tp3, chs3>& _fxy, const void* _wtab, int borderType, const Scalar& _borderValue)
    {
    	typedef typename CastOp::rtype T;
    	typedef typename CastOp::type1 WT;
    	Size ssize = _src.size(), dsize = _dst.size();
    	int cn = _src.channels;
    	const AT* wtab = (const AT*)_wtab;
    	const T* S0 = (const T*)_src.ptr();
    	size_t sstep = _src.step / sizeof(S0[0]);
    	Scalar_<T> cval(saturate_cast<T>(_borderValue[0]),
    		saturate_cast<T>(_borderValue[1]),
    		saturate_cast<T>(_borderValue[2]),
    		saturate_cast<T>(_borderValue[3]));
    	int dx, dy;
    	CastOp castOp;
    	int borderType1 = borderType != BORDER_TRANSPARENT ? borderType : BORDER_REFLECT_101;
     
    	unsigned width1 = std::max(ssize.width - 3, 0), height1 = std::max(ssize.height - 3, 0);
     
    	for (dy = 0; dy < dsize.height; dy++) {
    		T* D = (T*)_dst.ptr(dy);
    		const short* XY = (const short*)_xy.ptr(dy);
    		const ushort* FXY = (const ushort*)_fxy.ptr(dy);
     
    		for (dx = 0; dx < dsize.width; dx++, D += cn) {
    			int sx = XY[dx * 2] - 1, sy = XY[dx * 2 + 1] - 1;
    			const AT* w = wtab + FXY[dx] * 16;
    			int i, k;
    			if ((unsigned)sx < width1 && (unsigned)sy < height1) {
    				const T* S = S0 + sy*sstep + sx*cn;
    				for (k = 0; k < cn; k++) {
    					WT sum = S[0] * w[0] + S[cn] * w[1] + S[cn * 2] * w[2] + S[cn * 3] * w[3];
    					S += sstep;
    					sum += S[0] * w[4] + S[cn] * w[5] + S[cn * 2] * w[6] + S[cn * 3] * w[7];
    					S += sstep;
    					sum += S[0] * w[8] + S[cn] * w[9] + S[cn * 2] * w[10] + S[cn * 3] * w[11];
    					S += sstep;
    					sum += S[0] * w[12] + S[cn] * w[13] + S[cn * 2] * w[14] + S[cn * 3] * w[15];
    					S += 1 - sstep * 3;
    					D[k] = castOp(sum);
    				}
    			} else {
    				int x[4], y[4];
    				if (borderType == BORDER_TRANSPARENT &&
    					((unsigned)(sx + 1) >= (unsigned)ssize.width ||
    					(unsigned)(sy + 1) >= (unsigned)ssize.height))
    					continue;
     
    				if (borderType1 == BORDER_CONSTANT &&
    					(sx >= ssize.width || sx + 4 <= 0 ||
    					sy >= ssize.height || sy + 4 <= 0)) {
    					for (k = 0; k < cn; k++)
    						D[k] = cval[k];
    					continue;
    				}
     
    				for (i = 0; i < 4; i++) {
    					x[i] = borderInterpolate(sx + i, ssize.width, borderType1)*cn;
    					y[i] = borderInterpolate(sy + i, ssize.height, borderType1);
    				}
     
    				for (k = 0; k < cn; k++, S0++, w -= 16) {
    					WT cv = cval[k], sum = cv*ONE;
    					for (i = 0; i < 4; i++, w += 4) {
    						int yi = y[i];
    						const T* S = S0 + yi*sstep;
    						if (yi < 0)
    							continue;
    						if (x[0] >= 0)
    							sum += (S[x[0]] - cv)*w[0];
    						if (x[1] >= 0)
    							sum += (S[x[1]] - cv)*w[1];
    						if (x[2] >= 0)
    							sum += (S[x[2]] - cv)*w[2];
    						if (x[3] >= 0)
    							sum += (S[x[3]] - cv)*w[3];
    					}
    					D[k] = castOp(sum);
    				}
    				S0 -= cn;
    			}
    		}
    	}
     
    	return 0;
    }
     
    template<class CastOp, typename AT, int ONE, typename _Tp1, typename _Tp2, typename _Tp3, int chs1, int chs2, int chs3>
    static int remapLanczos4(const Mat_<_Tp1, chs1>& _src, Mat_<_Tp1, chs1>& _dst,
    	const Mat_<_Tp2, chs2>& _xy, const Mat_<_Tp3, chs3>& _fxy, const void* _wtab, int borderType, const Scalar& _borderValue)
    {
    	typedef typename CastOp::rtype T;
    	typedef typename CastOp::type1 WT;
    	Size ssize = _src.size(), dsize = _dst.size();
    	int cn = _src.channels;
    	const AT* wtab = (const AT*)_wtab;
    	const T* S0 = (const T*)_src.ptr();
    	size_t sstep = _src.step / sizeof(S0[0]);
    	Scalar_<T> cval(saturate_cast<T>(_borderValue[0]),
    		saturate_cast<T>(_borderValue[1]),
    		saturate_cast<T>(_borderValue[2]),
    		saturate_cast<T>(_borderValue[3]));
    	int dx, dy;
    	CastOp castOp;
    	int borderType1 = borderType != BORDER_TRANSPARENT ? borderType : BORDER_REFLECT_101;
     
    	unsigned width1 = std::max(ssize.width - 7, 0), height1 = std::max(ssize.height - 7, 0);
     
    	for (dy = 0; dy < dsize.height; dy++) {
    		T* D = (T*)_dst.ptr(dy);
    		const short* XY = (const short*)_xy.ptr(dy);
    		const ushort* FXY = (const ushort*)_fxy.ptr(dy);
     
    		for (dx = 0; dx < dsize.width; dx++, D += cn) {
    			int sx = XY[dx * 2] - 3, sy = XY[dx * 2 + 1] - 3;
    			const AT* w = wtab + FXY[dx] * 64;
    			const T* S = S0 + sy*sstep + sx*cn;
    			int i, k;
    			if ((unsigned)sx < width1 && (unsigned)sy < height1) {
    				for (k = 0; k < cn; k++) {
    					WT sum = 0;
    					for (int r = 0; r < 8; r++, S += sstep, w += 8)
    						sum += S[0] * w[0] + S[cn] * w[1] + S[cn * 2] * w[2] + S[cn * 3] * w[3] +
    						S[cn * 4] * w[4] + S[cn * 5] * w[5] + S[cn * 6] * w[6] + S[cn * 7] * w[7];
    					w -= 64;
    					S -= sstep * 8 - 1;
    					D[k] = castOp(sum);
    				}
    			} else {
    				int x[8], y[8];
    				if (borderType == BORDER_TRANSPARENT &&
    					((unsigned)(sx + 3) >= (unsigned)ssize.width ||
    					(unsigned)(sy + 3) >= (unsigned)ssize.height))
    					continue;
     
    				if (borderType1 == BORDER_CONSTANT &&
    					(sx >= ssize.width || sx + 8 <= 0 ||
    					sy >= ssize.height || sy + 8 <= 0)) {
    					for (k = 0; k < cn; k++)
    						D[k] = cval[k];
    					continue;
    				}
     
    				for (i = 0; i < 8; i++) {
    					x[i] = borderInterpolate(sx + i, ssize.width, borderType1)*cn;
    					y[i] = borderInterpolate(sy + i, ssize.height, borderType1);
    				}
     
    				for (k = 0; k < cn; k++, S0++, w -= 64) {
    					WT cv = cval[k], sum = cv*ONE;
    					for (i = 0; i < 8; i++, w += 8) {
    						int yi = y[i];
    						const T* S1 = S0 + yi*sstep;
    						if (yi < 0)
    							continue;
    						if (x[0] >= 0)
    							sum += (S1[x[0]] - cv)*w[0];
    						if (x[1] >= 0)
    							sum += (S1[x[1]] - cv)*w[1];
    						if (x[2] >= 0)
    							sum += (S1[x[2]] - cv)*w[2];
    						if (x[3] >= 0)
    							sum += (S1[x[3]] - cv)*w[3];
    						if (x[4] >= 0)
    							sum += (S1[x[4]] - cv)*w[4];
    						if (x[5] >= 0)
    							sum += (S1[x[5]] - cv)*w[5];
    						if (x[6] >= 0)
    							sum += (S1[x[6]] - cv)*w[6];
    						if (x[7] >= 0)
    							sum += (S1[x[7]] - cv)*w[7];
    					}
    					D[k] = castOp(sum);
    				}
    				S0 -= cn;
    			}
    		}
    	}
     
    	return 0;
    }
     
    template<typename _Tp1, typename _Tp2, int chs1, int chs2>
    static int remap_nearest(const Mat_<_Tp1, chs1>& src, Mat_<_Tp1, chs1>& dst,
    	const Mat_<_Tp2, chs2>& map1, const Mat_<_Tp2, chs2>& map2, int borderMode, const Scalar& borderValue)
    {
    	const void* ctab = 0;
    	bool fixpt = typeid(uchar).name() == typeid(_Tp1).name();
    	bool planar_input = map1.channels == 1;
    	Range range(0, dst.rows);
     
    	int x, y, x1, y1;
    	const int buf_size = 1 << 14;
    	int brows0 = std::min(128, dst.rows);
    	int bcols0 = std::min(buf_size / brows0, dst.cols);
    	brows0 = std::min(buf_size / bcols0, dst.rows);
     
    	Mat_<short, 2> _bufxy(brows0, bcols0);
     
    	for (y = range.start; y < range.end; y += brows0) {
    		for (x = 0; x < dst.cols; x += bcols0) {
    			int brows = std::min(brows0, range.end - y);
    			int bcols = std::min(bcols0, dst.cols - x);
    			Mat_<_Tp1, chs1> dpart;
    			dst.getROI(dpart, Rect(x, y, bcols, brows));
    			Mat_<short, 2> bufxy;
    			_bufxy.getROI(bufxy, Rect(0, 0, bcols, brows));
     
    			if (sizeof(_Tp2) == sizeof(short)) { // short
    				for (y1 = 0; y1 < brows; y1++) {
    					short* XY = (short*)bufxy.ptr(y1);
    					const short* sXY = (const short*)map1.ptr(y + y1) + x * 2;
    					const ushort* sA = (const ushort*)map2.ptr(y + y1) + x;
     
    					for (x1 = 0; x1 < bcols; x1++) {
    						int a = sA[x1] & (INTER_TAB_SIZE2 - 1);
    						XY[x1 * 2] = sXY[x1 * 2] + NNDeltaTab_i[a][0];
    						XY[x1 * 2 + 1] = sXY[x1 * 2 + 1] + NNDeltaTab_i[a][1];
    					}
    				}
    			} else { // float
    				for (y1 = 0; y1 < brows; y1++) {
    					short* XY = (short*)bufxy.ptr(y1);
    					const float* sX = (const float*)map1.ptr(y + y1) + x;
    					const float* sY = (const float*)map2.ptr(y + y1) + x;
     
    					x1 = 0;
    					for (; x1 < bcols; x1++) {
    						XY[x1 * 2] = saturate_cast<short>(sX[x1]);
    						XY[x1 * 2 + 1] = saturate_cast<short>(sY[x1]);
    					}
    				}
    			}
     
    			remapNearest<_Tp1, short, chs1, 2>(src, dpart, bufxy, borderMode, borderValue);
    		}
    	}
     
    	return 0;
    }
     
    template<typename _Tp1, typename _Tp2, int chs1, int chs2>
    static int remap_linear(const Mat_<_Tp1, chs1>& src, Mat_<_Tp1, chs1>& dst,
    	const Mat_<_Tp2, chs2>& map1, const Mat_<_Tp2, chs2>& map2, int borderMode, const Scalar& borderValue)
    {
    	const void* ctab = 0;
    	bool fixpt = typeid(uchar).name() == typeid(_Tp1).name();
    	bool planar_input = map1.channels == 1;
    	ctab = initInterTab2D<_Tp1>(INTER_LINEAR, fixpt);
    	Range range(0, dst.rows);
     
    	int x, y, x1, y1;
    	const int buf_size = 1 << 14;
    	int brows0 = std::min(128, dst.rows);
    	int bcols0 = std::min(buf_size / brows0, dst.cols);
    	brows0 = std::min(buf_size / bcols0, dst.rows);
     
    	Mat_<short, 2> _bufxy(brows0, bcols0);
    	Mat_<ushort, 1> _bufa(brows0, bcols0);
     
    	for (y = range.start; y < range.end; y += brows0) {
    		for (x = 0; x < dst.cols; x += bcols0) {
    			int brows = std::min(brows0, range.end - y);
    			int bcols = std::min(bcols0, dst.cols - x);
    			Mat_<_Tp1, chs1> dpart;
    			dst.getROI(dpart, Rect(x, y, bcols, brows));
    			Mat_<short, 2> bufxy;
    			_bufxy.getROI(bufxy, Rect(0, 0, bcols, brows));
    			Mat_<ushort, 1> bufa;
    			_bufa.getROI(bufa, Rect(0, 0, bcols, brows));
     
    			for (y1 = 0; y1 < brows; y1++) {
    				short* XY = (short*)bufxy.ptr(y1);
    				ushort* A = (ushort*)bufa.ptr(y1);
     
    				if (planar_input) {
    					const float* sX = (const float*)map1.ptr(y + y1) + x;
    					const float* sY = (const float*)map2.ptr(y + y1) + x;
     
    					x1 = 0;
    					for (; x1 < bcols; x1++) {
    						int sx = fbcRound(sX[x1] * INTER_TAB_SIZE);
    						int sy = fbcRound(sY[x1] * INTER_TAB_SIZE);
    						int v = (sy & (INTER_TAB_SIZE - 1))*INTER_TAB_SIZE + (sx & (INTER_TAB_SIZE - 1));
    						XY[x1 * 2] = saturate_cast<short>(sx >> INTER_BITS);
    						XY[x1 * 2 + 1] = saturate_cast<short>(sy >> INTER_BITS);
    						A[x1] = (ushort)v;
    					}
    				} else {
    					const float* sXY = (const float*)map1.ptr(y + y1) + x * 2;
    					x1 = 0;
    					for (x1 = 0; x1 < bcols; x1++) {
    						int sx = fbcRound(sXY[x1 * 2] * INTER_TAB_SIZE);
    						int sy = fbcRound(sXY[x1 * 2 + 1] * INTER_TAB_SIZE);
    						int v = (sy & (INTER_TAB_SIZE - 1))*INTER_TAB_SIZE + (sx & (INTER_TAB_SIZE - 1));
    						XY[x1 * 2] = saturate_cast<short>(sx >> INTER_BITS);
    						XY[x1 * 2 + 1] = saturate_cast<short>(sy >> INTER_BITS);
    						A[x1] = (ushort)v;
    					}
    				}
    			}
     
    			if (typeid(_Tp1).name() == typeid(uchar).name()) { // uchar
    				remapBilinear<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, _Tp1, short, ushort, chs1, 2, 1>(src, dpart, bufxy, bufa, ctab, borderMode, borderValue);
    			} else { // float
    				remapBilinear<Cast<float, float>, float, _Tp1, short, ushort, chs1, 2, 1>(src, dpart, bufxy, bufa, ctab, borderMode, borderValue);
    			}
    		}
    	}
     
    	return 0;
    }
     
    template<typename _Tp1, typename _Tp2, int chs1, int chs2>
    static int remap_cubic(const Mat_<_Tp1, chs1>& src, Mat_<_Tp1, chs1>& dst,
    	const Mat_<_Tp2, chs2>& map1, const Mat_<_Tp2, chs2>& map2, int borderMode, const Scalar& borderValue)
    {
    	const void* ctab = 0;
    	bool fixpt = typeid(uchar).name() == typeid(_Tp1).name();
    	bool planar_input = map1.channels == 1;
    	ctab = initInterTab2D<_Tp1>(INTER_CUBIC, fixpt);
    	Range range(0, dst.rows);
     
    	int x, y, x1, y1;
    	const int buf_size = 1 << 14;
    	int brows0 = std::min(128, dst.rows);
    	int bcols0 = std::min(buf_size / brows0, dst.cols);
    	brows0 = std::min(buf_size / bcols0, dst.rows);
     
    	Mat_<short, 2> _bufxy(brows0, bcols0);
    	Mat_<ushort, 1> _bufa(brows0, bcols0);
     
    	for (y = range.start; y < range.end; y += brows0) {
    		for (x = 0; x < dst.cols; x += bcols0) {
    			int brows = std::min(brows0, range.end - y);
    			int bcols = std::min(bcols0, dst.cols - x);
    			Mat_<_Tp1, chs1> dpart;
    			dst.getROI(dpart, Rect(x, y, bcols, brows));
    			Mat_<short, 2> bufxy;
    			_bufxy.getROI(bufxy, Rect(0, 0, bcols, brows));
    			Mat_<ushort, 1> bufa;
    			_bufa.getROI(bufa, Rect(0, 0, bcols, brows));
     
    			for (y1 = 0; y1 < brows; y1++) {
    				short* XY = (short*)bufxy.ptr(y1);
    				ushort* A = (ushort*)bufa.ptr(y1);
     
    				if (planar_input) {
    					const float* sX = (const float*)map1.ptr(y + y1) + x;
    					const float* sY = (const float*)map2.ptr(y + y1) + x;
     
    					x1 = 0;
    					for (; x1 < bcols; x1++) {
    						int sx = fbcRound(sX[x1] * INTER_TAB_SIZE);
    						int sy = fbcRound(sY[x1] * INTER_TAB_SIZE);
    						int v = (sy & (INTER_TAB_SIZE - 1))*INTER_TAB_SIZE + (sx & (INTER_TAB_SIZE - 1));
    						XY[x1 * 2] = saturate_cast<short>(sx >> INTER_BITS);
    						XY[x1 * 2 + 1] = saturate_cast<short>(sy >> INTER_BITS);
    						A[x1] = (ushort)v;
    					}
    				} else {
    					const float* sXY = (const float*)map1.ptr(y + y1) + x * 2;
    					x1 = 0;
    					for (x1 = 0; x1 < bcols; x1++) {
    						int sx = fbcRound(sXY[x1 * 2] * INTER_TAB_SIZE);
    						int sy = fbcRound(sXY[x1 * 2 + 1] * INTER_TAB_SIZE);
    						int v = (sy & (INTER_TAB_SIZE - 1))*INTER_TAB_SIZE + (sx & (INTER_TAB_SIZE - 1));
    						XY[x1 * 2] = saturate_cast<short>(sx >> INTER_BITS);
    						XY[x1 * 2 + 1] = saturate_cast<short>(sy >> INTER_BITS);
    						A[x1] = (ushort)v;
    					}
    				}
    			}
     
    			if (typeid(_Tp1).name() == typeid(uchar).name()) { // uchar
    				remapBicubic<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, INTER_REMAP_COEF_SCALE, _Tp1, short, ushort, chs1, 2, 1>(src, dpart, bufxy, bufa, ctab, borderMode, borderValue);
    			} else { // float
    				remapBicubic<Cast<float, float>, float, 1, _Tp1, short, ushort, chs1, 2, 1>(src, dpart, bufxy, bufa, ctab, borderMode, borderValue);
    			}
    		}
    	}
     
    	return 0;
    }
     
    template<typename _Tp1, typename _Tp2, int chs1, int chs2>
    static int remap_lanczos4(const Mat_<_Tp1, chs1>& src, Mat_<_Tp1, chs1>& dst,
    	const Mat_<_Tp2, chs2>& map1, const Mat_<_Tp2, chs2>& map2, int borderMode, const Scalar& borderValue)
    {
    	const void* ctab = 0;
    	bool fixpt = typeid(uchar).name() == typeid(_Tp1).name();
    	bool planar_input = map1.channels == 1;
    	ctab = initInterTab2D<_Tp1>(INTER_LANCZOS4, fixpt);
    	Range range(0, dst.rows);
     
    	int x, y, x1, y1;
    	const int buf_size = 1 << 14;
    	int brows0 = std::min(128, dst.rows);
    	int bcols0 = std::min(buf_size / brows0, dst.cols);
    	brows0 = std::min(buf_size / bcols0, dst.rows);
     
    	Mat_<short, 2> _bufxy(brows0, bcols0);
    	Mat_<ushort, 1> _bufa(brows0, bcols0);
     
    	for (y = range.start; y < range.end; y += brows0) {
    		for (x = 0; x < dst.cols; x += bcols0) {
    			int brows = std::min(brows0, range.end - y);
    			int bcols = std::min(bcols0, dst.cols - x);
    			Mat_<_Tp1, chs1> dpart;
    			dst.getROI(dpart, Rect(x, y, bcols, brows));
    			Mat_<short, 2> bufxy;
    			_bufxy.getROI(bufxy, Rect(0, 0, bcols, brows));
    			Mat_<ushort, 1> bufa;
    			_bufa.getROI(bufa, Rect(0, 0, bcols, brows));
     
    			for (y1 = 0; y1 < brows; y1++) {
    				short* XY = (short*)bufxy.ptr(y1);
    				ushort* A = (ushort*)bufa.ptr(y1);
     
    				if (planar_input) {
    					const float* sX = (const float*)map1.ptr(y + y1) + x;
    					const float* sY = (const float*)map2.ptr(y + y1) + x;
     
    					x1 = 0;
    					for (; x1 < bcols; x1++) {
    						int sx = fbcRound(sX[x1] * INTER_TAB_SIZE);
    						int sy = fbcRound(sY[x1] * INTER_TAB_SIZE);
    						int v = (sy & (INTER_TAB_SIZE - 1))*INTER_TAB_SIZE + (sx & (INTER_TAB_SIZE - 1));
    						XY[x1 * 2] = saturate_cast<short>(sx >> INTER_BITS);
    						XY[x1 * 2 + 1] = saturate_cast<short>(sy >> INTER_BITS);
    						A[x1] = (ushort)v;
    					}
    				} else {
    					const float* sXY = (const float*)map1.ptr(y + y1) + x * 2;
    					x1 = 0;
    					for (x1 = 0; x1 < bcols; x1++) {
    						int sx = fbcRound(sXY[x1 * 2] * INTER_TAB_SIZE);
    						int sy = fbcRound(sXY[x1 * 2 + 1] * INTER_TAB_SIZE);
    						int v = (sy & (INTER_TAB_SIZE - 1))*INTER_TAB_SIZE + (sx & (INTER_TAB_SIZE - 1));
    						XY[x1 * 2] = saturate_cast<short>(sx >> INTER_BITS);
    						XY[x1 * 2 + 1] = saturate_cast<short>(sy >> INTER_BITS);
    						A[x1] = (ushort)v;
    					}
    				}
    			}
     
    			if (typeid(_Tp1).name() == typeid(uchar).name()) { // uchar
    				remapLanczos4<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, INTER_REMAP_COEF_SCALE, _Tp1, short, ushort, chs1, 2, 1>(src, dpart, bufxy, bufa, ctab, borderMode, borderValue);
    			}
    			else { // float
    				remapLanczos4<Cast<float, float>, float, 1, _Tp1, short, ushort, chs1, 2, 1>(src, dpart, bufxy, bufa, ctab, borderMode, borderValue);
    			}
    		}
    	}
     
    	return 0;
    }
     
    } // namespace fbc
     
    #endif // FBC_CV_REMAP_HPP_

    展开全文
  • 包含各种仿射变换的程序,即平移、旋转、均匀和非均匀缩放、正投影、斜投影和透视投影。 它还包含面部绘图功能,可以轻松地针对特定问题... 这可以在代码仿射变换阶段之后完成。 法向量和初始状态需要由用户定义。
  • 本篇文章详细的介绍了各种操作的实现原理,以及代码的实现和使用仿射变换仿射变换也称仿射投影,是指几何中,对一个向量空间进行线性变换并接上一个平移,变换为另一个向量空间。所以,仿射变换其实也就是在讲如何来...

    导读

    在图像处理中,我们经常需要对图像进行各种操作如平移缩放旋转翻转等,这些操作都属于图像的仿射变换,我们通过一个变换矩阵就能很容易的实现。本篇文章详细的介绍了各种操作的实现原理,以及代码的实现和使用

    仿射变换

    仿射变换也称仿射投影,是指几何中,对一个向量空间进行线性变换并接上一个平移,变换为另一个向量空间。所以,仿射变换其实也就是在讲如何来进行两个向量空间的变换

    假设有一个向量空间k:

    9f555a0227775b44326bd7f75adde823.png

    向量空间k

    和另一个向量空间j:

    43f2c7c909f17662af8191a615b71c42.png

    向量空间j

    如果我们想要将向量空间由k变为j,可以通过下面的公式来实现:

    628b7a61d2066a4d258e59b65951d69c.png

    向量空间变换

    将上式进行拆分可得

    e84e33f4bfa9ca6d972c7d74965d0288.png

    向量空间坐标变换

    我们再将上式转换为矩阵的乘法

    1d6ef379b8c3cbc5272eb4c231fd5c92.png

    向量空间变换的矩阵表示

    上式表明通过一个两行三列的矩阵M就可以实现两个向量空间之间的转换,在进行仿射变换的时候我们也只需要一个矩阵M就可以实现图像的平移缩放旋转翻转变换。

    接下来,会先介绍原理然后利用OpenCV来实现相应的例子,这里主要利用OpenCV的warpAffine函数来实现仿射变换

    warpAffine函数参数:

    • src:输入的图像数组
    • M:仿射变换矩阵
    • dsize:变换后图像的大小
    • flags:使用的插值算法
    • borderValue:边界的填充值

    图像平移

    在平面坐标系有点P(x,y)和点P′(x′,y′),如果我们想要将P点移动到P′通过下面的变换就可以实现

    80a039a631d909cbe58282acf145e3c1.png

    坐标平移

    其中ΔxΔy就是x方向上和y方向上的偏移量,我们将其转换为矩阵的形式上面的矩阵MM就是仿射变换的平移参数,接下来我们利用OpenCV中的warpAffine函数来实现

    6bf3ed94e6ee626c6bb2555ea657e5ee.png

    坐标平移的矩阵形式

    上面的矩阵M就是仿射变换的平移参数,接下来我们利用OpenCV中的warpAffine函数来实现图像的平移

    f496932abc233f2cd8c9045a8ff4995e.png

    仿射变换实现图像平移

    ee9d4ba3c7fa04c3b9fb3f2040cc1d48.png

    图像平移

    图像翻转

    有时候我们我们需要对图像进行水平翻转垂直翻转镜像翻转(同时进行水平和垂直翻转),想要实现这个功能并不难,我们可以通过opencv内置的flip方法很容易实现,还可以通过numpy的索引来实现,当然也可以通过仿射变换矩阵来实现这个功能

    fdb1238d80daad02c1588b82b4a5d547.png

    上图中的ABCD表示图像的四个顶点,如果我们需要对图像进行水平翻转,那么我们就需要将A点和B点进行交换,C点和D点进行交换,沿着x轴的中线进行对称交换位置,通过下面的式子可以实现水平翻转

    4b21c816c252d6c9f5407491a6db8390.png

    水平翻转

    上式中的w表示图像的宽,同理可得垂直翻转的实现公式

    29ffb6165270e02901c77be87680084a.png

    垂直翻转

    上式中的h表示的是图像的高

    • 利用变换矩阵翻转图像

    水平翻转的变换矩阵M:

    095fa5c29435b4e8a0659cd846228361.png

    仿射变换水平翻转矩阵

    垂直翻转的变换矩阵M:

    0704d9c103181a0ec708caeea3ba2ec8.png

    仿射变换垂直翻转矩阵

    镜像翻转的变换矩阵M:

    a2add0167fdb7a22e04cf3389f5e761a.png

    仿射变换镜像翻转矩阵

    241d2bcc28c0b57190e9ee69d00017f6.png

    仿射变换实现图像的镜像翻转

    724868e046d4019640be290d5f25159f.png

    镜像翻转

    • OpenCV的flip函数翻转图像

    flip函数参数:

    • src:输入的图像数组
    • flipCode:图像翻转参数,1表示水平翻转,0表示垂直翻转,-1表示镜像翻转
    ad74ec17d72785bc73d6a139660039be.png

    flip函数实现图像翻转

    • numpy的索引翻转图像
    a48a16a67e42546201deb3ca76c5734f.png

    numpy通过索引数组实现图像翻转

    图像缩放

    如果我们想要对坐标系的P点进行缩放操作,通过下面的公式就可以实现

    25cf6e0b96ef6704b30350ed9326a7e4.png

    图像缩放

    通过在x和y前面添加一个缩放系数即可,同样我们将其转换为矩阵形式

    840510f4636cdaf4d19436a7feb1f0f7.png

    图像缩放的矩阵形式

    通过上面的矩阵M我们就可以实现对图片的缩放

    d4b1afb1b183b7a3827ca57a56c119d2.png

    仿射变换实现图像缩放

    ec68047b346305ec96eeca845cb27e13.png

    仿射变换实现图像缩放

    这里使用仿射变换实现的图片缩放其实和resize函数的效果是一样的,缩放时选择的插值函数不同最终效果会有所偏差

    图像旋转

    • 围绕原点旋转

    我们先来看看一个二维平面上的点在围绕原点是如何旋转的

    b0477fc964426394afb952b648711426.png

    任意一点围绕原点旋转

    上图中点v围绕原点旋转θ度之后得到了点v′,我们将坐标点用极坐标的形式来表示可以得到v(rcosϕ,rsinϕ),所以v′(rcos(θ+ϕ),rsin(θ+ϕ))利用正弦和余弦展开式将其展开可得,对于v点来说:

    1255df84ba2fcb2515ce29f992903c95.png

    v点坐标展开式

    对于v′来说:

    15dbce48a5400088bc6ab282a283a6e5.png

    v'点坐标展开式

    然后我们再将x和y代入上式可得

    a2e286f4543b0fcff738972a559dc367.png

    变换后的x和y的坐标

    然后再将上式用矩阵M表示,可得

    8fe93fcab086195a6bda943dd99bbd97.png

    旋转后坐标的矩阵表示形式

    特别注意:我们在建立直角坐标系的时候是以左下角为原点建立的,然而对于图像而言是以左上角为原点建立的,所以我们需要对角度θ进行取反,结合三角函数的特性,M矩阵的表达式如下

    0aee78fdc6e54eb83d46c58d8fd670c0.png

    围绕原点旋转的仿射变换矩阵

    还需要注意的是这里的角度都是弧度制,所以我们在使用的时候还需要对其进行转换,转换代码如下

    #将角度转换为弧度制radian_theta = theta/180 * np.pi#np.pi指的是π

    将图片围绕原点进行逆时针旋转θ度的代码如下

    6710102fef02b8aad9fa4f90981f32ed.png

    将图像围绕原点旋转

    • 围绕任意点旋转

    如果我们想围绕任意坐标点旋转呢?其实也并不难,下图的v点在围绕c点(a,b)旋转90度得到v′。其实我们可以将其等价于,先将V点平移到V1点,然后再将V1点围绕原点旋转90度得到V2点,最后再将V2点沿着V点平移的反方向平移相同长度,最终得到V'。这样我们就将围绕任意坐标点旋转的问题转换成了围绕原点旋转的问题

    13aeb06767415921b839b2f52372afd5.png

    我们来回顾一下,围绕原点旋转坐标的变换公式

    df0cc251b9b7684f4567b46ccf972a15.png

    围绕原点旋转

    在围绕原点旋转变换公式的基础上,我们将其改进为围绕任意点c(a,b)旋转,我们现在原来的坐标进行平移,得到变换后的坐标,最后再沿着之前平移的反方向进行平移,就得到围绕任意点旋转的变换公式

    d500e5542c010db5a275bafa0b2168e9.png

    围绕任意点旋转

    将其展开可得

    36ca0444483c216f0eb797eae496608f.png

    围绕任意点旋转的展开式

    将上式用矩阵M表示:

    40f23002c1ed98cd65a7f15c4ba30335.png

    矩阵形式表示围绕点c进行旋转

    上式中的c(a,b)表示旋转中心,因为坐标系问题需要对θ进行取反,最终M矩阵的表达式如下

    4186ede23b1b9bb4295930b81d2fade6.png

    旋转矩阵

    f86a32a8653c01963830cda81e547e75.png

    围绕图像中心旋转图像

    cd996d2430f302481ed26c158c6d62b4.png

    围绕图像中心旋转图像

    细心的同学也许已经发现了,上图中围绕图像中心旋转后的图片部分被裁剪掉了,如果我们想让旋转之后的图片仍然是完整,应该如何修改呢?

    9f4c62e3fa7f443c41f452881190a7c0.png

    围绕图像中心旋转后仍保持完整

    10552013b6113ca52bfb6aafdcc712df.png
    展开全文
  • 通过一个以上的控制点,计算四参数转换参数,并利用转换参数将其他测量点转换成不同的坐标系下的坐标! 通过一个以上的控制点,计算四参数转换参数,并利用转换参数将其他测量点转换成不同的坐标系下的坐标!
  • 仿射变换实现

    千次阅读 2022-04-04 23:54:39
    仿射变换本质是一个2* 3的矩阵M 将 ...
  • 算法框架和步骤可以以图像旋转算法为...T为仿射变换矩阵。参考测试代码:(顺时针旋转30度)I =imread('peppers.png');delta_ang = pi/3;T1=[cos(delta_ang) sin(delta_ang) 0;-sin(delta_ang) cos(delta_ang) 0;0 ...
  • 仿射变换

    2019-03-19 21:33:13
    初学图像到图像的映射,在仿射变换前会首先介绍单应性变换。 单应性变换 单应性变换是指将一个平面内的点映射到另一个平面内的二维投影变换。平面是指图像或者是三维空间中的平面表面。单应性变换具有很强的实用性...
  • 图像仿射变换

    千次阅读 2021-07-15 20:07:11
    什么是仿射变换?3. 定义4. 常用的转换矩阵5.实际应用5.1 栗子15.2 栗子2 1. 引用 opencv学习(三十五)之仿射变换warpAffine 2. 什么是仿射变换? 3. 定义 向量空间中进行一次线性变换(乘以一个矩阵A)再加上一个平...
  • 仿射变换 仿射变换都是借助cv2.wrapAffine函数。关键在于计算M矩阵 关于wrapAffine函数,有一个细节需要注意:参数dst尺寸,最开始我是以为resize的,但结果不是resize,而是裁剪。下面细细解释:原图如下:尺寸为 ...
  • 图像的几何变换主要分为:刚性变换、相似变换、仿射变换和透视变换(也称为投影变换)。 刚性变换:平移、旋转; 相似变换:缩放、剪切; 仿射变换:从一个二维坐标系变换到另一个二维坐标系的过程,属于线性变换。...
  • 在激活函数中具有仿射变换输入的极限学习机
  • 仿射变换实现的换脸demo python实现
  • 之前做过了Harris特征匹配和SIFT特征匹配的测试例子,如果要实现拼接,会涉及到一些基础的图像处理,简单的2D图像变换主要包括以下几种: 1. 平移变换 主要是水平方向和垂直方向地移动变换,2个自由度。 2. 刚体...
  • 基于仿射变换的数字图象置乱技术 部分源码 function chengxu() A=imread('lena.png'); figure,imshow(A); title('原图像(256*256)'); for K=1:16 %置乱16次 B=zhiluan(A); %调用zhuluan函数,实现置乱 figure,...
  • 纯C++仿射变换实现

    千次阅读 2020-10-16 17:06:33
    目录纯C++仿射变换实现前言原理代码实现结果验证 纯C++仿射变换实现 最近需要使用仿射变换进行一些应用,但又不想用到OpenCV和Halcon之类的库进行,就自己写了一个计算和实现函数,大概记录一下这个过程。 基本概念...
  • MATLAB中应用于图像的仿射变换

    千次阅读 2022-01-06 11:28:55
    仿射变换的类型如下表所示 以函数checkerboard中生成的棋盘图像进行顺时针旋转30°为例 注意:imtransform在默认的情况下将输出范围之外的位置全部重为0! 代码如下: f=checkerboard(50);%生成棋盘图像 angle=pi/6...
  • 图像的仿射变换、透视变换、旋转
  • 多种加密_仿射变换_

    2021-09-29 11:15:52
    代码 包含凯撒变换 仿射变换 以及梅森旋转法 并可生成随机数文件 自定义保存位置
  • 仿射变换和透视变换

    2021-10-21 11:41:23
    最近对场景图片做一些研究和矫正,所以对仿射变换和透视变换有些了解,然后把自己的使用过程分享如下 1.仿射变换 作用:提高识别的效果;如:在ocr字符识别的第一步会将文字区域进行矫正; 实施该方法的前提:有...
  • 图像的仿射变换

    2014-09-26 21:57:17
    采用opencv对图像进行仿射变换,修改代码的程序可以改变旋转的任意角度
  • 资源名:基于仿射变换的数字图象置乱技术_MATLAB源程序代码_数字图象置乱_仿射变换_matlab 资源类型:matlab项目全套源码 源码说明: 全部项目源码都是经过测试校正后百分百成功运行的,如果您下载后不能运行可联系...
  • 图像仿射变换OpenCV API与自行代码实现
  • 仿射变换代码

    2014-06-04 19:39:19
    仿射变换代码,适合初学者用。仅供参考,真正了解其中的原理还得看书。
  • 如果设置旋转角度angle为0,缩放系数为0.6,则可以实现图像的缩放,案例代码如下: import cv2 img = cv2.imread('src.jpg') rows, cols, channels = img.shape #获取仿射变换矩阵 M = cv2.getRotationMatrix2D(...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,013
精华内容 4,805
关键字:

仿射变换代码