精华内容
下载资源
问答
  • warpaffine
    千次阅读
    2021-04-18 10:58:46

    warpAffine 是图像处理中比较常见的一种变换,可以将图像校正或对齐。

    对于线性插值方式,OpenCV 首先将坐标映射保存成两张图,然后调用 remap 函数。第二步是比较耗时的部分,并且 warpPerspective 亦采用此处理。

    remap 通过构建查找表来存储系数乘积,这样减少了乘法运算次数。

    由于篇幅过长,将文章分成 warpAffineremap 两部分。

    warpAffine
    remap
    WarpPerspective

    cv::warpAffine

    cv::warpAffine
    hal::warpAffine

    检查输入通道数量以及插值类型。
    如果目的矩阵为空则创建内存,如果是原地操作则复制一份源数据。

        CV_INSTRUMENT_REGION();
    
        int interpolation = flags & INTER_MAX;
        CV_Assert( _src.channels() <= 4 || (interpolation != INTER_LANCZOS4 &&
                                            interpolation != INTER_CUBIC) );
    
        CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat() &&
                   _src.cols() <= SHRT_MAX && _src.rows() <= SHRT_MAX,
                   ocl_warpTransform_cols4(_src, _dst, _M0, dsize, flags, borderType,
                                           borderValue, OCL_OP_AFFINE))
    
        CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(),
                   ocl_warpTransform(_src, _dst, _M0, dsize, flags, borderType,
                                     borderValue, OCL_OP_AFFINE))
    
        Mat src = _src.getMat(), M0 = _M0.getMat();
        _dst.create( dsize.empty() ? src.size() : dsize, src.type() );
        Mat dst = _dst.getMat();
        CV_Assert( src.cols > 0 && src.rows > 0 );
        if( dst.data == src.data )
            src = src.clone();
    

    如果未设置WARP_INVERSE_MAP,则对参数矩阵M求逆。

        double M[6] = {0};
        Mat matM(2, 3, CV_64F, M);
        if( interpolation == INTER_AREA )
            interpolation = INTER_LINEAR;
    
        CV_Assert( (M0.type() == CV_32F || M0.type() == CV_64F) && M0.rows == 2 && M0.cols == 3 );
        M0.convertTo(matM, matM.type());
    
        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;
        }
    
    #if defined (HAVE_IPP) && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_WARPAFFINE
        CV_IPP_CHECK()
        {
            int type = src.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);
            if( ( depth == CV_8U || depth == CV_16U || depth == CV_32F ) &&
               ( cn == 1 || cn == 3 || cn == 4 ) &&
               ( interpolation == INTER_NEAREST || interpolation == INTER_LINEAR || interpolation == INTER_CUBIC) &&
               ( borderType == cv::BORDER_TRANSPARENT || borderType == cv::BORDER_CONSTANT) )
            {
                ippiWarpAffineBackFunc ippFunc = 0;
                if ((flags & WARP_INVERSE_MAP) != 0)
                {
                    ippFunc =
                    type == CV_8UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_8u_C1R :
                    type == CV_8UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_8u_C3R :
                    type == CV_8UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_8u_C4R :
                    type == CV_16UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_16u_C1R :
                    type == CV_16UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_16u_C3R :
                    type == CV_16UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_16u_C4R :
                    type == CV_32FC1 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_32f_C1R :
                    type == CV_32FC3 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_32f_C3R :
                    type == CV_32FC4 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_32f_C4R :
                    0;
                }
                else
                {
                    ippFunc =
                    type == CV_8UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffine_8u_C1R :
                    type == CV_8UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffine_8u_C3R :
                    type == CV_8UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffine_8u_C4R :
                    type == CV_16UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffine_16u_C1R :
                    type == CV_16UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffine_16u_C3R :
                    type == CV_16UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffine_16u_C4R :
                    type == CV_32FC1 ? (ippiWarpAffineBackFunc)ippiWarpAffine_32f_C1R :
                    type == CV_32FC3 ? (ippiWarpAffineBackFunc)ippiWarpAffine_32f_C3R :
                    type == CV_32FC4 ? (ippiWarpAffineBackFunc)ippiWarpAffine_32f_C4R :
                    0;
                }
                int mode =
                interpolation == INTER_LINEAR ? IPPI_INTER_LINEAR :
                interpolation == INTER_NEAREST ? IPPI_INTER_NN :
                interpolation == INTER_CUBIC ? IPPI_INTER_CUBIC :
                0;
                CV_Assert(mode && ippFunc);
    
                double coeffs[2][3];
                for( int i = 0; i < 2; i++ )
                    for( int j = 0; j < 3; j++ )
                        coeffs[i][j] = matM.at<double>(i, j);
    
                bool ok;
                Range range(0, dst.rows);
                IPPWarpAffineInvoker invoker(src, dst, coeffs, mode, borderType, borderValue, ippFunc, &ok);
                parallel_for_(range, invoker, dst.total()/(double)(1<<16));
                if( ok )
                {
                    CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT);
                    return;
                }
                setIppErrorStatus();
            }
        }
    #endif
    

    调用命名空间中的 hal::warpAffine 函数,各参数独立传入。

        hal::warpAffine(src.type(), src.data, src.step, src.cols, src.rows, dst.data, dst.step, dst.cols, dst.rows,
                        M, interpolation, borderType, borderValue.val);
    

    hal::warpAffine

    hal::warpAffine
    parallel_for_
    WarpAffineInvoker

    d s t ( x , y ) = s r c ( M 11 x + M 12 y + M 13 , M 21 x + M 22 y + M 23 ) \mathrm{dst}(x,y)=\mathrm{src}(M_{11}x+M_{12}y+M_{13}, M_{21}x+M_{22}y+M_{23}) dst(x,y)=src(M11x+M12y+M13,M21x+M22y+M23)

    OpenCV 屏蔽了 Carotene 中的实现。
    根据描述构建出源和目的矩阵。
    INTER_BITS 为5,即插值所用比特数。
    预先计算行元素变换系数,adelta M 11 x M_{11}x M11xbdelta M 21 x M_{21}x M21x

        CALL_HAL(warpAffine, cv_hal_warpAffine, src_type, src_data, src_step, src_width, src_height, dst_data, dst_step, dst_width, dst_height, M, interpolation, borderType, borderValue);
    
        Mat src(Size(src_width, src_height), src_type, const_cast<uchar*>(src_data), src_step);
        Mat dst(Size(dst_width, dst_height), src_type, dst_data, dst_step);
    
        int x;
        AutoBuffer<int> _abdelta(dst.cols*2);
        int* adelta = &_abdelta[0], *bdelta = adelta + dst.cols;
        const int AB_BITS = MAX(10, (int)INTER_BITS);
        const int AB_SCALE = 1 << AB_BITS;
    
        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);
        }
    

    创建调用者 WarpAffineInvokerparallel_for_ 函数并行调用。

        Range range(0, dst.rows);
        WarpAffineInvoker invoker(src, dst, interpolation, borderType,
                                  Scalar(borderValue[0], borderValue[1], borderValue[2], borderValue[3]),
                                  adelta, bdelta, M);
        parallel_for_(range, invoker, dst.total()/(double)(1<<16));
    

    WarpAffineInvoker

    WarpAffineInvoker
    ParallelLoopBody
    public:
        WarpAffineInvoker(const Mat &_src, Mat &_dst, int _interpolation, int _borderType,
                          const Scalar &_borderValue, int *_adelta, int *_bdelta, const double *_M) :
            ParallelLoopBody(), src(_src), dst(_dst), interpolation(_interpolation),
            borderType(_borderType), borderValue(_borderValue), adelta(_adelta), bdelta(_bdelta),
            M(_M)
        {
        }
    

    operator()

    处理操作符函数。

    BLOCK_SZ指定分块大小。
    XY存储源图上对应的坐标,A为辅助系数。
    INTER_BITS 为5。
    INTER_TAB_SIZE 2 I N T E R _ B I T S = 32 2^{\mathrm{INTER\_BITS}}=32 2INTER_BITS=32,这样双线性插值表大小为32x32x4。
    AB_SCALE 2 10 2^{10} 210。这样计算结果的 10.6 的格式存储。
    处理区域为BLOCK_SZ/2行、BLOCK_SZ*2列的矩形。为什么?

        virtual void operator() (const Range& range) const CV_OVERRIDE
        {
            const int BLOCK_SZ = 64;
            AutoBuffer<short, 0> __XY(BLOCK_SZ * BLOCK_SZ * 2), __A(BLOCK_SZ * BLOCK_SZ);
            short *XY = __XY.data(), *A = __A.data();
            const int AB_BITS = MAX(10, (int)INTER_BITS);
            const int AB_SCALE = 1 << AB_BITS;
            int round_delta = interpolation == INTER_NEAREST ? AB_SCALE/2 : AB_SCALE/INTER_TAB_SIZE/2, x, y, x1, y1;
        #if CV_TRY_AVX2
            bool useAVX2 = CV_CPU_HAS_SUPPORT_AVX2;
        #endif
        #if CV_TRY_SSE4_1
            bool useSSE4_1 = CV_CPU_HAS_SUPPORT_SSE4_1;
        #endif
    
            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);
    

    xy指向结果矩阵的当前行行首。
    X0 M 12 y + M 13 M_{12}y+M_{13} M12y+M13
    Y0 M 22 x + M 23 M_{22}x+M_{23} M22x+M23
    round_delta为16。
    _XY将缓冲区__XY封装成了矩阵。
    dpart为结果的 RoI 区域。

            for( y = range.start; y < range.end; y += bh0 )
            {
                for( x = 0; x < dst.cols; x += bw0 )
                {
                    int bw = std::min( bw0, dst.cols - x);
                    int bh = std::min( bh0, range.end - y);
    
                    Mat _XY(bh, bw, CV_16SC2, XY), matA;
                    Mat dpart(dst, Rect(x, y, bw, bh));
    
                    for( y1 = 0; y1 < bh; y1++ )
                    {
                        short* xy = XY + y1*bw*2;
                        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;
    

    如果是最近邻方法。
    WarpAffineInvoker_Blockline_SSE41

                        if( interpolation == INTER_NEAREST )
                        {
                            x1 = 0;
                            #if CV_TRY_SSE4_1
                            if( useSSE4_1 )
                                opt_SSE4_1::WarpAffineInvoker_Blockline_SSE41(adelta + x, bdelta + x, xy, X0, Y0, bw);
                            else
                            #endif
                            {
                                #if CV_SIMD128
                                {
                                    v_int32x4 v_X0 = v_setall_s32(X0), v_Y0 = v_setall_s32(Y0);
                                    int span = v_uint16x8::nlanes;
                                    for( ; x1 <= bw - span; x1 += span )
                                    {
                                        v_int16x8 v_dst[2];
                                        #define CV_CONVERT_MAP(ptr,offset,shift) v_pack(v_shr<AB_BITS>(shift+v_load(ptr + offset)),\
                                                                                        v_shr<AB_BITS>(shift+v_load(ptr + offset + 4)))
                                        v_dst[0] = CV_CONVERT_MAP(adelta, x+x1, v_X0);
                                        v_dst[1] = CV_CONVERT_MAP(bdelta, x+x1, v_Y0);
                                        #undef CV_CONVERT_MAP
                                        v_store_interleave(xy + (x1 << 1), v_dst[0], v_dst[1]);
                                    }
                                }
                                #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);
                                }
                            }
                        }
    

    对于双线性插值。
    alpha指向当前行。
    avx2可以调用 warpAffineBlockline
    v_mask为向量掩码。
    每次处理span*2个数。
    v_setall_s32 通过 OPENCV_HAL_IMPL_NEON_INIT 宏来定义。
    v_load 从存储器中加载寄存器内容。
    计算 M 11 x + M 12 y + M 13 M_{11}x+M_{12}y+M_{13} M11x+M12y+M13 M 21 x + M 22 y + M 23 M_{21}x+M_{22}y+M_{23} M21x+M22y+M23 时,是两个v_int32x4相加(vaddq_s32),得到v_int32x4的结果。然后右移 (10-5)位。
    v_shr 右移。
    v_pack 将两个数据宽度减半后拼到一起。
    两次右移后
    v_store_interleave 将2个寄存器中的数据交错存储到内存中。
    v_shl 左移。
    v_store 将寄存器内容存储到内存中。
    v_Y0v_X0合并。

                        else
                        {
                            short* alpha = A + y1*bw;
                            x1 = 0;
                            #if CV_TRY_AVX2
                            if ( useAVX2 )
                                x1 = opt_AVX2::warpAffineBlockline(adelta + x, bdelta + x, xy, alpha, X0, Y0, bw);
                            #endif
                            #if CV_SIMD128
                            {
                                v_int32x4 v__X0 = v_setall_s32(X0), v__Y0 = v_setall_s32(Y0);
                                v_int32x4 v_mask = v_setall_s32(INTER_TAB_SIZE - 1);
                                int span = v_float32x4::nlanes;
                                for( ; x1 <= bw - span * 2; x1 += span * 2 )
                                {
                                    v_int32x4 v_X0 = v_shr<AB_BITS - INTER_BITS>(v__X0 + v_load(adelta + x + x1));
                                    v_int32x4 v_Y0 = v_shr<AB_BITS - INTER_BITS>(v__Y0 + v_load(bdelta + x + x1));
                                    v_int32x4 v_X1 = v_shr<AB_BITS - INTER_BITS>(v__X0 + v_load(adelta + x + x1 + span));
                                    v_int32x4 v_Y1 = v_shr<AB_BITS - INTER_BITS>(v__Y0 + v_load(bdelta + x + x1 + span));
    
                                    v_int16x8 v_xy[2];
                                    v_xy[0] = v_pack(v_shr<INTER_BITS>(v_X0), v_shr<INTER_BITS>(v_X1));
                                    v_xy[1] = v_pack(v_shr<INTER_BITS>(v_Y0), v_shr<INTER_BITS>(v_Y1));
                                    v_store_interleave(xy + (x1 << 1), v_xy[0], v_xy[1]);
    
                                    v_int32x4 v_alpha0 = v_shl<INTER_BITS>(v_Y0 & v_mask) | (v_X0 & v_mask);
                                    v_int32x4 v_alpha1 = v_shl<INTER_BITS>(v_Y1 & v_mask) | (v_X1 & v_mask);
                                    v_store(alpha + x1, v_pack(v_alpha0, v_alpha1));
                                }
                            }
                            #endif
    

    XY为映射到源图上的像素坐标。alphaYX的小数部分合并存储。

                            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)));
                            }
                        }
                    }
    

    cv::remapsrc中找到dpart的源值。最近邻方式不需要第二张图。

                    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;
        const double *M;
    

    参考资料:

    更多相关内容
  • 图像预处理之warpaffine与双线性插值及其高性能实现 视频讲解:https://www.bilibili.com/video/BV1ZU4y1A7EG 代码Repo:https://github.com/shouxieai/tensorRT_Pro 本文为视频讲解的个人笔记。 warpaffine矩阵...

    图像预处理之warpaffine与双线性插值及其高性能实现

    视频讲解:https://www.bilibili.com/video/BV1ZU4y1A7EG

    代码Repo:https://github.com/shouxieai/tensorRT_Pro

    本文为视频讲解的个人笔记。

    warpaffine矩阵变换

    对于坐标点的变换,我们通常考虑的是旋转、缩放、平移这三种变换。例如将点 P ( x , y ) P(x,y) P(x,y) 旋转 θ \theta θ 度,缩放 s c a l e scale scale 倍,平移 o x , o y ox,oy ox,oy 。warpaffine 将坐标点的旋转、缩放、平移三种操作集成为一个矩阵乘法运算。

    旋转变换

    我们先来看旋转,如图所示,我们要将点 P ( x , y ) P(x,y) P(x,y) 旋转到点 P ′ ( x ′ , y ′ ) P'(x',y') P(x,y) ,推导的过程很简单,我们要求的就是 x ′ , y ′ x',y' x,y 两点的坐标,将其转换为 m × c o s ( θ + α ) m\times cos(\theta+\alpha) m×cos(θ+α) m × s i n ( θ + α ) m\times sin(\theta+\alpha) m×sin(θ+α) ,再用公式展开,即得结果(详见图中公式):

    { x ′ y ′ } = { c o s ( θ ) − s i n ( θ ) s i n ( θ ) c o s ( θ ) } { x y } \left\{ \begin{array}{rc} x' \\ y' \end{array} \right\}= \left\{ \begin{array}{rc} cos(\theta) & -sin(\theta) \\ sin(\theta) & cos(\theta) \end{array} \right\} \left\{ \begin{array}{rc} x \\ y \end{array} \right\} {xy}={cos(θ)sin(θ)sin(θ)cos(θ)}{xy}
    再考虑到我们在图像处理时的坐标系(如在 OpenCV 中的坐标系、常见目标检测的坐标系等)通常是原点在左上角,因此应该为:

    { x ′ y ′ } = { c o s ( θ ) s i n ( θ ) − s i n ( θ ) c o s ( θ ) } { x y } \left\{ \begin{array}{rc} x' \\ y' \end{array} \right\}= \left\{ \begin{array}{rc} cos(\theta) & sin(\theta) \\ -sin(\theta) & cos(\theta) \end{array} \right\} \left\{ \begin{array}{rc} x \\ y \end{array} \right\} {xy}={cos(θ)sin(θ)sin(θ)cos(θ)}{xy}
    将旋转变换的矩阵记为 R R R ,则 P ′ = R P P'=RP P=RP

    在这里插入图片描述

    缩放变换

    缩放变换比较简单,两坐标直接乘以缩放系数 s c a l e scale scale 即可:

    x ′ = x × s c a l e y ′ = y × s c a l e x'=x\times scale \\ y'=y\times scale x=x×scaley=y×scale
    写成矩阵形式即:

    { x ′ y ′ } = { s c a l e 0 0 s c a l e } { x y } \left\{ \begin{array}{rc} x' \\ y' \end{array} \right\}= \left\{ \begin{array}{rc} scale & 0 \\ 0 & scale \end{array} \right\} \left\{ \begin{array}{rc} x \\ y \end{array} \right\} {xy}={scale00scale}{xy}
    将缩放变换的变换矩阵记为 S S S,则:
    P ′ = S P P'=SP P=SP
    则旋转+缩放可以通过矩阵相乘写到同一个矩阵中:
    { x ′ y ′ } = { c o s ( θ ) × s c a l e s i n ( θ ) × s c a l e − s i n ( θ ) × s c a l e c o s ( θ ) × s c a l e } { x y } \left\{ \begin{array}{rc} x' \\ y' \end{array} \right\}= \left\{ \begin{array}{rc} cos(\theta) \times scale & sin(\theta) \times scale \\ -sin(\theta) \times scale & cos(\theta) \times scale \end{array} \right\} \left\{ \begin{array}{rc} x \\ y \end{array} \right\} {xy}={cos(θ)×scalesin(θ)×scalesin(θ)×scalecos(θ)×scale}{xy}
    即: P ′ = S R P P'=SRP P=SRP

    注意旋转和缩放顺序是随意的,不影响结果,这也可以通过代码来验证:

    import numpy as np
    
    theta = 0.8
    scale = 2
    rot = np.array([
        [np.cos(theta), np.sin(theta)],
        [-np.sin(theta), np.cos(theta)]
    ])
    
    sca = np.array([
        [scale, 0],
        [0, scale]
    ])
    
    print(np.allclose(rot @ sca, sca @ rot))
    # 输出:True
    

    平移变换

    平移变换可以表示为:
    x ′ = x + o x y ′ = y + o y x'=x+ox\\ y'=y+oy x=x+oxy=y+oy
    矩阵形式:
    { x ′ y ′ } = { 1 0 0 1 } { x y } + { o x o y } \left\{ \begin{array}{rc} x' \\ y' \end{array} \right\}= \left\{ \begin{array}{rc} 1 & 0 \\ 0 & 1 \end{array} \right\} \left\{ \begin{array}{rc} x \\ y \end{array} \right\} + \left\{ \begin{array}{rc} ox \\ oy \end{array} \right\} {xy}={1001}{xy}+{oxoy}
    可以发现,平移变换直接写成矩阵形式,已经不是单纯的矩阵相乘了,而是多了一个很麻烦的相加的操作。这就很难与我们之前的缩放+旋转的操作合并到一起,该怎么办呢?

    我们可以增加一个维度,将二维的非齐次的形式转换为三维的齐次的形式,即这个知乎回答中所提到的:增加一个维度之后,就可以在高维度通过线性变换来完成低维度的放射变换。(该回答将放射变换讲的很形象,推荐阅读)。

    那么我们增加一维 ( x , y , w ) (x,y,w) (x,y,w),从而将点 P P P 表示为 P ( x w , y w , 1 ) P(\frac{x}{w},\frac{y}{w},1) P(wx,wy,1) ,这样平移变换就也可以表示为齐次矩阵乘的形式:

    { x ′ y ′ w } = { 1 0 o x 0 1 o y 0 0 1 } { x y 1 } \left\{ \begin{array}{rc} x' \\ y' \\ w \\ \end{array} \right\}= \left\{ \begin{array}{rc} 1 & 0 & ox \\ 0 & 1 & oy \\ 0 & 0 & 1 \end{array} \right\} \left\{ \begin{array}{rc} x \\ y \\ 1 \end{array} \right\} xyw=100010oxoy1xy1
    最后我们得到缩放+旋转+平移变换的矩阵表示(注意平移与缩放、旋转的顺序是不能随意调换的):

    { x ′ y ′ w } = { c o s ( θ ) × s c a l e s i n ( θ ) × s c a l e o x − s i n ( θ ) × s c a l e c o s ( θ ) × s c a l e o y 0 0 1 } { x y 1 } \left\{ \begin{array}{rc} x' \\ y' \\ w \end{array} \right\}= \left\{ \begin{array}{rc} cos(\theta) \times scale & sin(\theta) \times scale & ox \\ -sin(\theta) \times scale & cos(\theta) \times scale & oy \\ 0 & 0 & 1 \end{array} \right\} \left\{ \begin{array}{rc} x \\ y \\ 1 \end{array} \right\} xyw=cos(θ)×scalesin(θ)×scale0sin(θ)×scalecos(θ)×scale0oxoy1xy1
    将平移变换的变换矩阵记为 R R R ,则: P ′ = T S R P P'=TSRP P=TSRP ,可以将整个 warpaffine 三个变换操作的矩阵记为 M M M ,即: M = T S R ,    P ′ = M P M=TSR,\ \ P'=MP M=TSR,  P=MP

    warpaffine矩阵变换的反变换

    • 旋转矩阵的逆矩阵,即是其转置: R − 1 = R T R^{-1}=R^T R1=RT
    • 整个 warp affine 的三个变换求反变换,对整个变换矩阵求逆即可: P ′ = M P ,    P = M − 1 P ′ P'=MP,\ \ P=M^{-1}P' P=MP,  P=M1P

    目标检测中的常用预处理

    在目标检测中,我们的预处理通常是先对图像进行等比缩放,然后居中,多余部分填充,就类似下图所展示的。

    在这里插入图片描述

    我们将这个过程分为三个步骤:

    1. 等比缩放,矩阵 S S S 实现

    在这里插入图片描述

    1. 将图片中心平移到左上坐标原点,矩阵 O O O 实现

    在这里插入图片描述

    1. 将图片平移到目标位置的重心,矩阵 T T T 实现

    在这里插入图片描述

    三步拆分法,看似麻烦了一点,实际上可以方便我们后续可能会需要到的更复杂的变换(比如在 O O O 平移后加入旋转变换),并且便于记忆。

    三步拆分法的矩阵表达: P ′ = T O S P P'=TOSP P=TOSP

    我们直接写出具体的矩阵:
    s c a l e = m i n ( D s t . w i d t h O r i g i n . w i d t h , D s t . h e i g h t O r i g i n . h e i g h t ) M = { s c a l e 0 − s c a l e × O r i g i n . w i d t h 2 + D s t . w i d t h 2 0 s c a l e − s c a l e × O r i g i n . h e i g h t 2 + D s t . h e i g h t 2 } scale = min(\frac{Dst.width}{Origin.width}, \frac{Dst.height}{Origin.height}) \\ \\ M = \left\{ \begin{array}{ll} scale & 0 & -\frac{scale \times Origin.width}{2} + \frac{Dst.width}{2} \\ 0 & scale & -\frac{scale \times Origin.height}{2} + \frac{Dst.height}{2} \\ \end{array} \right\} scale=min(Origin.widthDst.width,Origin.heightDst.height)M={scale00scale2scale×Origin.width+2Dst.width2scale×Origin.height+2Dst.height}

    { x ′ y ′ } = { s c a l e 0 − s c a l e × O r i g i n . w i d t h 2 + D s t . w i d t h 2 0 s c a l e − s c a l e × O r i g i n . h e i g h t 2 + D s t . h e i g h t 2 } { x y 1 } \left\{ \begin{array}{ll} x' \\ y' \\ \end{array} \right\}= \left\{ \begin{array}{ll} scale & 0 & -\frac{scale \times Origin.width}{2} + \frac{Dst.width}{2} \\ 0 & scale & -\frac{scale \times Origin.height}{2} + \frac{Dst.height}{2} \\ \end{array} \right\} \left\{ \begin{array}{ll} x \\ y \\ 1 \end{array} \right\} {xy}={scale00scale2scale×Origin.width+2Dst.width2scale×Origin.height+2Dst.height}xy1

    逆变换:
    k = s c a l e b 1 = − s c a l e × O r i g i n . w i d t h 2 + D s t . w i d t h 2 b 2 = − s c a l e × O r i g i n . h e i g h t 2 + D s t . h e i g h t 2 x ′ = k x + b 1 y ′ = k y + b 2 x = x ′ − b 1 k = x ′ × 1 k + ( − b 1 k ) y = y ′ − b 2 k = y ′ × 1 k + ( − b 2 k ) M − 1 = { 1 k 0 − b 1 k 0 1 k − b 2 k } k = scale \\ b1 = -\frac{scale \times Origin.width}{2} + \frac{Dst.width}{2} \\ b2 = -\frac{scale \times Origin.height}{2} + \frac{Dst.height}{2} \\ x' = kx + b1 \\ y' = ky + b2 \\ x = \frac{x' - b1}{k} = x'\times \frac{1}{k} + (-\frac{b1}{k}) \\ y = \frac{y' - b2}{k} = y'\times \frac{1}{k} + (-\frac{b2}{k}) \\ M^{-1} = \left\{ \begin{array}{ll} \frac{1}{k} & 0 & -\frac{b1}{k} \\ 0 & \frac{1}{k} & -\frac{b2}{k} \\ \end{array} \right\} k=scaleb1=2scale×Origin.width+2Dst.widthb2=2scale×Origin.height+2Dst.heightx=kx+b1y=ky+b2x=kxb1=x×k1+(kb1)y=kyb2=y×k1+(kb2)M1={k100k1kb1kb2}

    warpaffine正逆变换代码实验

    TODO

    双线性插值

    线性插值

    距离目标点越远,影响就越小,因此权重是对面的距离占比。

    如目标点距离冷水 0.6,距离热水 0.4,则冷水权重为 0.4 ,热水权重为 0.6 。

    在这里插入图片描述

    p0 = 20    # 冷水
    p1 = 100   # 热水
    pos = 0.6  # 应该多少度
    
    value = (1 - pos) * p0 + pos * p1
    print(value)
    

    双线性插值

    线性插值的二维版本,原理一直,只是权重从计算长度占比改为计算面积占比。

    调色板,红点对目标点(紫点)的影响权重即为对面的面积(红框面积)占总面积的比例。

    在这里插入图片描述

    高性能实现

    为什么高性能?

    • 我们在操作每个像素的过程中,可以将模型需要的像素级预处理(如减均值除标准差、除以255、BGR通道转换等)一并做了,避免多个操作分开来反复对每个像素进行循环访问这种低效行为。
    • warpaffine 极其适合通过 cuda 核函数进行 GPU 加速。可以参考 repo 中的 preprocess_kernel.cu 。完整代码比较长这里就不放了。
    • 以下是 warpaffine 双线性插值的 Python 实现,供参考:
    def pyWarpAffine(image, M, dst_size, constant=(0, 0, 0)):
        
        # 注意输入的M矩阵格式,是Origin->Dst
        # 而这里需要的是Dst->Origin,所以要取逆矩阵
        M = cv2.invertAffineTransform(M)
        constant = np.array(constant)
        ih, iw   = image.shape[:2]
        dw, dh   = dst_size
        dst      = np.full((dh, dw, 3), constant, dtype=np.uint8)
        irange   = lambda p: p[0] >= 0 and p[0] < iw and p[1] >= 0 and p[1] < ih
        
        for y in range(dh):
            for x in range(dw):
                
                homogeneous = np.array([[x, y, 1]]).T
                ox, oy = M @ homogeneous
                
                low_ox = int(np.floor(ox))
                low_oy = int(np.floor(oy))
                high_ox = low_ox + 1
                high_oy = low_oy + 1
                
                # p0     p1
                #      o
                # p2     p3
                
                pos = ox - low_ox, oy - low_oy
                p0_area = (1 - pos[0]) * (1 - pos[1])
                p1_area = pos[0] * (1 - pos[1])
                p2_area = (1 - pos[0]) * pos[1]
                p3_area = pos[0] * pos[1]
                
                p0 = low_ox, low_oy
                p1 = high_ox, low_oy
                p2 = low_ox, high_oy
                p3 = high_ox, high_oy
                p0_value = image[p0[1], p0[0]] if irange(p0) else constant
                p1_value = image[p1[1], p1[0]] if irange(p1) else constant
                p2_value = image[p2[1], p2[0]] if irange(p2) else constant
                p3_value = image[p3[1], p3[0]] if irange(p3) else constant
                dst[y, x] = p0_area * p0_value + p1_area * p1_value + p2_area * p2_value + p3_area * p3_value
                # 交换bgr  rgb
                # normalize ->  -mean /std
                # 1行代码实现normalize , /255.0
                # bgr bgr bgr -> bbb ggg rrr
                # focus
                # focus offset, 1行代码实现focus
                
        return dst
    
                
    cat1 = cv2.imread("cat1.png")
    #acat1_cv, M, inv = align(cat1, (100, 100))
    M = cv2.getRotationMatrix2D((0, 0), 30, 0.5)
    acat1_cv = cv2.warpAffine(cat1, M, (100, 100))
    acat1_py = pyWarpAffine(cat1, M, (100, 100))
    
    plt.figure(figsize=(10, 10))
    plt.subplot(1, 2, 1)
    plt.title("OpenCV")
    plt.imshow(acat1_cv[..., ::-1])
    
    plt.subplot(1, 2, 2)
    plt.title("PyWarpAffine")
    plt.imshow(acat1_py[..., ::-1])
    
    展开全文
  • ncnn 中的 warpAffine

    2021-05-09 16:15:48
    详细请参考 [opencv ncnn warpaffine 性能测试](https://zhuanlan.zhihu.com/p/355147243)。在具体实现方面,优点是简洁明快,双线性插值采用10bit 量化,比 OpenCV 精度高;缺点是边界填充仅支持常量值。 下面从 ...

    ncnn 的仿射变换对于深度学习的预处理即小图变换进行了优化,速度可达到 OpenCV 的两倍。详细请参考 opencv ncnn warpaffine 性能测试。在具体实现方面,优点是简洁明快,双线性插值采用10bit 量化,比 OpenCV 精度高;缺点是边界填充仅支持常量值。

    下面从 ncnn 的测试代码入手进行分析。

    test_mat_pixel_affine.cpp

        SRAND(7767517);
    
        return test_mat_pixel_affine_0() || test_mat_pixel_affine_1();
    

    test_mat_pixel_affine_0

        return 0
               || test_mat_pixel_affine_a(60, 70)
               || test_mat_pixel_affine_b(60, 70)
               || test_mat_pixel_affine_c(60, 70)
               || test_mat_pixel_affine_d(60, 70)
               || test_mat_pixel_affine_e(60, 70)
               || test_mat_pixel_affine_f(60, 70)
               || test_mat_pixel_affine_g(60, 70)
    
               || test_mat_pixel_affine_a(120, 160)
               || test_mat_pixel_affine_b(120, 160)
               || test_mat_pixel_affine_c(120, 160)
               || test_mat_pixel_affine_d(120, 160)
               || test_mat_pixel_affine_e(120, 160)
               || test_mat_pixel_affine_f(120, 160)
               || test_mat_pixel_affine_g(120, 160)
    
               || test_mat_pixel_affine_a(220, 330)
               || test_mat_pixel_affine_b(220, 330)
               || test_mat_pixel_affine_c(220, 330)
               || test_mat_pixel_affine_d(220, 330)
               || test_mat_pixel_affine_e(220, 330)
               || test_mat_pixel_affine_f(220, 330)
               || test_mat_pixel_affine_g(220, 330);
    

    test_mat_pixel_affine_a

    get_rotation_matrix 生成变换参数矩阵。

        for (int c = 1; c <= 4; c++)
        {
            ncnn::Mat a0 = RandomMat(w, h, c);
    
            float tm[6];
            float tm_inv[6];
            ncnn::get_rotation_matrix(10.f, 0.15f, w / 2, h / 2, tm);
            ncnn::invert_affine_transform(tm, tm_inv);
    
            ncnn::Mat a1(w / 2, h / 2, (size_t)c, c);
            ncnn::Mat a2 = a0.clone();
    
            if (c == 1)
            {
                ncnn::warpaffine_bilinear_c1(a0, w, h, a1, w / 2, h / 2, tm, 0);
                ncnn::warpaffine_bilinear_c1(a1, w / 2, h / 2, a2, w, h, tm_inv, -233);
            }
            if (c == 2)
            {
                ncnn::warpaffine_bilinear_c2(a0, w, h, a1, w / 2, h / 2, tm, 0);
                ncnn::warpaffine_bilinear_c2(a1, w / 2, h / 2, a2, w, h, tm_inv, -233);
            }
            if (c == 3)
            {
                ncnn::warpaffine_bilinear_c3(a0, w, h, a1, w / 2, h / 2, tm, 0);
                ncnn::warpaffine_bilinear_c3(a1, w / 2, h / 2, a2, w, h, tm_inv, -233);
            }
            if (c == 4)
            {
                ncnn::warpaffine_bilinear_c4(a0, w, h, a1, w / 2, h / 2, tm, 0);
                ncnn::warpaffine_bilinear_c4(a1, w / 2, h / 2, a2, w, h, tm_inv, -233);
            }
    
            if (CompareNearlyEqual(a0, a2) != 0)
            {
                fprintf(stderr, "test_mat_pixel_affine_a failed w=%d h=%d c=%d\n", w, h, c);
                return -1;
            }
        }
    
        return 0;
    

    get_rotation_matrix

    [ x ′   y ′ w ′ ] = [ 1 0 t x 0 1 t y 0 0 1 ] [ cos ⁡ θ − sin ⁡ θ 0 sin ⁡ θ cos ⁡ θ 0 0 0 1 ] [ s x 0 0 0 s y 0 0 0 1 ] [ 1 0 − t x 0 1 − t y 0 0 1 ] [ x   y w ] = [ cos ⁡ θ − sin ⁡ θ t x sin ⁡ θ cos ⁡ θ t y 0 0 1 ] [ s x 0 0 0 s y 0 0 0 1 ] [ 1 0 − t x 0 1 − t y 0 0 1 ] [ x   y w ] = [ s x cos ⁡ θ − s y sin ⁡ θ t x s x sin ⁡ θ s y cos ⁡ θ t y 0 0 1 ] [ 1 0 − t x 0 1 − t y 0 0 1 ] [ x   y w ] = [ s x cos ⁡ θ − s y sin ⁡ θ − t x s x cos ⁡ + t y s y sin ⁡ θ + t x s x sin ⁡ θ s y cos ⁡ θ − t x s x sin ⁡ θ − t y s y cos ⁡ θ + t y 0 0 1 ] [ x   y w ] \begin{aligned} \begin{bmatrix} x' \ \\ y' \\ w'\end{bmatrix} &= \begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y\\ 0 & 0 & 1\end{bmatrix} \begin{bmatrix} \cos \theta & -\sin \theta & 0\\ \sin \theta & \cos \theta & 0\\ 0 & 0 & 1\end{bmatrix} \begin{bmatrix} s_x & 0 & 0\\ 0 & s_y & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & -t_x \\ 0 & 1 & -t_y\\ 0 & 0 & 1\end{bmatrix} \begin{bmatrix} x \ \\ y \\ w\end{bmatrix}\\ &= \begin{bmatrix} \cos \theta & -\sin \theta & t_x\\ \sin \theta & \cos \theta & t_y\\ 0 & 0 & 1\end{bmatrix}\begin{bmatrix} s_x & 0 & 0\\ 0 & s_y & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & -t_x \\ 0 & 1 & -t_y\\ 0 & 0 & 1\end{bmatrix}\begin{bmatrix} x \ \\ y \\ w\end{bmatrix}\\ &= \begin{bmatrix} s_x\cos \theta & -s_y\sin \theta & t_x\\ s_x\sin \theta & s_y\cos\theta & t_y\\ 0 & 0 & 1\end{bmatrix} \begin{bmatrix} 1 & 0 & -t_x \\ 0 & 1 & -t_y\\ 0 & 0 & 1\end{bmatrix} \begin{bmatrix} x \ \\ y \\ w\end{bmatrix}\\ &= \begin{bmatrix} s_x\cos\theta & -s_y\sin \theta & -t_x s_x\cos + t_y s_y\sin\theta+t_x \\ s_x\sin \theta & s_y\cos \theta & -t_x s_x\sin\theta- t_ys_y\cos\theta + t_y \\ 0 & 0 & 1\end{bmatrix} \begin{bmatrix} x \ \\ y \\ w\end{bmatrix} \end{aligned} x yw=100010txty1cosθsinθ0sinθcosθ0001sx000sy0001100010txty1x yw=cosθsinθ0sinθcosθ0txty1sx000sy0001100010txty1x yw=sxcosθsxsinθ0sysinθsycosθ0txty1100010txty1x yw=sxcosθsxsinθ0sysinθsycosθ0txsxcos+tysysinθ+txtxsxsinθtysycosθ+ty1x yw

    1. 平移坐标,使原点位于 ( t x , t y ) (t_x, t_y) (tx,ty)
    2. 旋转 θ \theta θ
    3. 缩放 ( s x , s y ) (s_x, s_y) (sx,sy)
    4. 平移回去。
        angle *= (float)(3.14159265358979323846 / 180);
        float alpha = cos(angle) * scale;
        float beta = sin(angle) * scale;
    
        tm[0] = alpha;
        tm[1] = beta;
        tm[2] = (1.f - alpha) * dx - beta * dy;
        tm[3] = -beta;
        tm[4] = alpha;
        tm[5] = beta * dx + (1.f - alpha) * dy;
    

    invert_affine_transform

    对于参数矩阵求逆。

        float D = tm[0] * tm[4] - tm[1] * tm[3];
        D = D != 0.f ? 1.f / D : 0.f;
    
        float A11 = tm[4] * D;
        float A22 = tm[0] * D;
        float A12 = -tm[1] * D;
        float A21 = -tm[3] * D;
        float b1 = -A11 * tm[2] - A12 * tm[5];
        float b2 = -A21 * tm[2] - A22 * tm[5];
    
        tm_inv[0] = A11;
        tm_inv[1] = A12;
        tm_inv[2] = b1;
        tm_inv[3] = A21;
        tm_inv[4] = A22;
        tm_inv[5] = b2;
    

    warpaffine_bilinear_c1

    ∣ x s r c y s r c ∣ = ∣ m 00 m 01 t x m 10 m 11 t y ∣ ∣ x y 1 ∣ \begin{vmatrix} x_{src} \\ y_{src} \end{vmatrix} = \begin{vmatrix} m_{00} & m_{01} & t_x \\ m_{10} & m_{11} & t_y \end{vmatrix} \begin{vmatrix} {x} \\ {y} \\ 1 \end{vmatrix} xsrcysrc=m00m10m01m11txtyxy1
    调用同名函数。

        return warpaffine_bilinear_c1(src, srcw, srch, srcw, dst, w, h, w, tm, type, v);
    

    warpaffine_bilinear_c1

    d s t ( x , y ) = s r c ( M 11 x + M 12 y + M 13 , M 21 x + M 22 y + M 23 ) \mathrm{dst}(x,y)=\mathrm{src}(M_{11}x+M_{12}y+M_{13}, M_{21}x+M_{22}y+M_{23}) dst(x,y)=src(M11x+M12y+M13,M21x+M22y+M23)
    adeltabdelta数组中的值和行中位置有关。
    Δ a = M 11 x \Delta a = M_{11}x Δa=M11x
    Δ b = M 21 x \Delta b = M_{21}x Δb=M21x

        const unsigned char* border_color = (const unsigned char*)&v;
        const int wgap = stride - w;
    
        const unsigned char* src0 = src;
        unsigned char* dst0 = dst;
    
    #define SATURATE_CAST_SHORT(X) (short)::std::min(::std::max((int)(X), SHRT_MIN), SHRT_MAX)
    #define SATURATE_CAST_INT(X)   (int)::std::min(::std::max((int)((X) + ((X) >= 0.f ? 0.5f : -0.5f)), INT_MIN), INT_MAX)
    
        std::vector<int> adelta(w);
        std::vector<int> bdelta(w);
        for (int x = 0; x < w; x++)
        {
            adelta[x] = SATURATE_CAST_INT(tm[0] * x * (1 << 10));
            bdelta[x] = SATURATE_CAST_INT(tm[3] * x * (1 << 10));
        }
    

    每次取一行中的8个数。
    X0Y0为当前行对应的值。
    X 0 = M 12 y + M 13 X_0=M_{12}y + M_{13} X0=M12y+M13
    Y 0 = M 12 y + M 23 Y_0=M_{12}y + M_{23} Y0=M12y+M23
    (sx_0, sy_0)(sx_7, sy_7)为源块中的对角坐标。
    sxy_inout=1表示8个数均在行内,sxy_inout=2表示8个数均在行外。

        int y = 0;
        for (; y < h; y++)
        {
            int X0 = SATURATE_CAST_INT((tm[1] * y + tm[2]) * (1 << 10));
            int Y0 = SATURATE_CAST_INT((tm[4] * y + tm[5]) * (1 << 10));
    
            int x = 0;
            for (; x + 7 < w; x += 8)
            {
                int sxy_inout = 0;
                {
                    int X_0 = X0 + adelta[x];
                    int Y_0 = Y0 + bdelta[x];
                    int X_7 = X0 + adelta[x + 7];
                    int Y_7 = Y0 + bdelta[x + 7];
    
                    short sx_0 = SATURATE_CAST_SHORT((X_0 >> 10));
                    short sy_0 = SATURATE_CAST_SHORT((Y_0 >> 10));
                    short sx_7 = SATURATE_CAST_SHORT((X_7 >> 10));
                    short sy_7 = SATURATE_CAST_SHORT((Y_7 >> 10));
    
                    if (((unsigned short)sx_0 < srcw - 1 && (unsigned short)sy_0 < srch - 1) && ((unsigned short)sx_7 < srcw - 1 && (unsigned short)sy_7 < srch - 1))
                    {
                        // all inside
                        sxy_inout = 1;
                    }
                    else if ((sx_0 < -1 && sx_7 < -1) || (sx_0 >= srcw && sx_7 >= srcw) || (sy_0 < -1 && sy_7 < -1) || (sy_0 >= srch && sy_7 >= srch))
                    {
                        // all outside
                        sxy_inout = 2;
                    }
                }
    

    源像素均在行内时:
    vaddq_s32 实现4个整型数相加。[_Xl _Xh] Δ a + X 0 = M 11 x + M 12 y + M 13 \Delta a + X_0 =M_{11}x + M_{12}y + M_{13} Δa+X0=M11x+M12y+M13[_Yl _Yh] Δ b + Y 0 = M 21 x + M 12 y + M 23 \Delta b + Y_0 = M_{21}x + M_{12}y + M_{23} Δb+Y0=M21x+M12y+M23

                if (sxy_inout == 1)
                {
                    // all inside
    #if __ARM_NEON
                    int32x4_t _Xl = vaddq_s32(vdupq_n_s32(X0), vld1q_s32(adelta.data() + x));
                    int32x4_t _Xh = vaddq_s32(vdupq_n_s32(X0), vld1q_s32(adelta.data() + x + 4));
                    int32x4_t _Yl = vaddq_s32(vdupq_n_s32(Y0), vld1q_s32(bdelta.data() + x));
                    int32x4_t _Yh = vaddq_s32(vdupq_n_s32(Y0), vld1q_s32(bdelta.data() + x + 4));
    

    f ( x , y ) = 1 ( x 2 − x 1 ) ( y 2 − y 1 ) [ x 2 − x x − x 1 ] [ f ( Q 11 ) f ( Q 12 ) f ( Q 21 ) f ( Q 22 ) ] [ y 2 − y y − y 1 ] f(x, y) = \frac{1}{(x_2-x_1)(y_2-y_1)}\begin{bmatrix} x_2 -x & x-x_1 \end{bmatrix}\begin{bmatrix} f(Q_{11}) & f(Q_{12}) \\ f(Q_{21}) & f(Q_{22})\end{bmatrix}\begin{bmatrix} y_2-y \\ y-y_1 \end{bmatrix} f(x,y)=(x2x1)(y2y1)1[x2xxx1][f(Q11)f(Q21)f(Q12)f(Q22)][y2yyy1]

    vqshrn_n_s32 带符号的右移饱和(立即数)。将int结果转成了short
    _sxl_sxh为对应到源图上的像素横坐标,_syl_syh为纵坐标。
    vdupq_n_u32 将向量元素复制到向量或标量。
    _v1024m1 1024 − 1 1024-1 10241
    vreinterpretq_u32_s32 向量重新解释强制转换操作,有符号转无符号。

    vmovn_u32 将每个值缩小到原始宽度的一半。

    vcombine_u16 将两个u16合并成32。
    _fx_fy x x x y y y 的小数部分。
    _alpha0_alpha1 x 2 − x x_2 -x x2x x − x 1 x-x_1 xx1_beta0_beta1 y 2 − y y_2 -y y2y y − y 1 y-y_1 yy1
    vsubq_u16 向量减。
    vmull_s16 带符号长乘(向量)。

                    int16x4_t _sxl = vqshrn_n_s32(_Xl, 10);
                    int16x4_t _sxh = vqshrn_n_s32(_Xh, 10);
                    int16x4_t _syl = vqshrn_n_s32(_Yl, 10);
                    int16x4_t _syh = vqshrn_n_s32(_Yh, 10);
    
                    uint32x4_t _v1024m1 = vdupq_n_u32((1 << 10) - 1);
                    uint16x8_t _fx = vcombine_u16(vmovn_u32(vandq_u32(vreinterpretq_u32_s32(_Xl), _v1024m1)), vmovn_u32(vandq_u32(vreinterpretq_u32_s32(_Xh), _v1024m1)));
                    uint16x8_t _fy = vcombine_u16(vmovn_u32(vandq_u32(vreinterpretq_u32_s32(_Yl), _v1024m1)), vmovn_u32(vandq_u32(vreinterpretq_u32_s32(_Yh), _v1024m1)));
    
                    uint16x8_t _alpha0 = vsubq_u16(vdupq_n_u16(1 << 10), _fx);
                    uint16x8_t _alpha1 = _fx;
                    uint16x8_t _beta0 = vsubq_u16(vdupq_n_u16(1 << 10), _fy);
                    uint16x8_t _beta1 = _fy;
    

    vaddw_s16 为有符号宽加。
    _a0l_a0h分别为4个 Q 11 Q_{11} Q11_b0l_b0h分别为4个 Q 21 Q_{21} Q21
    vgetq_lane_s32 从一个向量中提取一个通道(元素)。
    vld2_lane_u8 从内存中以双向量结构加载两个元素,并将其返回到结果中。 加载的值来自连续的存储器地址。 结构中未加载的元素将按原样返回结果。 n 是要加载的元素的索引。
    _a0a1_b0b1中原本为空,每次从指定地址向通道加载一个 N 元素结构。
    Q 11 Q_{11} Q11 Q 12 Q_{12} Q12 的地址是相邻的, Q 21 Q_{21} Q21 Q 22 Q_{22} Q22 亦然。这样 vld2_lane_u8 可以同时加载 f ( Q 11 ) f(Q_{11}) f(Q11) f ( Q 12 ) f(Q_{12}) f(Q12),或 f ( Q 21 ) f(Q_{21}) f(Q21) f ( Q 22 ) f(Q_{22}) f(Q22) 中的一个通道。作为对比,TNN 中的 WarpAffineCalculateOneRow 调用两次 vld1_lane_u8
    vmovl_u8 左移,对读取的uint8x8_t进行宽度扩展。
    _a0_0_a1_0_b0_0_b1_0分别为 f ( Q 11 ) f(Q_{11}) f(Q11) f ( Q 12 ) f(Q_{12}) f(Q12) f ( Q 21 ) f(Q_{21}) f(Q21) f ( Q 22 ) f(Q_{22}) f(Q22)

                    int16x4_t _srcstride = vdup_n_s16(srcstride);
    
                    int32x4_t _a0l = vaddw_s16(vmull_s16(_srcstride, _syl), _sxl);
                    int32x4_t _a0h = vaddw_s16(vmull_s16(_srcstride, _syh), _sxh);
                    int32x4_t _b0l = vaddw_s16(_a0l, _srcstride);
                    int32x4_t _b0h = vaddw_s16(_a0h, _srcstride);
    
                    uint8x8x2_t _a0a1 = uint8x8x2_t();
                    uint8x8x2_t _b0b1 = uint8x8x2_t();
                    {
                        _a0a1 = vld2_lane_u8(src0 + vgetq_lane_s32(_a0l, 0), _a0a1, 0);
                        _b0b1 = vld2_lane_u8(src0 + vgetq_lane_s32(_b0l, 0), _b0b1, 0);
    
                        _a0a1 = vld2_lane_u8(src0 + vgetq_lane_s32(_a0l, 1), _a0a1, 1);
                        _b0b1 = vld2_lane_u8(src0 + vgetq_lane_s32(_b0l, 1), _b0b1, 1);
    
                        _a0a1 = vld2_lane_u8(src0 + vgetq_lane_s32(_a0l, 2), _a0a1, 2);
                        _b0b1 = vld2_lane_u8(src0 + vgetq_lane_s32(_b0l, 2), _b0b1, 2);
    
                        _a0a1 = vld2_lane_u8(src0 + vgetq_lane_s32(_a0l, 3), _a0a1, 3);
                        _b0b1 = vld2_lane_u8(src0 + vgetq_lane_s32(_b0l, 3), _b0b1, 3);
    
                        _a0a1 = vld2_lane_u8(src0 + vgetq_lane_s32(_a0h, 0), _a0a1, 4);
                        _b0b1 = vld2_lane_u8(src0 + vgetq_lane_s32(_b0h, 0), _b0b1, 4);
    
                        _a0a1 = vld2_lane_u8(src0 + vgetq_lane_s32(_a0h, 1), _a0a1, 5);
                        _b0b1 = vld2_lane_u8(src0 + vgetq_lane_s32(_b0h, 1), _b0b1, 5);
    
                        _a0a1 = vld2_lane_u8(src0 + vgetq_lane_s32(_a0h, 2), _a0a1, 6);
                        _b0b1 = vld2_lane_u8(src0 + vgetq_lane_s32(_b0h, 2), _b0b1, 6);
    
                        _a0a1 = vld2_lane_u8(src0 + vgetq_lane_s32(_a0h, 3), _a0a1, 7);
                        _b0b1 = vld2_lane_u8(src0 + vgetq_lane_s32(_b0h, 3), _b0b1, 7);
                    }
    
                    uint16x8_t _a0_0 = vmovl_u8(_a0a1.val[0]);
                    uint16x8_t _a1_0 = vmovl_u8(_a0a1.val[1]);
                    uint16x8_t _b0_0 = vmovl_u8(_b0b1.val[0]);
                    uint16x8_t _b1_0 = vmovl_u8(_b0b1.val[1]);
    

    vget_low_u16 返回128位输入向量的下半部分。输出是一个64位向量,其元素数为输入向量的一半。
    vmlal_u16 无符号乘加。将第二和第三个向量中的对应元素相乘,然后将乘积与第一个输入向量中的对应元素相加。
    vqshrn_n_u32 将整数的四字向量中的每个元素右移一个立即数,并将结果放入一个双字向量中,如果发生饱和,则置位粘滞 QC 标志(FPSCR 位[27])。
    vqmovn_u16 将操作数向量的每个元素复制到目标向量的相应元素。结果元素是操作数元素宽度的一半,并且值会饱和到结果宽度。
    _a00_0l_a00_0h
    f ( Q 11 ) ( x 2 − x ) + f ( Q 12 ) ( x − x 1 ) f(Q_{11})(x_2 -x)+ f(Q_{12})(x-x_1) f(Q11)(x2x)+f(Q12)(xx1)
    _b00_0l_b00_0h
    f ( Q 21 ) ( y 2 − y ) + f ( Q 22 ) ( y − y 1 ) f(Q_{21})(y_2 -y)+ f(Q_{22})(y-y_1) f(Q21)(y2y)+f(Q22)(yy1)

                    uint16x4_t _a00_0l = vqshrn_n_u32(vmlal_u16(vmull_u16(vget_low_u16(_a0_0), vget_low_u16(_alpha0)), vget_low_u16(_a1_0), vget_low_u16(_alpha1)), 5);
                    uint16x4_t _a00_0h = vqshrn_n_u32(vmlal_u16(vmull_u16(vget_high_u16(_a0_0), vget_high_u16(_alpha0)), vget_high_u16(_a1_0), vget_high_u16(_alpha1)), 5);
                    uint16x4_t _b00_0l = vqshrn_n_u32(vmlal_u16(vmull_u16(vget_low_u16(_b0_0), vget_low_u16(_alpha0)), vget_low_u16(_b1_0), vget_low_u16(_alpha1)), 5);
                    uint16x4_t _b00_0h = vqshrn_n_u32(vmlal_u16(vmull_u16(vget_high_u16(_b0_0), vget_high_u16(_alpha0)), vget_high_u16(_b1_0), vget_high_u16(_alpha1)), 5);
    

    f ( x , y ) = ( a 0 α 0 + a 1 α 1 ) β 0 + ( b 0 α 0 + b 1 α 1 ) β 1 = f ( Q 11 ) ( x 2 − x ) ( y 2 − y ) + f ( Q 12 ) ( x − x 1 ) ( y 2 − y ) + f ( Q 21 ) ( x 2 − x ) ( y 2 − y ) + f ( Q 22 ) ( x − x 1 ) ( y − y 1 ) \begin{aligned} f(x, y) &= (a_0\alpha_0+ a_1\alpha_1)\beta_0 + (b_0\alpha_0+ b_1\alpha_1)\beta_1\\ &= f(Q_{11})(x_2 -x)(y_2 -y) + f(Q_{12})(x-x_1)(y_2 -y) \\ &\qquad+ f(Q_{21})(x_2 -x)(y_2 -y) + f(Q_{22})(x-x_1)(y-y_1) \end{aligned} f(x,y)=(a0α0+a1α1)β0+(b0α0+b1α1)β1=f(Q11)(x2x)(y2y)+f(Q12)(xx1)(y2y)+f(Q21)(x2x)(y2y)+f(Q22)(xx1)(yy1)

    vqmovn_u16 结果元素是操作数元素宽度的一半,并且值会饱和到结果宽度。
    vst1_u8 将向量存储到内存中。

                    uint16x4_t _dst_0l = vqshrn_n_u32(vmlal_u16(vmull_u16(_a00_0l, vget_low_u16(_beta0)), _b00_0l, vget_low_u16(_beta1)), 15);
                    uint16x4_t _dst_0h = vqshrn_n_u32(vmlal_u16(vmull_u16(_a00_0h, vget_high_u16(_beta0)), _b00_0h, vget_high_u16(_beta1)), 15);
    
                    uint8x8_t _dst = vqmovn_u16(vcombine_u16(_dst_0l, _dst_0h));
    
                    vst1_u8(dst0, _dst);
    
                    dst0 += 8;
    

    a0a1b0b1为位置。4个像素插值得到结果。

    #else
                    for (int xi = 0; xi < 8; xi++)
                    {
                        int X = X0 + adelta[x + xi];
                        int Y = Y0 + bdelta[x + xi];
    
                        short sx = SATURATE_CAST_SHORT((X >> 10));
                        short sy = SATURATE_CAST_SHORT((Y >> 10));
    
                        short fx = X & ((1 << 10) - 1);
                        short fy = Y & ((1 << 10) - 1);
    
                        short alpha0 = (1 << 10) - fx;
                        short alpha1 = fx;
    
                        short beta0 = (1 << 10) - fy;
                        short beta1 = fy;
    
                        const unsigned char* a0 = src0 + srcstride * sy + sx;
                        const unsigned char* a1 = src0 + srcstride * sy + sx + 1;
                        const unsigned char* b0 = src0 + srcstride * (sy + 1) + sx;
                        const unsigned char* b1 = src0 + srcstride * (sy + 1) + sx + 1;
    
                        dst0[0] = (unsigned char)(((((unsigned short)((a0[0] * alpha0 + a1[0] * alpha1) >> 5) * beta0)) + (((unsigned short)((b0[0] * alpha0 + b1[0] * alpha1) >> 5) * beta1))) >> 15);
    
                        dst0 += 1;
                    }
    #endif // __ARM_NEON
                }
    

    如果全部落在边界外,赋指定了边界值,-233表示跳过不处理。

                else if (sxy_inout == 2)
                {
                    // all outside
                    if (type != -233)
                    {
    #if __ARM_NEON
                        uint8x8_t _border_color = vdup_n_u8(border_color[0]);
                        vst1_u8(dst0, _border_color);
    #else
                        for (int xi = 0; xi < 8; xi++)
                        {
                            dst0[xi] = border_color[0];
                        }
    #endif // __ARM_NEON
                    }
                    else
                    {
                        // skip
                    }
    
                    dst0 += 8;
                }
    

    如果是在边界上,逐元素处理:

    • 如果不是透明模式且源像素在边界外则直接取填充值;
    • 如果是透明模式且源像素在右下边界上或右下边界外则跳过;
    • 否则根据位置独立确定a0a1b0b1的值。
                else // if (sxy_inout == 0)
                {
                    for (int xi = 0; xi < 8; xi++)
                    {
                        int X = X0 + adelta[x + xi];
                        int Y = Y0 + bdelta[x + xi];
    
                        short sx = SATURATE_CAST_SHORT((X >> 10));
                        short sy = SATURATE_CAST_SHORT((Y >> 10));
    
                        if (type != -233 && (sx < -1 || sx >= srcw || sy < -1 || sy >= srch))
                        {
                            dst0[0] = border_color[0];
                        }
                        else if (type == -233 && ((unsigned short)sx >= srcw - 1 || (unsigned short)sy >= srch - 1))
                        {
                            // skip
                        }
                        else
                        {
                            short fx = X & ((1 << 10) - 1);
                            short fy = Y & ((1 << 10) - 1);
    
                            short alpha0 = (1 << 10) - fx;
                            short alpha1 = fx;
    
                            short beta0 = (1 << 10) - fy;
                            short beta1 = fy;
    
                            short sx1 = sx + 1;
                            short sy1 = sy + 1;
    
                            const unsigned char* a0 = src0 + srcstride * sy + sx;
                            const unsigned char* a1 = src0 + srcstride * sy + sx + 1;
                            const unsigned char* b0 = src0 + srcstride * (sy + 1) + sx;
                            const unsigned char* b1 = src0 + srcstride * (sy + 1) + sx + 1;
    
                            if ((unsigned short)sx >= srcw || (unsigned short)sy >= srch)
                            {
                                a0 = type != -233 ? border_color : dst0;
                            }
                            if ((unsigned short)sx1 >= srcw || (unsigned short)sy >= srch)
                            {
                                a1 = type != -233 ? border_color : dst0;
                            }
                            if ((unsigned short)sx >= srcw || (unsigned short)sy1 >= srch)
                            {
                                b0 = type != -233 ? border_color : dst0;
                            }
                            if ((unsigned short)sx1 >= srcw || (unsigned short)sy1 >= srch)
                            {
                                b1 = type != -233 ? border_color : dst0;
                            }
    
                            dst0[0] = (unsigned char)(((((unsigned short)((a0[0] * alpha0 + a1[0] * alpha1) >> 5) * beta0)) + (((unsigned short)((b0[0] * alpha0 + b1[0] * alpha1) >> 5) * beta1))) >> 15);
                        }
    
                        dst0 += 1;
                    }
                }
            }
    

    处理行尾剩余的元素。

            for (; x < w; x++)
            {
                int X = X0 + adelta[x];
                int Y = Y0 + bdelta[x];
    
                short sx = SATURATE_CAST_SHORT((X >> 10));
                short sy = SATURATE_CAST_SHORT((Y >> 10));
    
                if (type != -233 && (sx < -1 || sx >= srcw || sy < -1 || sy >= srch))
                {
                    dst0[0] = border_color[0];
                }
                else if (type == -233 && ((unsigned short)sx >= srcw - 1 || (unsigned short)sy >= srch - 1))
                {
                    // skip
                }
                else
                {
                    short fx = X & ((1 << 10) - 1);
                    short fy = Y & ((1 << 10) - 1);
    
                    short alpha0 = (1 << 10) - fx;
                    short alpha1 = fx;
    
                    short beta0 = (1 << 10) - fy;
                    short beta1 = fy;
    
                    short sx1 = sx + 1;
                    short sy1 = sy + 1;
    
                    const unsigned char* a0 = src0 + srcstride * sy + sx;
                    const unsigned char* a1 = src0 + srcstride * sy + sx + 1;
                    const unsigned char* b0 = src0 + srcstride * (sy + 1) + sx;
                    const unsigned char* b1 = src0 + srcstride * (sy + 1) + sx + 1;
    
                    if ((unsigned short)sx >= srcw || (unsigned short)sy >= srch)
                    {
                        a0 = type != -233 ? border_color : dst0;
                    }
                    if ((unsigned short)sx1 >= srcw || (unsigned short)sy >= srch)
                    {
                        a1 = type != -233 ? border_color : dst0;
                    }
                    if ((unsigned short)sx >= srcw || (unsigned short)sy1 >= srch)
                    {
                        b0 = type != -233 ? border_color : dst0;
                    }
                    if ((unsigned short)sx1 >= srcw || (unsigned short)sy1 >= srch)
                    {
                        b1 = type != -233 ? border_color : dst0;
                    }
    
                    dst0[0] = (unsigned char)(((((unsigned short)((a0[0] * alpha0 + a1[0] * alpha1) >> 5) * beta0)) + (((unsigned short)((b0[0] * alpha0 + b1[0] * alpha1) >> 5) * beta1))) >> 15);
                }
    
                dst0 += 1;
            }
    
            dst0 += wgap;
        }
    
    #undef SATURATE_CAST_SHORT
    #undef SATURATE_CAST_INT
    

    参考资料:

    展开全文
  • 函数 warpAffine 使用指定的矩阵变换源图像: 当设置了 WARP_INVERSE_MAP 标志时。 否则,先用 invertAffineTransform 反转变换,然后代入上面的公式,而不是 M。该函数不能就地操作。.....................

    一、概述

            对图像应用仿射变换。函数 warpAffine 使用指定的矩阵变换源图像:

            当设置了 WARP_INVERSE_MAP 标志时。 否则,先用 invertAffineTransform 反转变换,然后代入上面的公式,而不是 M。该函数不能就地操作。

    二、warpAffine函数

    1、函数原型

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

    2、参数详解

    src输入图像。
    dst输出大小为 dsize 且类型与 src 相同的图像。
    M2×3 变换矩阵。
    dsize输出图像的大小。
    flags插值方法(见 InterpolationFlags)和可选标志 WARP_INVERSE_MAP 的组合,这意味着 M 是逆变换(dst→src)。
    borderMode像素外推法(参见 BorderTypes); 当borderMode=BORDER_TRANSPARENT时,表示目标图像中与源图像中的“异常值”对应的像素未被函数修改。
    borderValue在恒定边界的情况下使用的值; 默认情况下,它是 0。

    三、OpenCV源码 

    1、源码路径

    opencv\modules\imgproc\src\imgwarp.cpp

    2、源码代码

    void cv::warpAffine( InputArray _src, OutputArray _dst,
                         InputArray _M0, Size dsize,
                         int flags, int borderType, const Scalar& borderValue )
    {
        CV_INSTRUMENT_REGION();
    
        int interpolation = flags & INTER_MAX;
        CV_Assert( _src.channels() <= 4 || (interpolation != INTER_LANCZOS4 &&
                                            interpolation != INTER_CUBIC) );
    
        CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat() &&
                   _src.cols() <= SHRT_MAX && _src.rows() <= SHRT_MAX,
                   ocl_warpTransform_cols4(_src, _dst, _M0, dsize, flags, borderType,
                                           borderValue, OCL_OP_AFFINE))
    
        CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(),
                   ocl_warpTransform(_src, _dst, _M0, dsize, flags, borderType,
                                     borderValue, OCL_OP_AFFINE))
    
        Mat src = _src.getMat(), M0 = _M0.getMat();
        _dst.create( dsize.empty() ? src.size() : dsize, src.type() );
        Mat dst = _dst.getMat();
        CV_Assert( src.cols > 0 && src.rows > 0 );
        if( dst.data == src.data )
            src = src.clone();
    
        double M[6] = {0};
        Mat matM(2, 3, CV_64F, M);
        if( interpolation == INTER_AREA )
            interpolation = INTER_LINEAR;
    
        CV_Assert( (M0.type() == CV_32F || M0.type() == CV_64F) && M0.rows == 2 && M0.cols == 3 );
        M0.convertTo(matM, matM.type());
    
        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;
        }
    
    #if defined (HAVE_IPP) && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_WARPAFFINE
        CV_IPP_CHECK()
        {
            int type = src.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);
            if( ( depth == CV_8U || depth == CV_16U || depth == CV_32F ) &&
               ( cn == 1 || cn == 3 || cn == 4 ) &&
               ( interpolation == INTER_NEAREST || interpolation == INTER_LINEAR || interpolation == INTER_CUBIC) &&
               ( borderType == cv::BORDER_TRANSPARENT || borderType == cv::BORDER_CONSTANT) )
            {
                ippiWarpAffineBackFunc ippFunc = 0;
                if ((flags & WARP_INVERSE_MAP) != 0)
                {
                    ippFunc =
                    type == CV_8UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_8u_C1R :
                    type == CV_8UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_8u_C3R :
                    type == CV_8UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_8u_C4R :
                    type == CV_16UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_16u_C1R :
                    type == CV_16UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_16u_C3R :
                    type == CV_16UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_16u_C4R :
                    type == CV_32FC1 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_32f_C1R :
                    type == CV_32FC3 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_32f_C3R :
                    type == CV_32FC4 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_32f_C4R :
                    0;
                }
                else
                {
                    ippFunc =
                    type == CV_8UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffine_8u_C1R :
                    type == CV_8UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffine_8u_C3R :
                    type == CV_8UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffine_8u_C4R :
                    type == CV_16UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffine_16u_C1R :
                    type == CV_16UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffine_16u_C3R :
                    type == CV_16UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffine_16u_C4R :
                    type == CV_32FC1 ? (ippiWarpAffineBackFunc)ippiWarpAffine_32f_C1R :
                    type == CV_32FC3 ? (ippiWarpAffineBackFunc)ippiWarpAffine_32f_C3R :
                    type == CV_32FC4 ? (ippiWarpAffineBackFunc)ippiWarpAffine_32f_C4R :
                    0;
                }
                int mode =
                interpolation == INTER_LINEAR ? IPPI_INTER_LINEAR :
                interpolation == INTER_NEAREST ? IPPI_INTER_NN :
                interpolation == INTER_CUBIC ? IPPI_INTER_CUBIC :
                0;
                CV_Assert(mode && ippFunc);
    
                double coeffs[2][3];
                for( int i = 0; i < 2; i++ )
                    for( int j = 0; j < 3; j++ )
                        coeffs[i][j] = matM.at<double>(i, j);
    
                bool ok;
                Range range(0, dst.rows);
                IPPWarpAffineInvoker invoker(src, dst, coeffs, mode, borderType, borderValue, ippFunc, &ok);
                parallel_for_(range, invoker, dst.total()/(double)(1<<16));
                if( ok )
                {
                    CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT);
                    return;
                }
                setIppErrorStatus();
            }
        }
    #endif
    
        hal::warpAffine(src.type(), src.data, src.step, src.cols, src.rows, dst.data, dst.step, dst.cols, dst.rows,
                        M, interpolation, borderType, borderValue.val);
    }
    namespace hal {
    
    void warpAffine(int src_type,
                    const uchar * src_data, size_t src_step, int src_width, int src_height,
                    uchar * dst_data, size_t dst_step, int dst_width, int dst_height,
                    const double M[6], int interpolation, int borderType, const double borderValue[4])
    {
        CALL_HAL(warpAffine, cv_hal_warpAffine, src_type, src_data, src_step, src_width, src_height, dst_data, dst_step, dst_width, dst_height, M, interpolation, borderType, borderValue);
    
        Mat src(Size(src_width, src_height), src_type, const_cast<uchar*>(src_data), src_step);
        Mat dst(Size(dst_width, dst_height), src_type, dst_data, dst_step);
    
        int x;
        AutoBuffer<int> _abdelta(dst.cols*2);
        int* adelta = &_abdelta[0], *bdelta = adelta + dst.cols;
        const int AB_BITS = MAX(10, (int)INTER_BITS);
        const int AB_SCALE = 1 << AB_BITS;
    
        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,
                                  Scalar(borderValue[0], borderValue[1], borderValue[2], borderValue[3]),
                                  adelta, bdelta, M);
        parallel_for_(range, invoker, dst.total()/(double)(1<<16));
    }
    
    }

    四、效果图像示例

    原图
    通过手动指定三点的变化,进行仿射变换

             其它仿射变换参考Opencv学习笔记 - 仿射变换/透视变换/对数极坐标变换_坐望云起的博客-CSDN博客_对数极坐标变换Opencv学习笔记 - 仿射变换/透视变换/对数极坐标变换https://skydance.blog.csdn.net/article/details/109705939

    展开全文
  • 【CV学习笔记】图像预处理warpaffine

    千次阅读 2022-04-14 14:36:07
    1、前言 在学习图像预处理的时候发现,之前用的图像预处理方法一般为 resize和letter box,这两种方法比较低效,后来在手写AI中接触到了warpaffine,只需要一步就能够对图像进行预处理,同时还能很方便的进行cuda加速...
  • OpenCV提供两种图像...然而,这里就遇到一个warpAffine函数的一个坑。我们先上代码,如下: import cv2 import plotly.express as px def img_show(img, bgr_mode=True): if bgr_mode: px.imshow(img[...,::-1])
  • } 二、Warpaffine仿射变换 Warpaffine仿射变换主要为了解决图像的缩放和平移来处理目标检测中常见的预处理行为: Warpaffine仿射变换的特点如下: warpaffine是对图像做平移缩放旋转进行综合统一描述的方法(可通过...
  • 仿射变换warpAffine

    2022-06-15 22:27:57
    仿射变换是指在向量空间中进行一次线性变换... 事实上,仿射变换代表的是两幅图之间的关系,我们通常使用2x3矩阵来表示仿射变换如下: 参考资料: opencv学习(三十五)之仿射变换warpAffine cv2.warpAffine 参数详解
  • opencv提供了两个变换函数,cv2.warpAffine和cv2.warpPerspective,使用这两个函数你可以实现所有类型的变换,cv2.warpAffine接收的参数2x3的变换矩阵,而cv2.warpPerspective接收的3x3的变换矩阵。 1.扩展缩放 ...
  • 返回Opencv-Python教程 在前一篇几何空间变换~缩放、转置、翻转文章中我们介绍了转置、缩放、翻转,其中水平或垂直方向的翻转实际上对图像进行了...dst=cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[..
  • opencv图像仿射变换,cv2.warpAffine

    千次阅读 2022-04-04 20:38:34
    目录 仿射变换原理介绍 cv2.warpAffine函数介绍 代码实例 仿射变换原理介绍 仿射变换,又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。 在有限维的情况,每个...
  • # 实现一个仿射变换,采用双线性插值方式实现一个warpaffine def pyWarpAffine(image, M, dst_size, constant=(0,0,0)): M = cv.invertAffineTransform(M) # 求仿射变换的逆矩阵,因为我们是把目的图片作为输入图片...
  • 1) cv2.imshow('src',img) imgInfo = img.shape height = imgInfo[0] width = imgInfo[1] #### matShift = np.float32([[1,0,100],[0,1,200]])# 2*3 dst = cv2.warpAffine(img,matShift,(height,width))#1 data 2 ...
  • cv2.warpAffine(src, M, dsize, flags, borderMode, borderValue)→ dst src输入图像 M变换矩阵 dsize为输出图像尺寸 flags插值方法 borderMode边界像素模式 borderValue像素边界颜色 代码例子: import ...
  • 对照opencv中warpAffine函数使用说明和测试实例发现,错误发生在传入的变换矩阵M中,M的格式上面设置的为uchar,本人以为与图像数据对应,导致了报错。最后将矩阵M的数据类型更改为float,内存报错解除。 以上仅供...
  • 3、warpAffine void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())根据...
  • 以下为warpAffine()接口的简单实现版本,默认borderMode=None, borderValue=(114, 114, 114) # Author: Jintao Huang # Email: hjt_study@qq.com # Date: 2021-6-16 import numpy as np import cv2 as cv from ...
  • warpAffine是一个不能忽略的东西,之前大意认为是很简单的东西,然而实际用起来还是需要掌握基本的数学思想的。 重点是一个2*3的平移矩阵M,分别指定x方向和y方向上的平移量tx和ty,平移矩阵的形式如下: python...
  • cv2.warpAffine和cv2.warpPerspective

    千次阅读 2022-02-28 09:53:52
    Opencv仿射变换(Affine Transformation)函数:cv2.warpAffine Affine Transformation:可实现旋转,平移,缩放,变换后的平行线依旧平行。 Opencv透视变换(Perspective Transformation)函数:cv2....
  • cv2.warpAffine 参数详解

    千次阅读 2021-02-08 13:31:32
    warpAffine :意思是仿射变化。官方给出的参数为: cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) → dst 其中: src – 输入图像。 M – 变换矩阵。 dsize – 输出图像的大小。...
  • 本篇笔记主要记录Opencv里的图像... cv2.warpAffine() #图像仿射 cv2.getRotationMatrix2D() #取得旋转角度的Matrix cv2.GetAffineTransform(src, dst, mapMatrix) #取得图像仿射的matrix cv2.getPerspectiveTr
  • cv2.warpAffine

    2020-08-17 21:26:30
    仿射变换函数cv2.warpAffine的用法: cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) → dst 其中: src - 输入图像。 M - 仿射变换矩阵。 dsize - 输出图像的大小。 flags - 插值...
  • 图像的仿射变换:cv2.warpAffine()

    千次阅读 2021-10-01 13:56:28
    这个函数是:cv2.getAffineTransform(pos1,pos2) 最后这个矩阵会被传给函数 cv2.warpAffine()来实现仿射变换。 import cv2 import numpy as np img = cv2.imread('me.jpg') height, width = img.shape[:2] # 在原...
  • 阿洲的程式教學關於Qt、OpenCV、影像處理演算法仿射變換(warpAffine)所謂的仿射轉換,包括旋轉、移動、放大縮小,除了remap()之外,OpenC
  • 在上个学习笔记中学习warpaffine,并且在opencv下面实现了图像的预处理,而warpaffine可以很好的利用cuda加速来实现,于是基于手写AI的项目,又学习了warpaffien在cuda上的加速。 原项目地址为:...
  • 文章目录前言一、重映射remap()二、仿射变换warpAffine()三、直方图均衡化总结 前言 笔者本科时候有幸接触了OpenCV3.2.0版本的学习,后因考研压力不得不暂时停下学习的脚步,现在考研任务结束了,未来的导师也是...
  • 譬如说本文的warpAffine函数,首先是不是应该看看帮助文档,如果是在jupyter notebook中,Shift+Tab键就可以查看其docstring,内容如下: warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])...
  • 10 if ox < iw and ox >= 0 and oy < ih and oy >= 0: img1[i, j] = data[oy, ox] else : img1[i, j] = borderValue[0] python实现warpaffine最近邻有些坐标取整有误 input shape: h=1920 w=1080 wp=1088 hp=1920 ...
  • 利用OpenCV的warpAffine函数对一个图片进行旋转的话,如果图像大小和原图像一样的话。就会将超出的范围截断。所以要对旋转的矩阵做平移,计算旋转后的新图像大小。 #include <iostream> using namespace std; ...
  • h/2) ra=np.random.randint(0,90) M = cv2.getRotationMatrix2D(center, ra, 1.0)#第三个参数缩放比例 rotated = cv2.warpAffine(img, M, (w, h),borderMode=cv2.BORDER_REFLECT)#边界填充模式 ''' 填充模式参数: ...

空空如也

空空如也

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

warpaffine