精华内容
下载资源
问答
  • 更多相关内容
  • 图像的仿射变换原理和实现发布时间:2018-05-06 01:03,浏览次数:1148仿射变换能够保持图像的“平直性”,包括旋转,缩放,平移,错切操作。一般而言,仿射变换矩阵为2*3的矩阵,第三列的元素起着平移的作用,前面...

    图像的仿射变换原理和实现

    发布时间:2018-05-06 01:03,

    浏览次数:1148

    仿射变换能够保持图像的“平直性”,包括旋转,缩放,平移,错切操作。一般而言,仿射变换矩阵为2*3的矩阵,第三列的元素起着平移的作用,前面两列的数字对角线上是缩放,其余为旋转或者错切的作用。透视变换原理及实现请看我

    下篇博客 。

    设仿射变换矩阵T = [a11,a12,a13 ; a21,a22,a23]; 图像上fixed points坐标为(Xk,Yk);moving

    points坐标为(xk,yk),其中k=1,2,...,n。

    为了求解该仿射变换矩阵T的6个未知参数,理论上至少需要6个方程联立求解,即需要3组点集对,n>=3,当n>3时用最小二乘法求解未知参数。并且这三组点不共线。

    数学展开式如下图:

    下面根据上面公式给出实现代码,图像使用最近邻插值。

    %% 读取原图像 clear ; close all; src=rgb2gray(imread('lena.png')); imshow(src); %%

    method1,用3组点集对图像进行仿射变换,调用matlab系统函数 fixedPoints = [1,1; 1,100;100,100];

    movingPoints = [20,20; 120,80; 160,200]; tic; tform =

    fitgeotrans(movingPoints,fixedPoints,'affine'); dst_img = imwarp(src,tform);

    t_sys = toc; figure;imshowpair(src,dst_img,'montage');

    title(['系统函数的仿射变换图像,耗时(s):',num2str(t_sys)]) %% method2,用3组点集对图像进行仿射变换,解方程求变换矩阵

    % T = [a11,a12,a13;a21,a22,a33]; % 满足fixed_pt_matrix = T*moving_pt_matrix; tic;

    fixed_pt_matrix = fixedPoints'; moving_pt_matrix =

    [movingPoints';ones(1,size(movingPoints,1))]; T =

    fixed_pt_matrix/moving_pt_matrix; width = size(src,2); height = size(src,1);

    [moving_pt_x,moving_pt_y] = meshgrid(1:width,1:height); coridate_affine =

    T*[moving_pt_x(:)';% 对原来图像所有坐标点变换到新平面上 moving_pt_y(:)'; ones(1,width*height)];

    x_temp = coridate_affine(1,:); y_temp = coridate_affine(2,:); fixed_pt_x =

    reshape(x_temp,... size(moving_pt_x))+... abs(min(x_temp))+1; fixed_pt_y =

    reshape(y_temp,... size(moving_pt_y))+... abs(min(y_temp))+1; fixed_pt_x =

    round(fixed_pt_x); fixed_pt_y = round(fixed_pt_y); dst_affine_img =

    zeros(round(max(y_temp)-min(y_temp))+1,... round(max(x_temp)-min(x_temp))+1);

    for i = 1:height for j = 1:width

    dst_affine_img(fixed_pt_y(i,j),fixed_pt_x(i,j)) = src(i,j); end end t_manual =

    toc; figure;imshowpair(src,uint8(dst_affine_img),'montage');

    title(['计算的仿射变换图像,耗时(s):',num2str(t_manual)]) %% 插值处理 [index_i,index_j] =

    find(dst_affine_img); for i = 1:size(dst_affine_img,1) for j =

    1:size(dst_affine_img,2) [min_distance,index_near] =

    min(sqrt((i-index_i).^2+(j-index_j).^2)); if dst_affine_img(i,j)==0 &&

    min_distance<=1 dst_affine_img(i,j) =

    dst_affine_img(index_i(index_near),index_j(index_near)); end end end figure;

    imshowpair(src,uint8(dst_affine_img),'montage'); title('插值后图像')

    再看一个对点集的简单测试,T =  [1,1,0;2,1,0];

    %% 坐标点的仿射变换 [pt_x,pt_y] = meshgrid(1:10); pt_x = pt_x(:); pt_y = pt_y(:);

    figure;subplot(211);plot(pt_x,pt_y,'ro');grid on; title('原始点集') dst_pt =

    zeros(length(pt_x),2); tf_affine = [1,1,0;2,1,0]; for i = 1:length(pt_x) dst =

    tf_affine*[pt_x(i),pt_y(i),1]'; dst_pt(i,:) = [dst(1),dst(2)]; end

    subplot(212);plot(dst_pt(:,1),dst_pt(:,2),'bo');grid on title('仿射后点集')reference:

    https://ww2.mathworks.cn/help/images/ref/fitgeotrans.html?s_tid=srchtitle

    展开全文
  • Opencv 仿射变换原理代码解析

    千次阅读 2019-05-22 09:19:33
    仿射变换原理 仿射变换是线性变换,有一张图可以很好地展示放射变换的效果 其实仿射变换是透视变换的一种特例,但是透视变换的自由度更高,3*3的矩阵表示,透视变换的自由度是8,而放射变换可以用2*3的矩阵表示,...
    • 仿射变换原理

    仿射变换是线性变换,有一张图可以很好地展示放射变换的效果

    其实仿射变换是透视变换的一种特例,但是透视变换的自由度更高,3*3的矩阵表示,透视变换的自由度是8,而放射变换可以用2*3的矩阵表示,【A B】 A是2*2的旋转部分+缩放因子S,而B是平移部分+缩放因子,是一个5个自由度的参数矩阵。

    典型的放射变换包括平移,缩放和旋转。

    其中Opencv中的旋转由于是绕某个图像坐标进行旋转,因此整个旋转的流程可以理解为平移—》旋转—》平移

    最后可以得到旋转变换矩阵

    而对于特定的放射矩阵W,opencv的求逆矩阵方法是分块矩阵求逆矩阵,注意不能想当然得将平移部分参数取反,因为平移因子是经过一些列的旋转操作后的参数,除非矩阵只有平移部分。

    记录下分块矩阵求逆矩阵公式,摘自知乎帖子

    知乎对仿射变换的解释,带交互动画的:https://www.zhihu.com/question/20666664

    • Opencv源码

    实现仿射变换和实现其他resize的插值函数是类似的,需要求出目标图像像素在原图像中的位置,获得对应点的像素值。插值的方式有多种,一般常见的有最近邻插值,双线性插值(Bilinear)https://www.cnblogs.com/yssongest/p/5303151.html,三次样条曲线(Cubic Spline)。

    源码中一下部分用于求输入仿射矩阵的逆矩阵

     if( !(flags & WARP_INVERSE_MAP) )
        {
            double D = M[0]*M[4] - M[1]*M[3];
            D = D != 0 ? 1./D : 0;
            double A11 = M[4]*D, A22=M[0]*D;
            M[0] = A11; M[1] *= -D;
            M[3] *= -D; M[4] = A22;
            double b1 = -M[0]*M[2] - M[1]*M[5];
            double b2 = -M[3]*M[2] - M[4]*M[5];
            M[2] = b1; M[5] = b2;
        }

    先对2*2矩阵求逆矩阵,然后对1*2的块求逆。

    执行仿射变换的代码:

    void cv::warpAffine( InputArray _src, OutputArray _dst,
                         InputArray _M0, Size dsize,
                         int flags, int borderType, const Scalar& borderValue )
    {
    	.....
    	//计算X分量的数值
    for( x = 0; x < dst.cols; x++ )
        {
            adelta[x] = saturate_cast<int>(M[0]*x*AB_SCALE);
            bdelta[x] = saturate_cast<int>(M[3]*x*AB_SCALE);
        }
    
        Range range(0, dst.rows);
        WarpAffineInvoker invoker(src, dst, interpolation, borderType,
                                  borderValue, adelta, bdelta, M);
        parallel_for_(range, invoker, dst.total()/(double)(1<<16));
    	.....
    }
    
    class WarpAffineInvoker :
        public ParallelLoopBody
    {
    public:
        WarpAffineInvoker(const Mat &_src, Mat &_dst, int _interpolation, int _borderType,
                          const Scalar &_borderValue, int *_adelta, int *_bdelta, double *_M) :
            ParallelLoopBody(), src(_src), dst(_dst), interpolation(_interpolation),
            borderType(_borderType), borderValue(_borderValue), adelta(_adelta), bdelta(_bdelta),
            M(_M)
        {
        }
    
        virtual void operator() (const Range& range) const
        {
            const int BLOCK_SZ = 64;
    		//XY 是映射坐标矩阵,但是是分Block进行计算,分块意义在于SSE进行访问加速
    		//A  是remap函数使用到的参数,和插值有关系,没有进一步深入
            short XY[BLOCK_SZ*BLOCK_SZ*2], A[BLOCK_SZ*BLOCK_SZ];
            const int AB_BITS = MAX(10, (int)INTER_BITS);
    		//AB_BITS 为10,放大1024倍
            const int AB_SCALE = 1 << AB_BITS;
    		//round_delta 计算结果为16...
            int round_delta = interpolation == INTER_NEAREST ? AB_SCALE/2 : AB_SCALE/INTER_TAB_SIZE/2, x, y, x1, y1;
        #if CV_SSE2
            bool useSSE2 = checkHardwareSupport(CV_CPU_SSE2);
        #endif
        #if CV_SSE4_1
            bool useSSE4_1 = checkHardwareSupport(CV_CPU_SSE4_1);
        #endif
    
    		//获得图像单个Block不越界的访问区域
            int bh0 = std::min(BLOCK_SZ/2, dst.rows);
            int bw0 = std::min(BLOCK_SZ*BLOCK_SZ/bh0, dst.cols);
            bh0 = std::min(BLOCK_SZ*BLOCK_SZ/bw0, dst.rows);
    
    		//全图高度访问(rows),
            for( y = range.start; y < range.end; y += bh0 )
            {
    			//先访问cols(x),x += bw0每次增加1个Block的宽度
                for( x = 0; x < dst.cols; x += bw0 )
                {
                    int bw = std::min( bw0, dst.cols - x);
                    int bh = std::min( bh0, range.end - y);
    
    				//单个Block的映射坐标矩阵
                    Mat _XY(bh, bw, CV_16SC2, XY), matA;
    				//目标图像的某一个Block
                    Mat dpart(dst, Rect(x, y, bw, bh));
    
    				//再访问rows(y1)
                    for( y1 = 0; y1 < bh; y1++ )
                    {
    					//xy 是映射坐标矩阵的指针
                        short* xy = XY + y1*bw*2;
    					//计算包含y部分的分量
                        int X0 = saturate_cast<int>((M[1]*(y + y1) + M[2])*AB_SCALE) + round_delta;
                        int Y0 = saturate_cast<int>((M[4]*(y + y1) + M[5])*AB_SCALE) + round_delta;
    
                        if( interpolation == INTER_NEAREST )
                        {
                            x1 = 0;
                            #if CV_NEON
                            int32x4_t v_X0 = vdupq_n_s32(X0), v_Y0 = vdupq_n_s32(Y0);
                            for( ; x1 <= bw - 8; x1 += 8 )
                            {
                                int16x8x2_t v_dst;
                                v_dst.val[0] = vcombine_s16(vqmovn_s32(vshrq_n_s32(vaddq_s32(v_X0, vld1q_s32(adelta + x + x1)), AB_BITS)),
                                                            vqmovn_s32(vshrq_n_s32(vaddq_s32(v_X0, vld1q_s32(adelta + x + x1 + 4)), AB_BITS)));
                                v_dst.val[1] = vcombine_s16(vqmovn_s32(vshrq_n_s32(vaddq_s32(v_Y0, vld1q_s32(bdelta + x + x1)), AB_BITS)),
                                                            vqmovn_s32(vshrq_n_s32(vaddq_s32(v_Y0, vld1q_s32(bdelta + x + x1 + 4)), AB_BITS)));
    
                                vst2q_s16(xy + (x1 << 1), v_dst);
                            }
                            #elif CV_SSE4_1
                            if (useSSE4_1)
                            {
                                __m128i v_X0 = _mm_set1_epi32(X0);
                                __m128i v_Y0 = _mm_set1_epi32(Y0);
                                for ( ; x1 <= bw - 16; x1 += 16)
                                {
                                    __m128i v_x0 = _mm_packs_epi32(_mm_srai_epi32(_mm_add_epi32(v_X0, _mm_loadu_si128((__m128i const *)(adelta + x + x1))), AB_BITS),
                                                                   _mm_srai_epi32(_mm_add_epi32(v_X0, _mm_loadu_si128((__m128i const *)(adelta + x + x1 + 4))), AB_BITS));
                                    __m128i v_x1 = _mm_packs_epi32(_mm_srai_epi32(_mm_add_epi32(v_X0, _mm_loadu_si128((__m128i const *)(adelta + x + x1 + 8))), AB_BITS),
                                                                   _mm_srai_epi32(_mm_add_epi32(v_X0, _mm_loadu_si128((__m128i const *)(adelta + x + x1 + 12))), AB_BITS));
    
                                    __m128i v_y0 = _mm_packs_epi32(_mm_srai_epi32(_mm_add_epi32(v_Y0, _mm_loadu_si128((__m128i const *)(bdelta + x + x1))), AB_BITS),
                                                                   _mm_srai_epi32(_mm_add_epi32(v_Y0, _mm_loadu_si128((__m128i const *)(bdelta + x + x1 + 4))), AB_BITS));
                                    __m128i v_y1 = _mm_packs_epi32(_mm_srai_epi32(_mm_add_epi32(v_Y0, _mm_loadu_si128((__m128i const *)(bdelta + x + x1 + 8))), AB_BITS),
                                                                   _mm_srai_epi32(_mm_add_epi32(v_Y0, _mm_loadu_si128((__m128i const *)(bdelta + x + x1 + 12))), AB_BITS));
    
                                    _mm_interleave_epi16(v_x0, v_x1, v_y0, v_y1);
    
                                    _mm_storeu_si128((__m128i *)(xy + x1 * 2), v_x0);
                                    _mm_storeu_si128((__m128i *)(xy + x1 * 2 + 8), v_x1);
                                    _mm_storeu_si128((__m128i *)(xy + x1 * 2 + 16), v_y0);
                                    _mm_storeu_si128((__m128i *)(xy + x1 * 2 + 24), v_y1);
                                }
                            }
                            #endif
                            for( ; x1 < bw; x1++ )
                            {
                                int X = (X0 + adelta[x+x1]) >> AB_BITS;
                                int Y = (Y0 + bdelta[x+x1]) >> AB_BITS;
                                xy[x1*2] = saturate_cast<short>(X);
                                xy[x1*2+1] = saturate_cast<short>(Y);
                            }
                        }
                        else
                        {
                            short* alpha = A + y1*bw;
                            x1 = 0;
                        #if CV_SSE2
                            if( useSSE2 )
                            {
                                __m128i fxy_mask = _mm_set1_epi32(INTER_TAB_SIZE - 1);
    							//XX 计算的是M1*dy+M2
    							//YY 计算的是M4*dy+M5
                                __m128i XX = _mm_set1_epi32(X0), YY = _mm_set1_epi32(Y0);
                                for( ; x1 <= bw - 8; x1 += 8 )
                                {
                                    __m128i tx0, tx1, ty0, ty1;
    								//分别取4个整数进行X和Y(映射坐标)的计算,一共8个
                                    tx0 = _mm_add_epi32(_mm_loadu_si128((const __m128i*)(adelta + x + x1)), XX);
                                    ty0 = _mm_add_epi32(_mm_loadu_si128((const __m128i*)(bdelta + x + x1)), YY);
                                    tx1 = _mm_add_epi32(_mm_loadu_si128((const __m128i*)(adelta + x + x1 + 4)), XX);
                                    ty1 = _mm_add_epi32(_mm_loadu_si128((const __m128i*)(bdelta + x + x1 + 4)), YY);
    
    								//右移5位
                                    tx0 = _mm_srai_epi32(tx0, AB_BITS - INTER_BITS);
                                    ty0 = _mm_srai_epi32(ty0, AB_BITS - INTER_BITS);
                                    tx1 = _mm_srai_epi32(tx1, AB_BITS - INTER_BITS);
                                    ty1 = _mm_srai_epi32(ty1, AB_BITS - INTER_BITS);
    
    								//fx_和fy_是remap的参数
                                    __m128i fx_ = _mm_packs_epi32(_mm_and_si128(tx0, fxy_mask),
                                                                _mm_and_si128(tx1, fxy_mask));
                                    __m128i fy_ = _mm_packs_epi32(_mm_and_si128(ty0, fxy_mask),
                                                                _mm_and_si128(ty1, fxy_mask));
    								//_mm_packs_epi32函数是将32bit有符号整数合成为16bit有符号整数
                                    tx0 = _mm_packs_epi32(_mm_srai_epi32(tx0, INTER_BITS),
                                                                _mm_srai_epi32(tx1, INTER_BITS));
                                    ty0 = _mm_packs_epi32(_mm_srai_epi32(ty0, INTER_BITS),
                                                        _mm_srai_epi32(ty1, INTER_BITS));
                                    fx_ = _mm_adds_epi16(fx_, _mm_slli_epi16(fy_, INTER_BITS));
    								//_mm_unpacklo_epi16函数是将16bit有符号整数交错地进行放置,以16bit为步长分开
    								//_mm_storeu_si128相当于memcpy
                                    _mm_storeu_si128((__m128i*)(xy + x1*2), _mm_unpacklo_epi16(tx0, ty0));
                                    _mm_storeu_si128((__m128i*)(xy + x1*2 + 8), _mm_unpackhi_epi16(tx0, ty0));
                                    _mm_storeu_si128((__m128i*)(alpha + x1), fx_);
                                }
                            }
                        #elif CV_NEON
                            int32x4_t v__X0 = vdupq_n_s32(X0), v__Y0 = vdupq_n_s32(Y0), v_mask = vdupq_n_s32(INTER_TAB_SIZE - 1);
                            for( ; x1 <= bw - 8; x1 += 8 )
                            {
                                int32x4_t v_X0 = vshrq_n_s32(vaddq_s32(v__X0, vld1q_s32(adelta + x + x1)), AB_BITS - INTER_BITS);
                                int32x4_t v_Y0 = vshrq_n_s32(vaddq_s32(v__Y0, vld1q_s32(bdelta + x + x1)), AB_BITS - INTER_BITS);
                                int32x4_t v_X1 = vshrq_n_s32(vaddq_s32(v__X0, vld1q_s32(adelta + x + x1 + 4)), AB_BITS - INTER_BITS);
                                int32x4_t v_Y1 = vshrq_n_s32(vaddq_s32(v__Y0, vld1q_s32(bdelta + x + x1 + 4)), AB_BITS - INTER_BITS);
    
                                int16x8x2_t v_xy;
                                v_xy.val[0] = vcombine_s16(vqmovn_s32(vshrq_n_s32(v_X0, INTER_BITS)), vqmovn_s32(vshrq_n_s32(v_X1, INTER_BITS)));
                                v_xy.val[1] = vcombine_s16(vqmovn_s32(vshrq_n_s32(v_Y0, INTER_BITS)), vqmovn_s32(vshrq_n_s32(v_Y1, INTER_BITS)));
    
                                vst2q_s16(xy + (x1 << 1), v_xy);
    
                                int16x4_t v_alpha0 = vmovn_s32(vaddq_s32(vshlq_n_s32(vandq_s32(v_Y0, v_mask), INTER_BITS),
                                                                         vandq_s32(v_X0, v_mask)));
                                int16x4_t v_alpha1 = vmovn_s32(vaddq_s32(vshlq_n_s32(vandq_s32(v_Y1, v_mask), INTER_BITS),
                                                                         vandq_s32(v_X1, v_mask)));
                                vst1q_s16(alpha + x1, vcombine_s16(v_alpha0, v_alpha1));
                            }
                        #endif
    					
    						//SSE不能访问的部分用常规方式遍历
                            for( ; x1 < bw; x1++ )
                            {
                                int X = (X0 + adelta[x+x1]) >> (AB_BITS - INTER_BITS);
                                int Y = (Y0 + bdelta[x+x1]) >> (AB_BITS - INTER_BITS);
                                xy[x1*2] = saturate_cast<short>(X >> INTER_BITS);
                                xy[x1*2+1] = saturate_cast<short>(Y >> INTER_BITS);
                                alpha[x1] = (short)((Y & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE +
                                        (X & (INTER_TAB_SIZE-1)));
                            }
                        }
                    }
    
    				//对单个Block进行重映射计算
                    if( interpolation == INTER_NEAREST )
                        remap( src, dpart, _XY, Mat(), interpolation, borderType, borderValue );
                    else
                    {
                        Mat _matA(bh, bw, CV_16U, A);
                        remap( src, dpart, _XY, _matA, interpolation, borderType, borderValue );
                    }
                }
            }
        }
    
    private:
        Mat src;
        Mat dst;
        int interpolation, borderType;
        Scalar borderValue;
        int *adelta, *bdelta;
        double *M;
    };

    当中涉及到SSE计算的原理图。

    • 图像旋转和平移

    以图像中某一个点为原点旋转,可以通过opencv  cv::getRotationMatrix2D(Center, angle, 1);函数获得仿射变换需要的旋转矩阵,注意是2*3的矩阵;添加平移矩阵的就比较简单,构造一个3*3的平移矩阵和旋转矩阵相乘,在进行仿射变换就OK了,因为仿射变换内部就是通过求解输入矩阵的逆矩阵,得到映射矩阵,从而知道dst的图像各个像素点在原图像中的坐标。

    展开全文
  • 今天学习了仿射变换,将一张图片放置到另一张图片,感觉十分有趣,所以写了这篇博客,与大家一起分享一下! 一、仿射变换 1.什么是仿射变换 2.alpha通道 3.仿射变换的求解 二、仿射变换的实现 1.主函数 2....

    今天学习了仿射变换,将一张图片放置到另一张图片,感觉十分有趣,所以写了这篇博客,与大家一起分享一下!

    一、仿射变换

    1.什么是仿射变换

    2.alpha通道

    3.仿射变换的求解

    二、仿射变换的实现

    1.主函数

    2.主要函数的介绍

    一、仿射变换

    1.什么是仿射变换

    (1)放射变换的定义

    仿射变换是将一个平面的点映射到另一个平面内的二维投影。仿射变换保持了二维图形的“平直性”,即原来是直线的地方还是直线。仿射变换具有很强的实用性,比如图像配准、图像纠正和纹理纠正,以及创建全景图像等。仿射变换的效果如下图所示:

    (2)仿射变换的数学表示

    仿射变换是一种二维坐标(x, y)到二维坐标(u, v)的线性变换,其数学表达式形式如下:

    对应的齐次坐标矩阵表示形式为:

    其中,取\bg_white h_2_0\bg_white h_2_1为0,\bg_white h_2_2为1。

    坐标变换:

    实现将一张图片放置到另一张图片上去,其实就是已知XH,而H中的3个变量已知,我们只需求另外6个变量即可。

    (3)仿射变换的几何意义

    仿射变换可以理解成一系列的原子变换复合实现的,具体暴扣:平移、旋转、尺度变换等。

    a.平移

    数学形式:

    矩阵形式:

    平移变换包含两个未知量,即t_xt_y。计算两个图形的平移量,其实就是求这两个未知量。

    b.旋转

    矩阵形式:

     

    c.尺度变换

    矩阵形式:

    d.刚体运动:旋转平移缩放

    矩阵形式:

    2.alpha通道

    alpha用来衡量一个像素或图像的透明度。在非压缩的32位RGB图像中,每个像素是由四个部分组成:一个alpha通道和三个颜色分量(R、G和B)。当Alpha值为0时,该像素是完全透明的,而当Alpha值为255时,则该像素是完全不透明。 

    alpha用于图像m1经过仿射变换后,将图像m1放置到图像m2时,只有alpha大于0的区域才能放置到图像m2上,alpha为0的区域在融合的时候就变成透明了。

    3.仿射变换的求解

    (1)流程

    a.输入两张图像m1和m2

    b.设置所需要放置的区域矩阵tp

    c.获取m1的宽m和高n,并设置原图区域矩阵为fp

    d.根据fp和tp计算仿射变换参数矩阵H

    e.将m1放置到m2上去

    (2)仿射变换参数矩阵H的求解

    放射变换求解的关键就是H的求解,我们已知原图区域矩阵fp和需要放置的tp,需要求得仿射变换参数矩阵H。

    我们利用最小二乘法求解H,其基本步骤如下:

    a.检查tp和fp大小是否一致,若不一致则报错

    b.对fp进行归一化

    c.对tp进行归一化

    d.利用SVD进行奇异值分解,剔除异常值,求得H

    二、仿射变换的实现

    1.主函数:

     # -*- coding: utf-8 -*-
    from PCV.geometry import warp, homography
    from PIL import  Image
    from pylab import *
    from scipy import ndimage
    
    # im1仿射到im2的例子
    #打开两张图片
    im1 = array(Image.open('D:/test/beatles.jpg').convert('L')) 
    im2 = array(Image.open('D:/test/billboard_for_rent.jpg').convert('L'))
    # 选定一些目标点,数组tp表示目标区域,三行分别代表y、x和齐次,其中区域按从左上角逆时针旋转,
    tp = array([[120,260,260,120],[16,16,305,305],[1,1,1,1]])#将一些目标点存储到数组中
    im3 = warp.image_in_image(im1,im2,tp) #使用仿射变换将m1放置到m2
    figure()
    gray()
    subplot(141)
    axis('off')
    imshow(im1)
    subplot(132)
    axis('off')
    imshow(im2)
    subplot(133)
    axis('off')
    imshow(im3)
    show()
    

    2.主要函数的介绍:

    (1)wrap.image_in_image(im1,im2,tp)

    函数的意义是通过方式变换将图像m1放置到图像m2的指定区域tp中

    def image_in_image(im1,im2,tp):
        """使用仿射变换将m1放置到m2,使得m1图像的角和tp尽可能的靠近。
            tp是齐次表示的,并且按照从左上角逆时针计算的"""
        
        # 扭曲的点
        m,n = im1.shape[:2]#获取m1的高m宽n
        fp = array([[0,m,m,0],[0,0,n,n],[1,1,1,1]])
        
        #计算仿射变换,并且将其应用于图像im1
        H = homography.Haffine_from_points(tp,fp)#返回给定点对的最优仿射变换求H
        im1_t = ndimage.affine_transform(im1,H[:2,:2],
                        (H[0,2],H[1,2]),im2.shape[:2])#图像扭曲
        alpha = (im1_t > 0)
        
        return (1-alpha)*im2 + alpha*im1_t

    (2)homography.Haffine_from_points(tp,fp)

    函数的意义是通过原图像矩阵fp和仿射变换后的图像矩阵tp求得H

    def Haffine_from_points(fp,tp):
        """计算H,仿射变换,使得tp是fp经过仿射变换H得到的"""
        
        if fp.shape != tp.shape:
            raise RuntimeError('number of points do not match')
            
        # 对点进行归一化
        # 映射起始点
        m = mean(fp[:2], axis=1)#取fp的前两行,压缩列求平均值
        maxstd = max(std(fp[:2], axis=1)) + 1e-9 #取fp的前两行,压缩列求标准差,取最大值
        C1 = diag([1/maxstd, 1/maxstd, 1]) 
        C1[0][2] = -m[0]/maxstd
        C1[1][2] = -m[1]/maxstd
        fp_cond = dot(C1,fp)
        
        # 映射对应点
        m = mean(tp[:2], axis=1)
        C2 = C1.copy() #两个点集,必须都进行相同的缩放
        C2[0][2] = -m[0]/maxstd
        C2[1][2] = -m[1]/maxstd
        tp_cond = dot(C2,tp)
        
        #因为归一化后点的均值为0,因此平移为0
        A = concatenate((fp_cond[:2],tp_cond[:2]), axis=0)#数组拼接
        U,S,V = linalg.svd(A.T)#奇异值分解,用小的多的数据集来表示原始数据集
        
        #创建B和C矩阵
        tmp = V[:2].T
        B = tmp[:2]
        C = tmp[2:4]
    
        #B的伪逆矩阵和C逐元素相乘,与zero(2,1)拼接
        tmp2 = concatenate((dot(C,linalg.pinv(B)),zeros((2,1))), axis=1) 
        H = vstack((tmp2,[0,0,1]))#数组合并
        
        # 反归一化
        H = dot(linalg.inv(C2),dot(H,C1))
        
        return H / H[2,2]

    效果如下图所示:

    左边为图像m1,中间为图像m2,右边为仿射变换后的效果图。我们可以清楚的看到m1放到了m2的指定区域,实现了两张图片的融合。

    展开全文
  • 图片仿射变换原理与实现

    千次阅读 2019-10-14 22:06:10
    图像仿射变换共有“旋转”、“平移”、“错切(shear)”、“缩放”、“翻转”5种。本文结合keras-retinanet的实现进行分析。之所以采用keras-retinanet进行分析,是因为该实现较为典型,比较容易理解。 keras-...
  • 音视频资料-图像仿射变换原理3:仿射变换类型及变换矩阵详解.rar
  • 考虑如图1所示的一个变换,即将点P经过一个运算映射f映射成点Q。下面解释如果现在已知了P和Q的坐标,应该如何求出这个变换f 图1 直观地,写出代数方程: {x′= a1x+a2y+txy′= b1x+b2y+ty \left\{ \begin...
  • 本文介绍了组合(也称复合)仿射变换的概念、变换过程以及变换矩阵,并以绕指定点旋转的组合变换、指定直线作为依赖轴的组合变换详细介绍了变换过程和变换矩阵的构成,有助于深入理解仿射变换的概念和处理过程。
  • 本文是老猿关于仿射变换原理、概念以及OpenCV-Python实现额的导览式文章,通过本文作为入口,读者可以非常快速的理解仿射变换的相关知识以及开发应用。
  • 仿射变换原理解析PPT课件.pptx
  • 图像的仿射变换原理及python实现

    万次阅读 多人点赞 2018-09-21 13:16:17
    1. 原理 ...仿射变换保持了二维图形的“平直性”(直线经仿射变换后依然为直线)和“平行性”(直线之间的相对位置关系保持不变,平行线经仿射变换后依然为平行线,且直线上点的位置顺序不会发生变化)...
  • 音视频资料-图像仿射变换原理4:组合变换及对应变换矩阵.rar
  • 音视频资料-图像仿射变换原理2:矩阵变换、线性变换和图像线性变换矩阵.rar
  • 音视频资料-图像仿射变换原理1:齐次坐标来龙去脉详解.rar
  • 音视频资料-图像仿射变换原理5:组合变换矩阵的OpenCV-Python实现.rar
  • 本节以绕图像中心点循环旋转的组合仿射变换和以图像中心点开始与x轴成30°夹角的线段作为依赖轴的循环错切的组合仿射变换为例,详细介绍了二者的...通过相关案例的介绍,对前面4节介绍的仿射变换原理将有更深入的认识。
  • 几何空间变换是图像处理中的最基础的算法,主要包括图像的旋转,平移,缩放,偏移,组合变换等等,在冈萨雷斯的数字图像处理第三版的第二章就做了相关介绍,最常用的空间坐标变换之一就是仿射变换。虽然仿射变换很...
  • ☞ ░ 老猿Python博文目录:https://blog.csdn.net/LaoYuanPython ░ 组合变换原理博文传送门: 图像仿射变换原理1:齐次坐标来龙去脉详解 图像仿射变换原理2:矩阵变换、线性变换和图像线性变换矩阵 图像仿射变换...
  • [Halcon&定位] 二维仿射变换原理与算子解析

    千次阅读 多人点赞 2019-05-17 17:18:00
    二维仿射变换,顾名思义就是在二维平面内,对对象进行平移、旋转、缩放等变换的行为(当然还有其他的变换,这里仅论述这三种最常见的)。
  • 本文介绍了仿射变换的类型及其关系以及仿射变换矩阵,基本的仿射变换包括平移、旋转、缩放和错切,镜像可以看做特殊的缩放。实际中一般图像的仿射变换就是平移、旋转、缩放和错切的叠加组合,每叠加一个处理,就进行...
  • opencv cv2.GetAffineTransform求解原理详解
  • 图像的几何变换主要分为:刚性变换、相似变换、仿射变换和透视变换(也称为投影变换)。 刚性变换:平移、旋转; 相似变换:缩放、剪切; 仿射变换:从一个二维坐标系变换到另一个二维坐标系的过程,属于线性变换。...
  • 图像的仿射变换

    2021-02-05 06:15:56
    目录:概述图像基本变换仿射变换原理python实现一、概述图像的几何变换主要包括:平移、缩放、旋转、仿射、透视等等。图像变换是建立在矩阵运算基础上的,通过矩阵运算可以很快的找到不同图像的对应关系。理解变换的...
  • 一般对图像的变化操作有放大、缩小、旋转等,统称为几何变换,对一个图像的图像变换主要有两大步骤,一是实现空间坐标... ##仿射变换## 二维空间坐标的仿射变换公式:$$ \left( \begin{matrix} \overline{x} \ \ove...
  • 最后的人脸对齐尺寸分为两种:112X96尺寸和112X112尺寸,其中首先需要定死仿射变换后人脸在目标图上的坐标,然后直接变换。废话不多说,直接手撕代码。 # 该代码实现利用人脸的五点仿射变换实现人脸对齐 # 具体就是...
  • 仿射变换原理

    千次阅读 2017-11-29 16:07:31
    如何合并这三次运算,需要从仿射变换原理说起。 举个例子,我们需要对以下图像(蓝色)顺时针旋转90度(橙色),可以这么做:假设原来的图像宽w高h  (1) 创建宽h高w内存区域存放新图像...
  • 仿射变换(Affine Transformation)原理及应用(1)

    万次阅读 多人点赞 2019-08-09 17:35:59
    仿射变换(Affine Transformation)原理及应用 文章目录1 什么是仿射变换2 仿射变换数学表达3 仿射变换理解3.1 平移变换3.2 反射变换3.3 旋转变换3.4 opencv中的仿射矩阵 1 什么是仿射变换 仿射变换(Affine ...
  • 一般对图像的变化操作有放大、缩小、旋转等,统称为几何...其中主要的图像变换有:仿射变换、投影变换、极坐标变换。 仿射变换 二维空间坐标的仿射变换公式: (x‾y‾)=(a11a12a21a22)(xy)+(a13a23) \left( \beg...
  • 文章目录CV — 数据增强:仿射变换一、前言(一) 仿射变换概念(二) 仿射变换矩阵二、代码实现(一) opencv 函数说明1. warpAffine2. warpPerspective(二) 实现代码 CV — 数据增强:仿射变换 一、前言 (一) 仿射变换...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,126
精华内容 2,850
关键字:

仿射变换原理