精华内容
下载资源
问答
  • OpenCV 中的 warpAffine

    2021-04-18 10:58:46
    warpAffine 是图像处理中比较常见的一种变换,可以将图像校正或对齐。 对于线性插值方式,OpenCV 首先将坐标映射保存成两张图,然后调用 remap 函数。第二步是比较耗时的部分,并且 warpPerspective 亦采用此处理。 ...

    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;
    

    参考资料:

    展开全文
  • 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
    

    参考资料:

    展开全文
  • OpenCV warpAffine的天坑

    2021-01-17 11:23:31
    问:在OpenCV中,通过resize将(w1, h1)尺寸的图片转化为(w2, h2)尺寸的图片,和通过warpAffine配合矩阵变换进行仿射变换,插值模型都使用双线性(不考虑OpenCV LINEAR实现中特殊处理的几种情况),那么得到的结果是否...

    问:

    在OpenCV中,通过resize将(w1, h1)尺寸的图片转化为(w2, h2)尺寸的图片,和通过warpAffine配合

    equation?tex=%5Cleft%28%5Cbegin%7Barray%7D%7Bccc%7D%5Cfrac%7Bw_2%7D%7Bw_1%7D+%26+0+%26+0%5C%5C+0+%26+%5Cfrac%7Bh_2%7D%7Bh_1%7D+%26+0%5Cend%7Barray%7D%5Cright%29

    矩阵变换进行仿射变换,插值模型都使用双线性(不考虑OpenCV LINEAR实现中特殊处理的几种情况),那么得到的结果是否相同呢?

    答:

    不相同,可以试试

    [[255, 200, 0, 50],

    [200, 255, 50, 0],

    [ 0, 50, 255, 200],

    [ 50, 0, 200, 255]]

    这个4 * 4的小图片,将它缩小一半:

    >>> d = numpy.array([[255, 200, 0, 50],

    ... [200, 255, 50, 0],

    ... [ 0, 50, 255, 200],

    ... [ 50, 0, 200, 255]], numpy.uint8)

    >>> cv2.resize(d, (2,2))

    array([[228, 25],

    [ 25, 228]], dtype=uint8)

    >>> cv2.warpAffine(d, numpy.matrix([[0.5,0,0],[0,0.5,0]]), (2,2))

    array([[255, 0],

    [ 0, 255]], dtype=uint8)

    显然resize的结果正确(进行了插值),而warp的结果不正确(只保留了奇数行列)。这是为什么呢?大家可以先思考下,过段时间再公布答案。

    展开全文
  • 返回Opencv-Python教程 在前一篇几何空间变换~缩放、转置、翻转文章中我们介绍了转置、缩放、翻转,其中水平或垂直方向的翻转实际上对图像进行了...dst=cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[..

    原文链接:http://www.juzicode.com/archives/6500

    返回Opencv-Python教程

    前一篇文章 几何空间变换~缩放、转置、翻转 介绍了图像的转置、缩放、翻转,其中水平或垂直方向的翻转实际上对图像进行了镜像操作,并不能达到旋转的效果,本文介绍的仿射变换则可以对图像进行任一角度的旋转,另外仿射变换还可以实现图像的矫正、平移。

    1、仿射变换warpAffine()

    仿射变换的接口形式如下:

    dst=cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])

    参数含义:

    • src: 输入图像。
    • M: 2×3 2行3列变换矩阵。
    • dsize: 输出图像的大小。
    • dst: 可选,输出图像,由dsize指定大小,type和src一样。
    • flags: 可选,插值方法
    • borderMode: 可选,边界像素模式
    • borderValue: 可选,边界填充值; 默认为0。

    1.1、平移

    通过手动指定算子M=[[1,0,X],[0,1,Y]]可以实现图像的移位,其中X表示向图像x方向(向右)移动的像素值,Y表示图像向y方向(向下)移动的像素值。

    import matplotlib.pyplot as plt
    import numpy as np
    import cv2
    
    print('VX公众号: 桔子code / juzicode.com')
    print('cv2.__version__:',cv2.__version__)
    plt.rc('font',family='Youyuan',size='9')
    plt.rc('axes',unicode_minus='False')
    
    img = cv2.imread('..\\messi5.jpg')
    rows,cols,_ = img.shape
    
    M = np.float32([[1,0,100],[0,1,50]])   #右移100-下移50
    img_ret1 = cv2.warpAffine(img,M,(cols,rows))
    M = np.float32([[1,0,-100],[0,1,-50]]) #左移100-上移50
    img_ret2 = cv2.warpAffine(img,M,(cols,rows))
    M = np.float32([[1,0,-100],[0,1,50]])  #左移100-下移50
    img_ret3 = cv2.warpAffine(img,M,(cols,rows))
    
    fig,ax = plt.subplots(2,2)
    ax[0,0].set_title('原图   by VX:桔子code')
    ax[0,0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
    ax[0,1].set_title('右移100-下移50')
    ax[0,1].imshow(cv2.cvtColor(img_ret1,cv2.COLOR_BGR2RGB))
    ax[1,0].set_title('左移100-上移50')
    ax[1,0].imshow(cv2.cvtColor(img_ret2,cv2.COLOR_BGR2RGB))
    ax[1,1].set_title('左移100-下移50') 
    ax[1,1].imshow(cv2.cvtColor(img_ret3,cv2.COLOR_BGR2RGB))
    #ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
    plt.show() 

    运行结果:

    1.2、旋转

    不像移位那样可以直观的手动构造算子M,旋转需要先用getRotationMatrix2D()方法构造出warpAffine()的算子M,getRotationMatrix2D()接口形式:

    retval=cv2.getRotationMatrix2D(center, angle, scale)

    参数含义:

    • center:旋转中心位置
    • angle:旋转角度
    • scale:缩放比例,不缩放时为1
    import matplotlib.pyplot as plt
    import numpy as np
    import cv2
    
    print('VX公众号: 桔子code / juzicode.com')
    print('cv2.__version__:',cv2.__version__)
    plt.rc('font',family='Youyuan',size='9')
    plt.rc('axes',unicode_minus='False')
    
    img = cv2.imread('..\\messi5.jpg')
    rows,cols,_ = img.shape
    
    #以图像中心旋转
    M = cv2.getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),90,1)#逆时针90度
    img_ret1 = cv2.warpAffine(img,M,(cols,rows))
    M = cv2.getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),-90,1)#顺时针90度
    img_ret2 = cv2.warpAffine(img,M,(cols,rows))
    M = cv2.getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),-180,1)#顺时针180度
    img_ret3 = cv2.warpAffine(img,M,(cols,rows))
    
    #显示图像
    fig,ax = plt.subplots(2,2)
    ax[0,0].set_title('VX:桔子code   原图')
    ax[0,0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
    ax[0,1].set_title('逆时针90度')
    ax[0,1].imshow(cv2.cvtColor(img_ret1,cv2.COLOR_BGR2RGB))
    ax[1,0].set_title('顺时针90度')
    ax[1,0].imshow(cv2.cvtColor(img_ret2,cv2.COLOR_BGR2RGB))
    ax[1,1].set_title('顺时针180度') 
    ax[1,1].imshow(cv2.cvtColor(img_ret3,cv2.COLOR_BGR2RGB))
    ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
    plt.show() 

    运行结果:

    从上图的运行结果看,在做逆时针或顺时针90度旋转时,出现了黑色边框,而且图像的长边部分超出了边界,这是因为以原图的中心点为旋转点、变化后的图像宽高仍然和原图一致导致的,下图是一个5×9个像素图像旋转的效果示意。左边是原图,右边是以1/2宽,1/2高的图像中心点E3为中心顺时针旋转90度之后的图像,可以看到旋转后的A5~A1,B5~B1,H5~H1,I5~I1等像素已经超出了原图黄色部分的边界:

    如果要实现顺时针旋转90度并且图像不被裁剪、没有黑色填充的效果,则应该重新选择旋转中心点以C3为中心点,同时新图像需要对调宽、高:

    这时中心点C3点的x和y轴的坐标都在(rows-1)/2处,所以在用getRotationMatrix2D()构造M算子时,center参数为:

    center=((rows-1)/2.0,(rows-1)/2.0)

    图像的dsize参数为:

    dsize = (rows,cols)

    完整的代码为(这里只适用宽大于高的图像,且做顺时针旋转90度,从后面的运行结果看,这种方法做逆时针旋转时也失效了):

    import matplotlib.pyplot as plt
    import numpy as np
    import cv2
    #这里只适用宽大于高的图像,且做顺时针旋转90度
    print('VX公众号: 桔子code / juzicode.com')
    print('cv2.__version__:',cv2.__version__)
    plt.rc('font',family='Youyuan',size='9')
    plt.rc('axes',unicode_minus='False')
    
    img = cv2.imread('..\\messi5.jpg')
    rows,cols,_ = img.shape#rows对应高度,cols对应宽度
    
    center=((rows-1)/2.0,(rows-1)/2.0)
    dsize = (rows,cols)
    
    #以图像中心旋转
    M = cv2.getRotationMatrix2D(center,-90,1)#顺时针90度
    img_ret2 = cv2.warpAffine(img,M,dsize)
    M = cv2.getRotationMatrix2D(center,90,1)#逆时针90度
    img_ret3 = cv2.warpAffine(img,M,dsize)
    
    #显示图像
    fig,ax = plt.subplots(2,2)
    ax[0,0].set_title('VX:桔子code   原图')
    ax[0,0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
    #ax[0,1].set_title('')
    #ax[0,1].imshow()
    ax[1,0].set_title('顺时针90度')
    ax[1,0].imshow(cv2.cvtColor(img_ret2,cv2.COLOR_BGR2RGB))
    ax[1,1].set_title('逆时针90度') 
    ax[1,1].imshow(cv2.cvtColor(img_ret3,cv2.COLOR_BGR2RGB))
    ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
    plt.show() 

    从运行结果看,经过重新选择中心点,对调宽高的方法,顺时针旋转已经保留了完整的图像,也没有出现黑框,但是逆时针旋转并没有达到效果,这里不再做更多的测试,因为OpenCV自带的rotate()方法就能实现旋转的目的,在稍后会介绍到。

    1.3、矫正

    有时候图像因为拍摄角度或成像设备的原因发生畸变,可以用仿射变换进行矫正,这时使用getAffineTransform()构建M算子,入参为变换前和变化后的2组坐标点,每组坐标点包含3个位置参数,比如下图这种图像我们选取红色所示标注的3个点,取得其坐标为[10,71],[37,320],[550,240],变化后的3个位置参数分别位于图像的左上、左下、右下3个位置点,其坐标分别为[[10,10],[0,300],[550,300]]:

    pts1 = np.float32([[10,71],[37,320],[550,240]])
    pts2 = np.float32([[10,10],[0,300],[550,300]])
    M = cv2.getAffineTransform(pts1,pts2)

    import matplotlib.pyplot as plt
    import numpy as np
    import cv2
    
    print('VX公众号: 桔子code / juzicode.com')
    print('cv2.__version__:',cv2.__version__)
    plt.rc('font',family='Youyuan',size='9')
    plt.rc('axes',unicode_minus='False')
    
    img = cv2.imread('..\\samples\\data\\imageTextR.png')
    rows,cols,_ = img.shape
    
    pts1 = np.float32([[10,71],[37,320],[550,240]])
    pts2 = np.float32([[10,10],[0,300],[550,300]])
    M = cv2.getAffineTransform(pts1,pts2)
    img_ret1 = cv2.warpAffine(img,M,(cols,rows)) 
    
    fig,ax = plt.subplots(1,2)
    ax[0].set_title('VX:桔子code   原图')
    ax[0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
    ax[1].set_title('变换后')
    ax[1].imshow(cv2.cvtColor(img_ret1,cv2.COLOR_BGR2RGB)) 
    #ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
    plt.show() 

    运行结果:

    这个例子实际上如果采用旋转的方法效果更好,这里只是为了达到演示矫正的效果选择了这张倾斜图片。

    2、旋转rotate()

    旋转的接口形式:

    cv2.rotate(src, rotateCode[, dst]) -> dst

    其中src为源图像,rotateCode可选3种参数:

    rotateCode含义
    cv2.ROTATE_90_CLOCKWISE顺时针旋转90度
    cv2.ROTATE_180旋转180度,不区分顺时针或逆时针,效果一样
    cv2.ROTATE_90_COUNTERCLOCKWISE逆时针旋转90度,等同于顺时针270度

    下面的例子对messi图像进行3种方式的旋转:

    import matplotlib.pyplot as plt
    import numpy as np
    import cv2
    
    print('VX公众号: 桔子code / juzicode.com')
    print('cv2.__version__:',cv2.__version__)
    plt.rc('font',family='Youyuan',size='9')
    plt.rc('axes',unicode_minus='False')
    
    img = cv2.imread('..\\messi5.jpg')
    img_ret1 = cv2.rotate(img,cv2.ROTATE_90_CLOCKWISE)
    img_ret2 = cv2.rotate(img,cv2.ROTATE_180)
    img_ret3 = cv2.rotate(img,cv2.ROTATE_90_COUNTERCLOCKWISE)
    
    #显示图像
    fig,ax = plt.subplots(2,2)
    ax[0,0].set_title('VX:桔子code   原图')
    ax[0,0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
    ax[0,1].set_title('顺时针90度')
    ax[0,1].imshow(cv2.cvtColor(img_ret1,cv2.COLOR_BGR2RGB))
    ax[1,0].set_title('180度')
    ax[1,0].imshow(cv2.cvtColor(img_ret2,cv2.COLOR_BGR2RGB))
    ax[1,1].set_title('逆时针90度') 
    ax[1,1].imshow(cv2.cvtColor(img_ret3,cv2.COLOR_BGR2RGB))
    ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
    plt.show() 

    运行结果:

    从roate()源码可以看到,3种方式的旋转实际是对flip()和transpose()的封装实现的,比如顺时针旋转90度,通过先用transpose做转置、再用flip翻转实现:

    小结:仿射变换可以实现图像的平移、旋转和校正;仿射变换方式的旋转会导致图像丢失、出现黑边,rotate()方法可以解决该问题;rotate()实际是通过封装transpose和flip来实现3种角度的旋转,rotate()并不支持其他角度的旋转

    扩展阅读:

    1. OpenCV-Python教程
    2. OpenCV-Python教程:几何空间变换~缩放、转置、翻转
    展开全文
  • # 实现一个仿射变换,采用双线性插值方式实现一个warpaffine def pyWarpAffine(image, M, dst_size, constant=(0,0,0)): M = cv.invertAffineTransform(M) # 求仿射变换的逆矩阵,因为我们是把目的图片作为输入图片...
  • 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(g_image_original, g_image_result, img, g_image_original.size()); } int main() { const std::string window_name = "image"; const std::string path = "E:/VSCode/git/my_program/...
  • cv2.warpAffine(src, M, dsize, flags, borderMode, borderValue)→ dst src输入图像 M变换矩阵 dsize为输出图像尺寸 flags插值方法 borderMode边界像素模式 borderValue像素边界颜色 代码例子: import ...
  • cv2.warpAffine

    2020-08-17 21:26:30
    仿射变换函数cv2.warpAffine的用法: cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) → dst 其中: src - 输入图像。 M - 仿射变换矩阵。 dsize - 输出图像的大小。 flags - 插值...
  • warpAffine是一个不能忽略的东西,之前大意认为是很简单的东西,然而实际用起来还是需要掌握基本的数学思想的。 重点是一个2*3的平移矩阵M,分别指定x方向和y方向上的平移量tx和ty,平移矩阵的形式如下: python...
  • 图像的仿射变换: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] # 在原...
  • OpenCV代码提取:warpAffine函数的实现

    万次阅读 2016-07-16 11:20:43
    OpenCV代码提取:warpAffine函数的实现
  • 利用warpAffine函数分别对这六张图片进行仿射变换,再最后将六张图片合成一张完整的图片, 想问下我没有合适的openCV函数能做到这一点,现在自己对openCV这一块不熟悉,现在想 知道能否在不分割图像的情况下做到这...
  • 譬如说本文的warpAffine函数,首先是不是应该看看帮助文档,如果是在jupyter notebook中,Shift+Tab键就可以查看其docstring,内容如下: warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])...
  • cv::warpAffine(image, image, rot, bbox.size(),cv::INTER_LINEAR,cv::BORDER_CONSTANT, cv::Scalar(255, 255, 255)); how I can do it ? 解决方案 Since you have the rotation matrix, you can rotate the ROI ...
  • 利用OpenCV的warpAffine函数对一个图片进行旋转的话,如果图像大小和原图像一样的话。就会将超出的范围截断。所以要对旋转的矩阵做平移,计算旋转后的新图像大小。 #include <iostream> using namespace std; ...
  • crop = cv2.warpAffine(image, mapping, (image.shape[1], image.shape[0]), borderMode=cv2.BORDER_CONSTANT, borderValue=padding) cv2.imshow('cv2warpAffine', crop) cv2.waitKey(0) 不做任何变化时: mapping ...
  • opencv学习(三十五)之仿射变换warpAffine

    万次阅读 多人点赞 2017-02-22 10:50:41
    利用opencv实现仿射变换一般会涉及到warpAffine和getRotationMatrix2D两个函数,其中warpAffine可以实现一些简单的重映射,而getRotationMatrix2D可以获得旋转矩阵。 warpAffine函数 void cv::warpAffine ...
  • NEON优化——OpenCV WarpAffine最近邻

    千次阅读 2018-11-23 21:07:30
    warpAffine一种典型的应用场景是将camera拍到的图像中人脸抠出来,通过变换输出一张正脸,然后再做一些人脸识别,关键点检测之类的运算。所以通常是输入尺寸固定,输出尺寸也固定,变的是转换矩阵。 最近邻的优势是...
  • double angle, double scale) 参数详解: Point2f center:表示旋转的中心点 double angle:表示旋转的角度 double scale:图像缩放因子 返回是一个2X3的矩阵 warpAffine函数: warpAffine( src, warp_dst, warp_...
  • 本篇笔记主要记录Opencv里的图像翻转,平移,旋转,仿射及透视功能,主要是下面几个API:cv2.flip() # 图像翻转cv2.warpAffine() #图像仿射cv2.getRotationMatrix2D() #取得旋转角度的Matrixcv2....
  • cv2.warpAffine 参数详解

    万次阅读 多人点赞 2018-08-19 15:51:55
    opencv中的仿射变换在python中的应用并未发现有细致的讲解,函数cv2.warpAffine的参数也模糊不清,今天和大家分享一下参数的功能和具体效果,如下: 官方给出的参数为: cv2.warpAffine(src, M, dsize[, dst[, ...
  • cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])→ dst src input image. dst output image that has the size dsize and the same type as src . M 2×3 transformation matrix. ...
  • 简述 仿射变换是二维坐标间的线性变换,故而变换后的图像仍然具有原图的一些性质,包括“平直性”以及“平行性”,常用于图像...下面我们将对warpAffine()函数进行介绍,并且实现图像的旋转和平移。 warpAffine...
  • 仿射变换详解 warpAffine

    万次阅读 热门讨论 2016-11-25 14:09:28
    warpAffine ( InputArray  src , OutputArray  dst , InputArray  M , Size  dsize , int  flags =INTER_LINEAR, int  borderMode =BORDER_CONSTANT, const Scalar&  borderValue =Scalar() ) ¶ ...
  • 使用warpAffine()进行图像旋转,是基于仿射变换的原理,通常2 ×\times× 3矩阵进行仿射变换: 对于二维矩阵: X=[xy] X = \begin{bmatrix} x \\ y \\ \end{bmatrix} X=[xy​] 进行如下变换: T=A⋅[xy]+B T =A\cdot...
  • 因为处理数据需求,要对tensor实现 cv2.warpAffine( )的功能 原图: size = 360 × 480 首先cv2.warpAffine( ), import cv2 import numpy as np img_path = r'C:\Users\CSDN\Desktop\00000001.jpg' image = cv2....
  • 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)#边界填充模式 ''' 填充模式参数: ...
  • warpAffine(warp_dst, warp_rotate_dst, rot_mat, warp_dst.size()); /// 显示结果 namedWindow(source_window, CV_WINDOW_AUTOSIZE); imshow(source_window, src); namedWindow(warp_window, CV_WINDOW_...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,643
精华内容 2,657
关键字:

warpaffine

友情链接: waveprogress.zip