精华内容
下载资源
问答
  • 仿射变换代码

    2013-11-22 12:17:30
    在几何上定义为两个向量空间之间的一个仿射变换或者仿射映射(来自拉丁语,affinis,“和。..相关”)由一个线性变换接上一个平移组成。
  • 仿射变换matlab代码

    热门讨论 2009-04-17 12:57:01
    仿射变换(Affine Transformation)matlab代码
  • 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的图像各个像素点在原图像中的坐标。

    展开全文
  • 本篇文章详细的介绍了各种操作的实现原理,以及代码的实现和使用仿射变换仿射变换也称仿射投影,是指几何中,对一个向量空间进行线性变换并接上一个平移,变换为另一个向量空间。所以,仿射变换其实也就是在讲如何来...

    导读

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

    仿射变换

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

    假设有一个向量空间k:

    18a753b947ff793fbb157505774b644e.png

    和另一个向量空间j:

    996fe2d93726c583d36279104fd6d4a0.png

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

    4d57e88611cf419f0b5f80427f77e63a.png

    将上式进行拆分可得

    ceedd29ac408461df06ff6fb55349c6c.png

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

    313ff5367a20446518afaeffa90f55c5.png

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

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

    warpAffine函数参数:

    • src:输入的图像数组

    • M:仿射变换矩阵

    • dsize:变换后图像的大小

    • flags:使用的插值算法

    • borderValue:边界的填充值

    图像平移

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

    aa1c84a57de081a3495f9046b552a7fc.png

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

    8673ec1ec8828ae44e8fb7e7179e5ee2.png

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

    d63f9171d5d59a55dfffe9642c572c86.png

    49225b43352bb66b123fc41a8b5ffa6a.png

    图像翻转

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

    2f65e4964d04cbbb5a8c60765bb2693a.png

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

    8fb2331a3da16472987ddb70bba41e7d.png

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

    03d33fefb5619a3ef433ec0194230426.png

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

    • 利用变换矩阵翻转图像

    水平翻转的变换矩阵M:

    7bc5237e27ab6698a70101d53f4c8537.png

    垂直翻转的变换矩阵M:

    7998ac570a6b6a4ae3548b26cac52bbd.png

    镜像翻转的变换矩阵M:

    2a8221471a9d21b08a11fb4983ab424d.png

    cdb303abb54d6a1923e29e953469bdba.png

    b55f5a1d8e876517452fe653a005209b.png

    • OpenCV的flip函数翻转图像

    flip函数参数:

    • src:输入的图像数组

    • flipCode:图像翻转参数,1表示水平翻转,0表示垂直翻转,-1表示镜像翻转

    f50a141c7427cee21c3bdd86456d8ae4.pngflip函数实现图像翻转

    • numpy的索引翻转图像

    8a1a5623e33f1e66b38c5e4fb08c0951.png

    图像缩放

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

    f0af74a47f73fb24d53f73c4cd6d4e4f.png

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

    356ff94cd36dfb67d28b141678648cab.png

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

    e3ae84ea958c3a27991b5eb209b38dc9.png

    705448d0e8c8703cda49def85b49c65e.png

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

    图像旋转

    • 围绕原点旋转

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

    d68b7fc3dbf62909a2de9dfce736a2ae.png

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

    8fd0400657c4692a106570741c42169c.png

    对于v′来说:

    1b74a6257e75b8b487cc2d6fac830d50.png

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

    2fd6ea70a4fb1bf6954b346f414382a8.png

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

    284ec2dce7219da9815c4dc7f0642ab1.png

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

    e611eea178f512a9111d784a450c7dd4.png

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

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

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

    a6ce95113c0361d9ee0d06bddf2c599c.png

    • 围绕任意点旋转

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

    7772475681b11eba2c5298c925d6ce07.png

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

    29399c9558ad3d6669f08620c73308fe.png

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

    eb397d9781064b99eb0beabea97ab233.png

    将其展开可得

    d67ea94169fd4e94dde568d43d8705fb.png

    将上式用矩阵M表示:

    b8cc7fc2a5db27c2b90dc50b870065d3.png

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

    47617f730f342ceecef3b3cbb42b54c4.png

    98eecd673e3ed439d3c490a66dc719be.png

    d4d15d168b9ae3317777343f046ef7ab.png

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

    8671a25ee57177884b5dd822745b5c69.png

    6a44306c6c95703ee4c808b319975148.png

    展开全文
  • 目录仿射变换代码结果透视变换代码结果 仿射变换后平行四边形的各边仍操持平行,透视变换结果允许是梯形等四边形。 仿射变换 用于旋转 (线性变换),平移 (向量加).缩放(线性变换),错切,反转。 原理: 代码 # ...


    仿射变换后平行四边形的各边仍操持平行,透视变换结果允许是梯形等四边形。

    仿射变换

    用于旋转 (线性变换),平移 (向量加).缩放(线性变换),错切,反转。
    原理:
    在这里插入图片描述

    代码

    # coding:utf-8
    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    import numpy as np
    from PIL import Image
    
    img = cv2.imread("E:/picture/aa.jpg")
    img = cv2.resize(img,(640,822))
    (r, g, b)=cv2.split(img)
    img=cv2.merge([b,g,r])
    
    rows,cols,ch = img.shape
    
    pts1 = np.float32([[50,50],[200,50],[50,200]])
    pts2 = np.float32([[10,100],[200,50],[100,250]])
    
    M = cv2.getAffineTransform(pts1,pts2)
    
    dst = cv2.warpAffine(img,M,(cols,rows))
    
    plt.subplot(121),plt.imshow(img),plt.title('Input')
    plt.subplot(122),plt.imshow(dst),plt.title('Output')
    plt.show()
    

    结果

    仿射变换

    透视变换

    用于将2D矩阵图像变换成3D的空间显示效果,全景拼接。
    原理:
    在这里插入图片描述

    代码

    # coding:utf-8
    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    import numpy as np
    from PIL import Image
    
    img = cv2.imread("E:/picture/aa.jpg")
    img = cv2.resize(img,(640,822))
    (r, g, b)=cv2.split(img)
    img=cv2.merge([b,g,r])
    
    rows,cols,ch = img.shape
    
    pts1 = np.float32([[100,200],[500,200],[100,500],[500,500]])
    pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])
    
    M = cv2.getPerspectiveTransform(pts1,pts2)
    
    dst = cv2.warpPerspective(img,M,(300,300))
    
    plt.subplot(121),plt.imshow(img),plt.title('Input')
    plt.subplot(122),plt.imshow(dst),plt.title('Output')
    plt.show()
    

    结果

    透视变换

    展开全文
  • 在MATLAB中用代码实现遥感图像的仿射变化 可以直接复制运行
  • MATLAB程序分享基于仿射变换的数字图象置乱技术源程-基于仿射变换的数字图象置乱技术 MATLAB源程序代码.rar 程序代码见附件,拿资料请顺便顶个贴~~ 如果下载有问题,请加我 qq 1530497909,给你在线传
  • 仿射变换和透视变换

    2016-04-21 18:07:37
    仿射变换我的理解就是图像在一个二维平面上进行不同程度的旋转或者拉伸,但是无论怎么变,他的图像都是平行四边形的,它的变换函数是一个2*3的矩阵。...仿射变换代码如下: #include #include int main() { CvPoint

    仿射变换我的理解就是图像在一个二维平面上进行不同程度的旋转或者拉伸,但是无论怎么变,他的图像都是平行四边形的,它的变换函数是一个2*3的矩阵。

    透视变换是一个观察者在3维空间中以不同的角度来观察这个图像,所以这个图像就是投影在观察者那个二维平面上的影像,它的变换函数是3*3,或者4*4的矩阵。

    仿射变换代码如下:

    #include<cv.h>
    #include<highgui.h>
    int main()
    {
    	CvPoint2D32f srcTri[3],dstTri[3];
    	CvMat* rot_mat=cvCreateMat(2,3,CV_32FC1); //旋转矩阵
    	CvMat* warp_mat=cvCreateMat(2,3,CV_32FC1); //
    	IplImage *src,*dst;
    	src=cvLoadImage("1.jpg",1);  ///原图
    	dst=cvCloneImage(src);  //目标图
    	dst->origin=src->origin;
    	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;
    	dstTri[0].x=src->width*0.0;
    	dstTri[0].y=src->height*0.33;
    	dstTri[1].x=src->width*0.85;
    	dstTri[1].y=src->height*0.25;
    	dstTri[2].x=src->width*0.15;
    	dstTri[2].y=src->height*0.7;
    	//-----------------------方法一------------------------
    	//----通过现有的变换后的点来求这个变换矩阵-----------
    	cvGetAffineTransform(srcTri,dstTri,warp_mat); //求变换矩阵
    	cvWarpAffine(src,dst,warp_mat);  //利用求得的变换矩阵来变换图像
    	cvNamedWindow("warp",1);
    	cvShowImage("warp",dst);
    	cvCopy(dst,src);
    	//----------------------方法二-------------------------
    	//通过已知的参数来旋转图像
    	CvPoint2D32f center=cvPoint2D32f(src->width/2,src->height/2); //旋转的中心点
    	double angle=-50.0;  //需要旋转的角度
    	double scale=0.6;  //图像的缩放系数要不然那四个角就出去了
    	cv2DRotationMatrix(center,angle,scale,rot_mat);  //计算旋转矩阵
    	cvWarpAffine(src,dst,rot_mat);  //旋转图像
    	cvNamedWindow("my",1);
    	cvShowImage("my",dst);
    	cvWaitKey();
    	return 0;
    
    }
    透视变换代码如下:

    #include<cv.h>
    #include<highgui.h>
    int main()
    {
    	
    	CvPoint2D32f srcQuad[4],dstQuad[4];
    	CvMat* warp_matrix=cvCreateMat(3,3,CV_32FC1);
    	IplImage *src,*dst;
    	src=cvLoadImage("1.jpg",1);  ///原图
    	dst=cvCloneImage(src);  //目标图
    	dst->origin=src->origin;
    	cvZero(dst);
    	srcQuad[0].x=0;
    	srcQuad[0].y=0;
    	srcQuad[1].x=src->width-1;
    	srcQuad[1].y=0;
    	srcQuad[2].x=0;
    	srcQuad[2].y=src->height-1;
    	srcQuad[3].x=src->width-1;
    	srcQuad[3].y=src->height-1;
    	dstQuad[0].x=src->width*0.05;
    	dstQuad[0].y=src->height*0.33;
    	dstQuad[1].x=src->width*0.9;
    	dstQuad[1].y=src->height*0.25;
    	dstQuad[2].x=src->width*0.2;
    	dstQuad[2].y=src->height*0.7;
    	dstQuad[3].x=src->width*0.8;
    	dstQuad[3].y=src->height*0.9;
    	//-----------------------方法一------------------------
    	//----通过现有的变换后的点来求这个变换矩阵-----------
    	cvGetPerspectiveTransform(srcQuad,dstQuad,warp_matrix); //求变换矩阵
    	cvWarpPerspective(src,dst,warp_matrix);  //利用求得的变换矩阵来变换图像
    	cvNamedWindow("warp",1);
    	cvShowImage("warp",dst);
    	
    	cvWaitKey();
    	return 0;
    
    }


    采用opencv2实现仿射变换

    #include<opencv2/opencv.hpp>
    #include<opencv2/imgproc/imgproc.hpp>
    #include<iostream>
    using namespace cv;
    using namespace std;
    int main()
    {
    	Point2f srcTriangle[3];
    	Point2f dstTriangle[3];
    	Mat rotMat(2,3,CV_32FC1);
    	Mat warpMat(2,3,CV_32FC1);
    	Mat srcImage,dstImage_warp,dstImage_warp_rotate;
    	srcImage=imread("1.jpg",1);
    	dstImage_warp=Mat::zeros(srcImage.rows,srcImage.cols,srcImage.type());
    	//仿射变换只需要3个点来确定,因为放射变换都是平行四边形
    	srcTriangle[0]=Point2f(0,0);  //图像的左下角
    	srcTriangle[1]=Point2f(static_cast<float>(srcImage.cols-1),0);  //图像的右下角
    	srcTriangle[2]=Point2f(0,static_cast<float>(srcImage.rows-1));  //图像的左上角
    
    	dstTriangle[0]=Point2f(static_cast<float>(srcImage.cols*0.0),static_cast<float>(srcImage.rows*0.33)); //(0,0)点变换后的坐标
    	dstTriangle[1]=Point2f(static_cast<float>(srcImage.cols*0.65),static_cast<float>(srcImage.rows*0.35)); //
    	dstTriangle[2]=Point2f(static_cast<float>(srcImage.cols*0.15),static_cast<float>(srcImage.rows*0.6));
    	warpMat=getAffineTransform(srcTriangle,dstTriangle);  //实际就是通过四个角的变换前后的位置来确定变换矩阵(前后四个角的位置上面给出)
    	warpAffine(srcImage,dstImage_warp,warpMat,dstImage_warp.size());  //利用这个得到的变换矩阵来变换图像
    	//这里可以想象成先让图像上几个点以给定的规则排列(这里是四个角的点),然后其他的都按这个规则来排列
    	imshow("xiaoguo",dstImage_warp);  //显示效果图
    
    	//----------------第二种方式-------------------------------
    	Point center=Point(dstImage_warp.cols/2,dstImage_warp.rows/2);
    	double angle=-30.0;
    	double scale=0.8;
    	rotMat=getRotationMatrix2D(center,angle,scale); //通过中心点,旋转角度,缩放大小构造旋转矩阵
    	warpAffine(dstImage_warp,dstImage_warp_rotate,rotMat,dstImage_warp_rotate.size()); //同前面一样
    	imshow("xiaoguo2",dstImage_warp_rotate);
    	waitKey(0);
    	return 0;
    }




    展开全文
  • VS2008下用C#实现Emgucv实现仿射变换代码例程,用于图像处理。
  • 图像仿射变换——MatLab代码实现

    千次阅读 2019-10-06 16:49:19
    这里先说一下我们的目的,最近在用Pix2Pix 做一个项目的时候,...为了解决这一问题,我们使用图像仿射变换技术来解决这个问题,仿射变换的原理网上一搜就有一大把,这里不做介绍,这里只给出代码实现。 img_x = "./1...
  • 图片的仿射变换

    2020-04-17 17:13:37
    实现图片仿射变换代码如下: import cv2 import numpy as np img = cv2.imread('image1.jpg',1) imgInfo = img.shape height = imgInfo[0] width = imgInfo[1] #src 3-> dst 3(左上角 左下角 右上角) matSrc = np...
  • 其中IVT算法的仿射变换似乎在各种视觉跟踪算法中用得更多,但是代码上没有做很详细的解释,我查找各类原创的论文、博客,似乎也没有讲清楚IVT仿射变换代码实现的基本原理。IVT中用到affparam2mat...
  • 同样是车牌识别,此次的车牌识别,需要进行连续两次的仿射变化,才能成功识别。代码如下:* 读图像read_image (Chepai2, 'E:/halcon实战/chepai2.jpg')* rgb图像转灰度图像rgb1_to_gray(Chepai2, GrayImage)* 1. ...
  • 算法框架和步骤可以以图像旋转算法为参考。 ...仿射变换公式: i´= a*i+b*j+ai  j´= c*i+d*j+aj [i´, j´,1] = [i, j, 1][a b 0; c d 0; ai aj 1];  function [im] = aff(I,T)
  • 一个仿射变换加密的Matlab程序源代码,通过设定的密钥参数k1、k2对给定的明文进行加密得到相应的密文。附带TXT文本代码
  • 透视、仿射变换代码

    千次阅读 2013-09-29 13:15:28
    //计算矩阵仿射变换 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 ...
  • 图像仿射变换

    2020-04-23 21:36:08
    图像处理中,可应用仿射变换对二维图像进行平移(Translation)、缩放(Scale)、旋转(Rotation)、翻转(Flip)等操作,同时,这些操作可以进行复合操作。 仿射变换可以用矩阵运算劳作,该矩阵称为变换矩阵。 ...
  • 今天遇到一个问题是关于仿射变换的,但是由于没有将仿射变换的具体原理型明白,看别人的代码看的很费解,最后终于在师兄的帮助下将原理弄明白了,我觉得最重要的是理解仿射变换可以看成是几种简单变换的复合实现, ...
  • //【3】设置原图像和目标图像上的三组点以计算仿射变换 srcTriangle[0] = Point2f(0, 0); srcTriangle[1] = Point2f(static_cast(srcImage.cols - 1), 0); srcTriangle[2] = Point2f(0, static_cast(srcImage.rows ...
  • 仿射变换详解 warpAffine

    万次阅读 热门讨论 2016-11-25 14:09:28
    今天遇到一个问题是关于仿射变换的,但是由于没有将仿射变换的具体原理型明白,看别人的代码看的很费解,最后终于在师兄的帮助下将原理弄明白了,我觉得最重要的是理解仿射变换可以看成是几种简单变换的复合实现, ...
  • Task01 OpenCV框架与图像仿射变换一、仿射变换原理1.1仿射变换过程1.2坐标系与变换矩阵二、仿射变换的种类与特点2.1仿射变换种类2.2仿射变换特点2.3仿射变换相关函数仿射函数翻转函数缩放函数三、基于OpenCV的代码...
  • 文章目录目标原理什么是仿射变换?我们如何得到一个仿射变换?代码这个程序是做什么的?代码如下所示:解释结果 目标 此教程我们将学会: 使用OpenCV函数 cv::warpAffine 实现简单的重新映射例程。 使用OpenCV函数 cv:...
  • Opencv之仿射变换

    2018-08-23 11:25:57
    opencv中通过定义两个array(pos1,pos2),结合cv2.getAffineTransform(pos1,pos2)来实现仿射变换。 pos1和pos2是3X2的矩阵,每一行都是一个点的位置,其中pos1定义的是原图中三个点的位置,pos2定义的是仿射后...
  • 纯C++仿射变换实现

    2020-10-16 17:06:33
    目录纯C++仿射变换实现前言原理代码实现结果验证 纯C++仿射变换实现 最近需要使用仿射变换进行一些应用,但又不想用到OpenCV和Halcon之类的库进行,就自己写了一个计算和实现函数,大概记录一下这个过程。 基本概念...
  • 筛选出来的点得坐标已经显示在PxRow、PxColunm里边 * Image Acquisition 01: Code generated by Image Acquisition 01 read_image (Image, 'C:/Users/Administrator/Desktop/标定板图片.png') dev_close_window () ...
  • 仿射变换理论讲解:数字图像处理——仿射变换 仿射变换例程讲解:null 仿射变换算子解析:null 代码 set_system ('clip_region', 'false') dev_open_window (0,0,500,500, 'black', WindowHandle) dev_set_part (-...

空空如也

空空如也

1 2 3 4 5 ... 19
收藏数 376
精华内容 150
关键字:

仿射变换代码