OpenCV 中的 remap 函数

2023-11-03

上一篇文章中提到 warpAffine 会分块处理,将坐标映射和插值系数分别存储下来,然后借助 remap 来实现最终的映射。而 remap 会根据映射关系取源像素并加权计算出目的像素值。其最核心的计算为 RemapVec_8u

cv::remap

cv::remap
RemapInvoker
nnfunc
ifunc

d s t ( x , y ) = s r c ( m a p x ( x , y ) , m a p y ( x , y ) ) \mathrm{dst}(x,y)=\mathrm{src}(\mathrm{map}_x(x,y), \mathrm{map}_y(x,y)) dst(x,y)=src(mapx(x,y),mapy(x,y))
定义不同类型的函数表。
remapBilinear

    CV_INSTRUMENT_REGION();

    static RemapNNFunc nn_tab[] =
    {
        remapNearest<uchar>, remapNearest<schar>, remapNearest<ushort>, remapNearest<short>,
        remapNearest<int>, remapNearest<float>, remapNearest<double>, 0
    };

    static RemapFunc linear_tab[] =
    {
        remapBilinear<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, RemapVec_8u, short>, 0,
        remapBilinear<Cast<float, ushort>, RemapNoVec, float>,
        remapBilinear<Cast<float, short>, RemapNoVec, float>, 0,
        remapBilinear<Cast<float, float>, RemapNoVec, float>,
        remapBilinear<Cast<double, double>, RemapNoVec, float>, 0
    };

    static RemapFunc cubic_tab[] =
    {
        remapBicubic<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, INTER_REMAP_COEF_SCALE>, 0,
        remapBicubic<Cast<float, ushort>, float, 1>,
        remapBicubic<Cast<float, short>, float, 1>, 0,
        remapBicubic<Cast<float, float>, float, 1>,
        remapBicubic<Cast<double, double>, float, 1>, 0
    };

    static RemapFunc lanczos4_tab[] =
    {
        remapLanczos4<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, INTER_REMAP_COEF_SCALE>, 0,
        remapLanczos4<Cast<float, ushort>, float, 1>,
        remapLanczos4<Cast<float, short>, float, 1>, 0,
        remapLanczos4<Cast<float, float>, float, 1>,
        remapLanczos4<Cast<double, double>, float, 1>, 0
    };

_map1不能为空,_map2可以。

    CV_Assert( !_map1.empty() );
    CV_Assert( _map2.empty() || (_map2.size() == _map1.size()));

    CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(),
               ocl_remap(_src, _dst, _map1, _map2, interpolation, borderType, borderValue))

    Mat src = _src.getMat(), map1 = _map1.getMat(), map2 = _map2.getMat();
    _dst.create( map1.size(), src.type() );
    Mat dst = _dst.getMat();


    CV_OVX_RUN(
        src.type() == CV_8UC1 && dst.type() == CV_8UC1 &&
        !ovx::skipSmallImages<VX_KERNEL_REMAP>(src.cols, src.rows) &&
        (borderType& ~BORDER_ISOLATED) == BORDER_CONSTANT &&
        ((map1.type() == CV_32FC2 && map2.empty() && map1.size == dst.size) ||
         (map1.type() == CV_32FC1 && map2.type() == CV_32FC1 && map1.size == dst.size && map2.size == dst.size) ||
         (map1.empty() && map2.type() == CV_32FC2 && map2.size == dst.size)) &&
        ((borderType & BORDER_ISOLATED) != 0 || !src.isSubmatrix()),
        openvx_remap(src, dst, map1, map2, interpolation, borderValue));

输入输出不能过大。
如果输入输出相同则进行拷贝。

    CV_Assert( dst.cols < SHRT_MAX && dst.rows < SHRT_MAX && src.cols < SHRT_MAX && src.rows < SHRT_MAX );

    if( dst.data == src.data )
        src = src.clone();

    if( interpolation == INTER_AREA )
        interpolation = INTER_LINEAR;

    int type = src.type(), depth = CV_MAT_DEPTH(type);

#if defined HAVE_IPP && !IPP_DISABLE_REMAP
    CV_IPP_CHECK()
    {
        if ((interpolation == INTER_LINEAR || interpolation == INTER_CUBIC || interpolation == INTER_NEAREST) &&
                map1.type() == CV_32FC1 && map2.type() == CV_32FC1 &&
                (borderType == BORDER_CONSTANT || borderType == BORDER_TRANSPARENT))
        {
            int ippInterpolation =
                interpolation == INTER_NEAREST ? IPPI_INTER_NN :
                interpolation == INTER_LINEAR ? IPPI_INTER_LINEAR : IPPI_INTER_CUBIC;

            ippiRemap ippFunc =
                type == CV_8UC1 ? (ippiRemap)ippiRemap_8u_C1R :
                type == CV_8UC3 ? (ippiRemap)ippiRemap_8u_C3R :
                type == CV_8UC4 ? (ippiRemap)ippiRemap_8u_C4R :
                type == CV_16UC1 ? (ippiRemap)ippiRemap_16u_C1R :
                type == CV_16UC3 ? (ippiRemap)ippiRemap_16u_C3R :
                type == CV_16UC4 ? (ippiRemap)ippiRemap_16u_C4R :
                type == CV_32FC1 ? (ippiRemap)ippiRemap_32f_C1R :
                type == CV_32FC3 ? (ippiRemap)ippiRemap_32f_C3R :
                type == CV_32FC4 ? (ippiRemap)ippiRemap_32f_C4R : 0;

            if (ippFunc)
            {
                bool ok;
                IPPRemapInvoker invoker(src, dst, map1, map2, ippFunc, ippInterpolation,
                                        borderType, borderValue, &ok);
                Range range(0, dst.rows);
                parallel_for_(range, invoker, dst.total() / (double)(1 << 16));

                if (ok)
                {
                    CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT);
                    return;
                }
                setIppErrorStatus();
            }
        }
    }
#endif

如果是最近邻插值设置nnfunc,否则设置ifunc
initInterTab2D 返回静态数组的指针给ctab

    RemapNNFunc nnfunc = 0;
    RemapFunc ifunc = 0;
    const void* ctab = 0;
    bool fixpt = depth == CV_8U;
    bool planar_input = false;

    if( interpolation == INTER_NEAREST )
    {
        nnfunc = nn_tab[depth];
        CV_Assert( nnfunc != 0 );
    }
    else
    {
        if( interpolation == INTER_LINEAR )
            ifunc = linear_tab[depth];
        else if( interpolation == INTER_CUBIC ){
            ifunc = cubic_tab[depth];
            CV_Assert( _src.channels() <= 4 );
        }
        else if( interpolation == INTER_LANCZOS4 ){
            ifunc = lanczos4_tab[depth];
            CV_Assert( _src.channels() <= 4 );
        }
        else
            CV_Error( CV_StsBadArg, "Unknown interpolation method" );
        CV_Assert( ifunc != 0 );
        ctab = initInterTab2D( interpolation, fixpt );
    }

    const Mat *m1 = &map1, *m2 = &map2;

    if( (map1.type() == CV_16SC2 && (map2.type() == CV_16UC1 || map2.type() == CV_16SC1 || map2.empty())) ||
        (map2.type() == CV_16SC2 && (map1.type() == CV_16UC1 || map1.type() == CV_16SC1 || map1.empty())) )
    {
        if( map1.type() != CV_16SC2 )
            std::swap(m1, m2);
    }
    else
    {
        CV_Assert( ((map1.type() == CV_32FC2 || map1.type() == CV_16SC2) && map2.empty()) ||
            (map1.type() == CV_32FC1 && map2.type() == CV_32FC1) );
        planar_input = map1.channels() == 1;
    }

调用 RemapInvoker 的函数。

    RemapInvoker invoker(src, dst, m1, m2,
                         borderType, borderValue, planar_input, nnfunc, ifunc,
                         ctab);
    parallel_for_(Range(0, dst.rows), invoker, dst.total()/(double)(1<<16));

initInterTab2D

initInterTab2D
initInterTab1D

initInterTab2D 存储_tab中两个核参数相乘的结果。

    static bool inittab[INTER_MAX+1] = {false};
    float* tab = 0;
    short* itab = 0;
    int ksize = 0;
    if( method == INTER_LINEAR )
        tab = BilinearTab_f[0][0], itab = BilinearTab_i[0][0], ksize=2;
    else if( method == INTER_CUBIC )
        tab = BicubicTab_f[0][0], itab = BicubicTab_i[0][0], ksize=4;
    else if( method == INTER_LANCZOS4 )
        tab = Lanczos4Tab_f[0][0], itab = Lanczos4Tab_i[0][0], ksize=8;
    else
        CV_Error( CV_StsBadArg, "Unknown/unsupported interpolation type" );

initInterTab1D 计算不同方法的一维表值,即原始系数。
INTER_TAB_SIZE 2 I N T E R _ B I T S 2^{\mathrm{INTER\_BITS}} 2INTER_BITS,表的大小取决于插值位宽。
tab尺寸为 INTER_TAB_SIZE2,从而能存储不同系数就的乘积。
NNDeltaTab_i的作用是什么?
内层循环计算中心系数乘积。vy为当前行的中心系数值。
INTER_REMAP_COEF_SCALE 2 I N T E R _ R E M A P _ C O E F _ B I T S 2^{\mathrm{INTER\_REMAP\_COEF\_BITS}} 2INTER_REMAP_COEF_BITS
isumitab当前位置上的中心元素之和。

    if( !inittab[method] )
    {
        AutoBuffer<float> _tab(8*INTER_TAB_SIZE);
        int i, j, k1, k2;
        initInterTab1D(method, _tab.data(), INTER_TAB_SIZE);
        for( i = 0; i < INTER_TAB_SIZE; i++ )
            for( j = 0; j < INTER_TAB_SIZE; j++, tab += ksize*ksize, itab += ksize*ksize )
            {
                int isum = 0;
                NNDeltaTab_i[i*INTER_TAB_SIZE+j][0] = j < INTER_TAB_SIZE/2;
                NNDeltaTab_i[i*INTER_TAB_SIZE+j][1] = i < INTER_TAB_SIZE/2;

                for( k1 = 0; k1 < ksize; k1++ )
                {
                    float vy = _tab[i*ksize + k1];
                    for( k2 = 0; k2 < ksize; k2++ )
                    {
                        float v = vy*_tab[j*ksize + k2];
                        tab[k1*ksize + k2] = v;
                        isum += itab[k1*ksize + k2] = saturate_cast<short>(v*INTER_REMAP_COEF_SCALE);
                    }
                }

计算完成后,tabitab重新指向首地址。

                if( isum != INTER_REMAP_COEF_SCALE )
                {
                    int diff = isum - INTER_REMAP_COEF_SCALE;
                    int ksize2 = ksize/2, Mk1=ksize2, Mk2=ksize2, mk1=ksize2, mk2=ksize2;
                    for( k1 = ksize2; k1 < ksize2+2; k1++ )
                        for( k2 = ksize2; k2 < ksize2+2; k2++ )
                        {
                            if( itab[k1*ksize+k2] < itab[mk1*ksize+mk2] )
                                mk1 = k1, mk2 = k2;
                            else if( itab[k1*ksize+k2] > itab[Mk1*ksize+Mk2] )
                                Mk1 = k1, Mk2 = k2;
                        }
                    if( diff < 0 )
                        itab[Mk1*ksize + Mk2] = (short)(itab[Mk1*ksize + Mk2] - diff);
                    else
                        itab[mk1*ksize + mk2] = (short)(itab[mk1*ksize + mk2] - diff);
                }
            }
        tab -= INTER_TAB_SIZE2*ksize*ksize;
        itab -= INTER_TAB_SIZE2*ksize*ksize;
#if CV_SIMD128
        if( method == INTER_LINEAR )
        {
            for( i = 0; i < INTER_TAB_SIZE2; i++ )
                for( j = 0; j < 4; j++ )
                {
                    BilinearTab_iC4[i][0][j*2] = BilinearTab_i[i][0][0];
                    BilinearTab_iC4[i][0][j*2+1] = BilinearTab_i[i][0][1];
                    BilinearTab_iC4[i][1][j*2] = BilinearTab_i[i][1][0];
                    BilinearTab_iC4[i][1][j*2+1] = BilinearTab_i[i][1][1];
                }
        }
#endif
        inittab[method] = true;
    }
    return fixpt ? (const void*)itab : (const void*)tab;

initInterTab1D

initInterTab1D
interpolateLinear
    float scale = 1.f/tabsz;
    if( method == INTER_LINEAR )
    {
        for( int i = 0; i < tabsz; i++, tab += 2 )
            interpolateLinear( i*scale, tab );
    }
    else if( method == INTER_CUBIC )
    {
        for( int i = 0; i < tabsz; i++, tab += 4 )
            interpolateCubic( i*scale, tab );
    }
    else if( method == INTER_LANCZOS4 )
    {
        for( int i = 0; i < tabsz; i++, tab += 8 )
            interpolateLanczos4( i*scale, tab );
    }
    else
        CV_Error( CV_StsBadArg, "Unknown interpolation method" );

interpolateLinear

计算插值系数。

    coeffs[0] = 1.f - x;
    coeffs[1] = x;

RemapInvoker

public:
    RemapInvoker(const Mat& _src, Mat& _dst, const Mat *_m1,
                 const Mat *_m2, int _borderType, const Scalar &_borderValue,
                 int _planar_input, RemapNNFunc _nnfunc, RemapFunc _ifunc, const void *_ctab) :
        ParallelLoopBody(), src(&_src), dst(&_dst), m1(_m1), m2(_m2),
        borderType(_borderType), borderValue(_borderValue),
        planar_input(_planar_input), nnfunc(_nnfunc), ifunc(_ifunc), ctab(_ctab)
    {
    }

brows0为缓冲区行数,bcols0为列数。
dpart为目的矩阵的当前处理区域。
_bufxyxy的缓冲区。bufxy为当前 RoI 区域。

    virtual void operator() (const Range& range) const CV_OVERRIDE
    {
        int x, y, x1, y1;
        const int buf_size = 1 << 14;
        int brows0 = std::min(128, dst->rows), map_depth = m1->depth();
        int bcols0 = std::min(buf_size/brows0, dst->cols);
        brows0 = std::min(buf_size/bcols0, dst->rows);

        Mat _bufxy(brows0, bcols0, CV_16SC2), _bufa;
        if( !nnfunc )
            _bufa.create(brows0, bcols0, CV_16UC1);

        for( y = range.start; y < range.end; y += brows0 )
        {
            for( x = 0; x < dst->cols; x += bcols0 )
            {
                int brows = std::min(brows0, range.end - y);
                int bcols = std::min(bcols0, dst->cols - x);
                Mat dpart(*dst, Rect(x, y, bcols, brows));
                Mat bufxy(_bufxy, Rect(0, 0, bcols, brows));

如果为最近邻映射。

                if( nnfunc )
                {
                    if( m1->type() == CV_16SC2 && m2->empty() ) // the data is already in the right format
                        bufxy = (*m1)(Rect(x, y, bcols, brows));
                    else if( map_depth != CV_32F )
                    {
                        for( y1 = 0; y1 < brows; y1++ )
                        {
                            short* XY = bufxy.ptr<short>(y1);
                            const short* sXY = m1->ptr<short>(y+y1) + x*2;
                            const ushort* sA = m2->ptr<ushort>(y+y1) + x;

                            for( x1 = 0; x1 < bcols; x1++ )
                            {
                                int a = sA[x1] & (INTER_TAB_SIZE2-1);
                                XY[x1*2] = sXY[x1*2] + NNDeltaTab_i[a][0];
                                XY[x1*2+1] = sXY[x1*2+1] + NNDeltaTab_i[a][1];
                            }
                        }
                    }
                    else if( !planar_input )
                        (*m1)(Rect(x, y, bcols, brows)).convertTo(bufxy, bufxy.depth());
                    else
                    {
                        for( y1 = 0; y1 < brows; y1++ )
                        {
                            short* XY = bufxy.ptr<short>(y1);
                            const float* sX = m1->ptr<float>(y+y1) + x;
                            const float* sY = m2->ptr<float>(y+y1) + x;
                            x1 = 0;

                            #if CV_SIMD128
                            {
                                int span = v_float32x4::nlanes;
                                for( ; x1 <= bcols - span * 2; x1 += span * 2 )
                                {
                                    v_int32x4 ix0 = v_round(v_load(sX + x1));
                                    v_int32x4 iy0 = v_round(v_load(sY + x1));
                                    v_int32x4 ix1 = v_round(v_load(sX + x1 + span));
                                    v_int32x4 iy1 = v_round(v_load(sY + x1 + span));

                                    v_int16x8 dx, dy;
                                    dx = v_pack(ix0, ix1);
                                    dy = v_pack(iy0, iy1);
                                    v_store_interleave(XY + x1 * 2, dx, dy);
                                }
                            }
                            #endif
                            for( ; x1 < bcols; x1++ )
                            {
                                XY[x1*2] = saturate_cast<short>(sX[x1]);
                                XY[x1*2+1] = saturate_cast<short>(sY[x1]);
                            }
                        }
                    }
                    nnfunc( *src, dpart, bufxy, borderType, borderValue );
                    continue;
                }

否则为线性插值。
XY指向bufxy的当前行,A指向bufa的当前行。
如果m1为双通道整型且m2为单通道整型, bufxy封装m1的当前处理分块,即源坐标值。
sAm2矩阵的当前位置。
INTER_TAB_SIZE2 2 10 2^{10} 210
由掩码得到小数部分保存到A中。

                Mat bufa(_bufa, Rect(0, 0, bcols, brows));
                for( y1 = 0; y1 < brows; y1++ )
                {
                    short* XY = bufxy.ptr<short>(y1);
                    ushort* A = bufa.ptr<ushort>(y1);

                    if( m1->type() == CV_16SC2 && (m2->type() == CV_16UC1 || m2->type() == CV_16SC1) )
                    {
                        bufxy = (*m1)(Rect(x, y, bcols, brows));

                        const ushort* sA = m2->ptr<ushort>(y+y1) + x;
                        x1 = 0;

                        #if CV_SIMD128
                        {
                            v_uint16x8 v_scale = v_setall_u16(INTER_TAB_SIZE2 - 1);
                            int span = v_uint16x8::nlanes;
                            for( ; x1 <= bcols - span; x1 += span )
                                v_store((unsigned short*)(A + x1), v_load(sA + x1) & v_scale);
                        }
                        #endif
                        for( ; x1 < bcols; x1++ )
                            A[x1] = (ushort)(sA[x1] & (INTER_TAB_SIZE2-1));
                    }

如果m1为单通道

                    else if( planar_input )
                    {
                        const float* sX = m1->ptr<float>(y+y1) + x;
                        const float* sY = m2->ptr<float>(y+y1) + x;

                        x1 = 0;
                        #if CV_SIMD128
                        {
                            v_float32x4 v_scale = v_setall_f32((float)INTER_TAB_SIZE);
                            v_int32x4 v_scale2 = v_setall_s32(INTER_TAB_SIZE - 1);
                            int span = v_float32x4::nlanes;
                            for( ; x1 <= bcols - span * 2; x1 += span * 2 )
                            {
                                v_int32x4 v_sx0 = v_round(v_scale * v_load(sX + x1));
                                v_int32x4 v_sy0 = v_round(v_scale * v_load(sY + x1));
                                v_int32x4 v_sx1 = v_round(v_scale * v_load(sX + x1 + span));
                                v_int32x4 v_sy1 = v_round(v_scale * v_load(sY + x1 + span));
                                v_uint16x8 v_sx8 = v_reinterpret_as_u16(v_pack(v_sx0 & v_scale2, v_sx1 & v_scale2));
                                v_uint16x8 v_sy8 = v_reinterpret_as_u16(v_pack(v_sy0 & v_scale2, v_sy1 & v_scale2));
                                v_uint16x8 v_v = v_shl<INTER_BITS>(v_sy8) | (v_sx8);
                                v_store(A + x1, v_v);

                                v_int16x8 v_d0 = v_pack(v_shr<INTER_BITS>(v_sx0), v_shr<INTER_BITS>(v_sx1));
                                v_int16x8 v_d1 = v_pack(v_shr<INTER_BITS>(v_sy0), v_shr<INTER_BITS>(v_sy1));
                                v_store_interleave(XY + (x1 << 1), v_d0, v_d1);
                            }
                        }
                        #endif
                        for( ; x1 < bcols; x1++ )
                        {
                            int sx = cvRound(sX[x1]*INTER_TAB_SIZE);
                            int sy = cvRound(sY[x1]*INTER_TAB_SIZE);
                            int v = (sy & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (sx & (INTER_TAB_SIZE-1));
                            XY[x1*2] = saturate_cast<short>(sx >> INTER_BITS);
                            XY[x1*2+1] = saturate_cast<short>(sy >> INTER_BITS);
                            A[x1] = (ushort)v;
                        }
                    }
                    else
                    {
                        const float* sXY = m1->ptr<float>(y+y1) + x*2;
                        x1 = 0;

                        #if CV_SIMD128
                        {
                            v_float32x4 v_scale = v_setall_f32((float)INTER_TAB_SIZE);
                            v_int32x4 v_scale2 = v_setall_s32(INTER_TAB_SIZE - 1), v_scale3 = v_setall_s32(INTER_TAB_SIZE);
                            int span = v_float32x4::nlanes;
                            for( ; x1 <= bcols - span * 2; x1 += span * 2 )
                            {
                                v_float32x4 v_fx, v_fy;
                                v_load_deinterleave(sXY + (x1 << 1), v_fx, v_fy);
                                v_int32x4 v_sx0 = v_round(v_fx * v_scale);
                                v_int32x4 v_sy0 = v_round(v_fy * v_scale);
                                v_load_deinterleave(sXY + ((x1 + span) << 1), v_fx, v_fy);
                                v_int32x4 v_sx1 = v_round(v_fx * v_scale);
                                v_int32x4 v_sy1 = v_round(v_fy * v_scale);
                                v_int32x4 v_v0 = v_muladd(v_scale3, (v_sy0 & v_scale2), (v_sx0 & v_scale2));
                                v_int32x4 v_v1 = v_muladd(v_scale3, (v_sy1 & v_scale2), (v_sx1 & v_scale2));
                                v_uint16x8 v_v8 = v_reinterpret_as_u16(v_pack(v_v0, v_v1));
                                v_store(A + x1, v_v8);
                                v_int16x8 v_dx = v_pack(v_shr<INTER_BITS>(v_sx0), v_shr<INTER_BITS>(v_sx1));
                                v_int16x8 v_dy = v_pack(v_shr<INTER_BITS>(v_sy0), v_shr<INTER_BITS>(v_sy1));
                                v_store_interleave(XY + (x1 << 1), v_dx, v_dy);
                            }
                        }
                        #endif

                        for( ; x1 < bcols; x1++ )
                        {
                            int sx = cvRound(sXY[x1*2]*INTER_TAB_SIZE);
                            int sy = cvRound(sXY[x1*2+1]*INTER_TAB_SIZE);
                            int v = (sy & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (sx & (INTER_TAB_SIZE-1));
                            XY[x1*2] = saturate_cast<short>(sx >> INTER_BITS);
                            XY[x1*2+1] = saturate_cast<short>(sy >> INTER_BITS);
                            A[x1] = (ushort)v;
                        }
                    }
                }

调用ifunc完成插值操作。

                ifunc(*src, dpart, bufxy, bufa, ctab, borderType, borderValue);
            }
        }
    }

类成员。

private:
    const Mat* src;
    Mat* dst;
    const Mat *m1, *m2;
    int borderType;
    Scalar borderValue;
    int planar_input;
    RemapNNFunc nnfunc;
    RemapFunc ifunc;
    const void *ctab;

remapBilinear

remapBilinear
RemapVec_8u

remapBilinear 在必要时填充边界,图像映射通过 RemapVec_8u 实现加速。
sstep为源图行跨度。MatStep 能够进行隐式类型转换。
cval为填充像素值。
width1height1为横向和纵向上的最大取值。

    typedef typename CastOp::rtype T;
    typedef typename CastOp::type1 WT;
    Size ssize = _src.size(), dsize = _dst.size();
    const int cn = _src.channels();
    const AT* wtab = (const AT*)_wtab;
    const T* S0 = _src.ptr<T>();
    size_t sstep = _src.step/sizeof(S0[0]);
    T cval[CV_CN_MAX];
    CastOp castOp;
    VecOp vecOp;

    for(int k = 0; k < cn; k++ )
        cval[k] = saturate_cast<T>(_borderValue[k & 3]);

    unsigned width1 = std::max(ssize.width-1, 0), height1 = std::max(ssize.height-1, 0);
    CV_Assert( !ssize.empty() );
#if CV_SIMD128
    if( _src.type() == CV_8UC3 )
        width1 = std::max(ssize.width-2, 0);
#endif

D指向目的图上的当前行。
XYFXY指向整数和小数部分的值。
curInlier判断是否超出边界。
如果dx在区域内,根据XY中的值是否超出边界来判断。
X0是上一次处理完后的行位置,也是本次起点,X1是本次处理的终点。
dy = 0时,由于X0=0,所以跳过不处理。如果prevInliercurInlier相等则跳过不处理。
dx = dsize.width本是一个不存在的位置,这使得在行尾prevInliercurInlier不相等,马上进行处理。

    for(int dy = 0; dy < dsize.height; dy++ )
    {
        T* D = _dst.ptr<T>(dy);
        const short* XY = _xy.ptr<short>(dy);
        const ushort* FXY = _fxy.ptr<ushort>(dy);
        int X0 = 0;
        bool prevInlier = false;

        for(int dx = 0; dx <= dsize.width; dx++ )
        {
            bool curInlier = dx < dsize.width ?
                (unsigned)XY[dx*2] < width1 &&
                (unsigned)XY[dx*2+1] < height1 : !prevInlier;
            if( curInlier == prevInlier )
                continue;

            int X1 = dx;
            dx = X0;
            X0 = X1;
            prevInlier = curInlier;

f ( x , y ) = a 0 α 0 β 0 + a 1 α 1 β 0 + b 0 α 0 β 1 + b 1 α 1 β 1 \begin{aligned} f(x, y) &= a_0\alpha_0\beta_0+ a_1\alpha_1\beta_0 + b_0\alpha_0\beta_1+ b_1\alpha_1\beta_1 \end{aligned} f(x,y)=a0α0β0+a1α1β0+b0α0β1+b1α1β1
如果不是内露层,调用vecOp处理可向量化的数据;剩余部分循环处理。总共处理X1-X0个像素。
S为对应到源图的左上点地址,w为融合后的4个权重参数。以FXY数组的值作为索引,可以获得4个中心系数乘积。

            if( !curInlier )
            {
                int len = vecOp( _src, D, XY + dx*2, FXY + dx, wtab, X1 - dx );
                D += len*cn;
                dx += len;

                if( cn == 1 )
                {
                    for( ; dx < X1; dx++, D++ )
                    {
                        int sx = XY[dx*2], sy = XY[dx*2+1];
                        const AT* w = wtab + FXY[dx]*4;
                        const T* S = S0 + sy*sstep + sx;
                        *D = castOp(WT(S[0]*w[0] + S[1]*w[1] + S[sstep]*w[2] + S[sstep+1]*w[3]));
                    }
                }
                else if( cn == 2 )
                    for( ; dx < X1; dx++, D += 2 )
                    {
                        int sx = XY[dx*2], sy = XY[dx*2+1];
                        const AT* w = wtab + FXY[dx]*4;
                        const T* S = S0 + sy*sstep + sx*2;
                        WT t0 = S[0]*w[0] + S[2]*w[1] + S[sstep]*w[2] + S[sstep+2]*w[3];
                        WT t1 = S[1]*w[0] + S[3]*w[1] + S[sstep+1]*w[2] + S[sstep+3]*w[3];
                        D[0] = castOp(t0); D[1] = castOp(t1);
                    }
                else if( cn == 3 )
                    for( ; dx < X1; dx++, D += 3 )
                    {
                        int sx = XY[dx*2], sy = XY[dx*2+1];
                        const AT* w = wtab + FXY[dx]*4;
                        const T* S = S0 + sy*sstep + sx*3;
                        WT t0 = S[0]*w[0] + S[3]*w[1] + S[sstep]*w[2] + S[sstep+3]*w[3];
                        WT t1 = S[1]*w[0] + S[4]*w[1] + S[sstep+1]*w[2] + S[sstep+4]*w[3];
                        WT t2 = S[2]*w[0] + S[5]*w[1] + S[sstep+2]*w[2] + S[sstep+5]*w[3];
                        D[0] = castOp(t0); D[1] = castOp(t1); D[2] = castOp(t2);
                    }
                else if( cn == 4 )
                    for( ; dx < X1; dx++, D += 4 )
                    {
                        int sx = XY[dx*2], sy = XY[dx*2+1];
                        const AT* w = wtab + FXY[dx]*4;
                        const T* S = S0 + sy*sstep + sx*4;
                        WT t0 = S[0]*w[0] + S[4]*w[1] + S[sstep]*w[2] + S[sstep+4]*w[3];
                        WT t1 = S[1]*w[0] + S[5]*w[1] + S[sstep+1]*w[2] + S[sstep+5]*w[3];
                        D[0] = castOp(t0); D[1] = castOp(t1);
                        t0 = S[2]*w[0] + S[6]*w[1] + S[sstep+2]*w[2] + S[sstep+6]*w[3];
                        t1 = S[3]*w[0] + S[7]*w[1] + S[sstep+3]*w[2] + S[sstep+7]*w[3];
                        D[2] = castOp(t0); D[3] = castOp(t1);
                    }
                else
                    for( ; dx < X1; dx++, D += cn )
                    {
                        int sx = XY[dx*2], sy = XY[dx*2+1];
                        const AT* w = wtab + FXY[dx]*4;
                        const T* S = S0 + sy*sstep + sx*cn;
                        for(int k = 0; k < cn; k++ )
                        {
                            WT t0 = S[k]*w[0] + S[k+cn]*w[1] + S[sstep+k]*w[2] + S[sstep+k+cn]*w[3];
                            D[k] = castOp(t0);
                        }
                    }
            }

否则处理边界的情况。
BORDER_TRANSPARENT直接跳过。

            else
            {
                if( borderType == BORDER_TRANSPARENT && cn != 3 )
                {
                    D += (X1 - dx)*cn;
                    dx = X1;
                    continue;
                }

单通道的常量边界、重复边界或者其他类型。
(sxsy)为原图上的坐标。sx0sy0sx1sy1是周围修剪后的坐标,用于插值。
cv::borderInterpolate 计算外推像素的源位置。

                if( cn == 1 )
                    for( ; dx < X1; dx++, D++ )
                    {
                        int sx = XY[dx*2], sy = XY[dx*2+1];
                        if( borderType == BORDER_CONSTANT &&
                            (sx >= ssize.width || sx+1 < 0 ||
                             sy >= ssize.height || sy+1 < 0) )
                        {
                            D[0] = cval[0];
                        }
                        else
                        {
                            int sx0, sx1, sy0, sy1;
                            T v0, v1, v2, v3;
                            const AT* w = wtab + FXY[dx]*4;
                            if( borderType == BORDER_REPLICATE )
                            {
                                sx0 = clip(sx, 0, ssize.width);
                                sx1 = clip(sx+1, 0, ssize.width);
                                sy0 = clip(sy, 0, ssize.height);
                                sy1 = clip(sy+1, 0, ssize.height);
                                v0 = S0[sy0*sstep + sx0];
                                v1 = S0[sy0*sstep + sx1];
                                v2 = S0[sy1*sstep + sx0];
                                v3 = S0[sy1*sstep + sx1];
                            }
                            else
                            {
                                sx0 = borderInterpolate(sx, ssize.width, borderType);
                                sx1 = borderInterpolate(sx+1, ssize.width, borderType);
                                sy0 = borderInterpolate(sy, ssize.height, borderType);
                                sy1 = borderInterpolate(sy+1, ssize.height, borderType);
                                v0 = sx0 >= 0 && sy0 >= 0 ? S0[sy0*sstep + sx0] : cval[0];
                                v1 = sx1 >= 0 && sy0 >= 0 ? S0[sy0*sstep + sx1] : cval[0];
                                v2 = sx0 >= 0 && sy1 >= 0 ? S0[sy1*sstep + sx0] : cval[0];
                                v3 = sx1 >= 0 && sy1 >= 0 ? S0[sy1*sstep + sx1] : cval[0];
                            }
                            D[0] = castOp(WT(v0*w[0] + v1*w[1] + v2*w[2] + v3*w[3]));
                        }
                    }

多通道的边界处理。

                else
                    for( ; dx < X1; dx++, D += cn )
                    {
                        int sx = XY[dx*2], sy = XY[dx*2+1];
                        if( borderType == BORDER_CONSTANT &&
                            (sx >= ssize.width || sx+1 < 0 ||
                             sy >= ssize.height || sy+1 < 0) )
                        {
                            for(int k = 0; k < cn; k++ )
                                D[k] = cval[k];
                        }
                        else
                        {
                            int sx0, sx1, sy0, sy1;
                            const T *v0, *v1, *v2, *v3;
                            const AT* w = wtab + FXY[dx]*4;
                            if( borderType == BORDER_REPLICATE )
                            {
                                sx0 = clip(sx, 0, ssize.width);
                                sx1 = clip(sx+1, 0, ssize.width);
                                sy0 = clip(sy, 0, ssize.height);
                                sy1 = clip(sy+1, 0, ssize.height);
                                v0 = S0 + sy0*sstep + sx0*cn;
                                v1 = S0 + sy0*sstep + sx1*cn;
                                v2 = S0 + sy1*sstep + sx0*cn;
                                v3 = S0 + sy1*sstep + sx1*cn;
                            }
                            else if( borderType == BORDER_TRANSPARENT &&
                                ((unsigned)sx >= (unsigned)(ssize.width-1) ||
                                (unsigned)sy >= (unsigned)(ssize.height-1)))
                                continue;
                            else
                            {
                                sx0 = borderInterpolate(sx, ssize.width, borderType);
                                sx1 = borderInterpolate(sx+1, ssize.width, borderType);
                                sy0 = borderInterpolate(sy, ssize.height, borderType);
                                sy1 = borderInterpolate(sy+1, ssize.height, borderType);
                                v0 = sx0 >= 0 && sy0 >= 0 ? S0 + sy0*sstep + sx0*cn : &cval[0];
                                v1 = sx1 >= 0 && sy0 >= 0 ? S0 + sy0*sstep + sx1*cn : &cval[0];
                                v2 = sx0 >= 0 && sy1 >= 0 ? S0 + sy1*sstep + sx0*cn : &cval[0];
                                v3 = sx1 >= 0 && sy1 >= 0 ? S0 + sy1*sstep + sx1*cn : &cval[0];
                            }
                            for(int k = 0; k < cn; k++ )
                                D[k] = castOp(WT(v0[k]*w[0] + v1[k]*w[1] + v2[k]*w[2] + v3[k]*w[3]));
                        }
                    }
            }
        }
    }

RemapVec_8u

处理1、3或4通道的数据,并且跨度不能过大。
S0为源图第0行,S1为源图第1行。
对于单通道数据,wtab指向输入的数组;否则指向 BilinearTab_iC4 数组。
D为目的数据的指针。
INTER_REMAP_COEF_SCALE 2 15 2^{15} 215
delta 2 14 2^{14} 214
xy2ofs的高低字节分别为源图上yx元素的偏移。

    int operator()( const Mat& _src, void* _dst, const short* XY,
                    const ushort* FXY, const void* _wtab, int width ) const
    {
        int cn = _src.channels(), x = 0, sstep = (int)_src.step;

        if( (cn != 1 && cn != 3 && cn != 4) || sstep >= 0x8000 )
            return 0;

        const uchar *S0 = _src.ptr(), *S1 = _src.ptr(1);
        const short* wtab = cn == 1 ? (const short*)_wtab : &BilinearTab_iC4[0][0][0];
        uchar* D = (uchar*)_dst;
        v_int32x4 delta = v_setall_s32(INTER_REMAP_COEF_SCALE / 2);
        v_int16x8 xy2ofs = v_reinterpret_as_s16(v_setall_s32(cn + (sstep << 16)));
        int CV_DECL_ALIGNED(16) iofs0[4], iofs1[4];
        const uchar* src_limit_8bytes = _src.datalimit - v_int16x8::nlanes;
#define CV_PICK_AND_PACK_RGB(ptr, offset, result)  \
        {                                          \
            const uchar* const p = ((const uchar*)ptr) + (offset); \
            if (p <= src_limit_8bytes)             \
            {                                      \
                v_uint8x16 rrggbb, dummy;          \
                v_uint16x8 rrggbb8, dummy8;        \
                v_uint8x16 rgb0 = v_reinterpret_as_u8(v_int32x4(*(unaligned_int*)(p), 0, 0, 0)); \
                v_uint8x16 rgb1 = v_reinterpret_as_u8(v_int32x4(*(unaligned_int*)(p + 3), 0, 0, 0)); \
                v_zip(rgb0, rgb1, rrggbb, dummy);  \
                v_expand(rrggbb, rrggbb8, dummy8); \
                result = v_reinterpret_as_s16(rrggbb8); \
            }                                      \
            else                                   \
            {                                      \
                result = v_int16x8((short)p[0], (short)p[3], /* r0r1 */ \
                                   (short)p[1], (short)p[4], /* g0g1 */ \
                                   (short)p[2], (short)p[5], /* b0b1 */ 0, 0); \
            }                                      \
        }
#define CV_PICK_AND_PACK_RGBA(ptr, offset, result) \
        {                                          \
            const uchar* const p = ((const uchar*)ptr) + (offset); \
            CV_DbgAssert(p <= src_limit_8bytes);   \
            v_uint8x16 rrggbbaa, dummy;            \
            v_uint16x8 rrggbbaa8, dummy8;          \
            v_uint8x16 rgba0 = v_reinterpret_as_u8(v_int32x4(*(unaligned_int*)(p), 0, 0, 0)); \
            v_uint8x16 rgba1 = v_reinterpret_as_u8(v_int32x4(*(unaligned_int*)(p + v_int32x4::nlanes), 0, 0, 0)); \
            v_zip(rgba0, rgba1, rrggbbaa, dummy);  \
            v_expand(rrggbbaa, rrggbbaa8, dummy8); \
            result = v_reinterpret_as_s16(rrggbbaa8); \
        }
#define CV_PICK_AND_PACK4(base,offset)             \
            v_uint16x8(*(unaligned_ushort*)(base + offset[0]), *(unaligned_ushort*)(base + offset[1]), \
                       *(unaligned_ushort*)(base + offset[2]), *(unaligned_ushort*)(base + offset[3]), \
                       0, 0, 0, 0)

如果是单通道的数据。
OPENCV_HAL_IMPL_NEON_LOADSTORE_OP 定义 v_load,从内存加载到寄存器(vld1q_s16)。
_xy0_xy1是对应到源图上的坐标。
v0v1v2v3是像素值, a0a1b0b1是系数。

        if( cn == 1 )
        {
            for( ; x <= width - 8; x += 8 )
            {
                v_int16x8 _xy0 = v_load(XY + x*2);
                v_int16x8 _xy1 = v_load(XY + x*2 + 8);
                v_int32x4 v0, v1, v2, v3, a0, b0, c0, d0, a1, b1, c1, d1, a2, b2, c2, d2;

v_dotprod 将两个寄存器中的值相乘,并对相邻的结果对求和。先调用 _v128_unzip,后者会使用 vuzp1q_s16vuzp2q_s16 解压向量。
xy0xy1为对应到源图上的一维地址。
v_storeOPENCV_HAL_IMPL_NEON_LOADSTORE_OP 定义,将数据存储到内存(vst1q_s32)。
保存到iofs0iofs1这一步能否省掉?

                v_int32x4 xy0 = v_dotprod( _xy0, xy2ofs );
                v_int32x4 xy1 = v_dotprod( _xy1, xy2ofs );
                v_store( iofs0, xy0 );
                v_store( iofs1, xy1 );

CV_PICK_AND_PACK4 根据基地址和偏移地址获取内存中的4个值然后构造一个 v_uint16x8 对象。这里存在一半的浪费。
vec16为源图上的对应像素,每个uint16存储了相邻的两个像素。
v_reinterpret_as_u8OPENCV_HAL_IMPL_NEON_INIT 定义,向量重新解释强制转换操作 (vreinterpretq_u8_u16)。
v_expand 将寄存器的内容复制到两个2倍宽包装类型的寄存器中(vget_low_u8+vmovl_u8+vget_high_u8)。
stub包含真正的8个uint16元素。
v0再次将每对元素拼到一起。
加载下一行得到v1

                v_uint16x8 stub, dummy;
                v_uint16x8 vec16;
                vec16 = CV_PICK_AND_PACK4(S0, iofs0);
                v_expand(v_reinterpret_as_u8(vec16), stub, dummy);
                v0 = v_reinterpret_as_s32(stub);
                vec16 = CV_PICK_AND_PACK4(S1, iofs0);
                v_expand(v_reinterpret_as_u8(vec16), stub, dummy);
                v1 = v_reinterpret_as_s32(stub);

v_load_low 将数据加载到低位,高位未定义(vcombine_s32+vld1_s32+vdup_n_s32)。
OPENCV_HAL_IMPL_NEON_UNPACKS 定义 v_zip,从成对的两个源寄存器的下半部分读取相邻的向量元素,将这些对进行交织并写入目标寄存器(vzip1q_s16)。
v_zip 交织两个向量。
wtab原本为int16,按照int32加载使得第一行的两个系数成对。
a0b0各自存储了两个像素的4个参数。加载方式比较低效?
a1b1中为垃圾值。
v_recombineOPENCV_HAL_IMPL_NEON_UNPACKS 宏定义,分别合并来两个向量的较低部分和较高部分(vcombine_s32+vget_low_s32+vget_high_s32)。
a2b2为交换后的值,即4个像素第一行和第二行的系数。
v_dotprod 点元素的乘积并将第三个元素添加到相邻对的总和中。

f ( x , y ) = ( a 0 α 0 + a 1 α 1 ) β 0 + ( b 0 α 0 + b 1 α 1 ) β 1 = a 0 α 0 β 0 + a 1 α 1 β 0 + b 0 α 0 β 1 + 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\\ &= a_0\alpha_0\beta_0+ a_1\alpha_1\beta_0 + b_0\alpha_0\beta_1+ b_1\alpha_1\beta_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) \end{aligned} f(x,y)=(a0α0+a1α1)β0+(b0α0+b1α1)β1=a0α0β0+a1α1β0+b0α0β1+b1α1β1=f(Q11)(x2x)(y2y)+f(Q12)(xx1)(y2y)+f(Q21)(x2x)(y2y)+f(Q22)(xx1)(yy1)

                v_zip(v_load_low((int*)(wtab + FXY[x]     * 4)), v_load_low((int*)(wtab + FXY[x + 1] * 4)), a0, a1);
                v_zip(v_load_low((int*)(wtab + FXY[x + 2] * 4)), v_load_low((int*)(wtab + FXY[x + 3] * 4)), b0, b1);
                v_recombine(a0, b0, a2, b2);
                v1 = v_dotprod(v_reinterpret_as_s16(v1), v_reinterpret_as_s16(b2), delta);
                v0 = v_dotprod(v_reinterpret_as_s16(v0), v_reinterpret_as_s16(a2), v1);

对于iofs1重复以上操作得到v2

                vec16 = CV_PICK_AND_PACK4(S0, iofs1);
                v_expand(v_reinterpret_as_u8(vec16), stub, dummy);
                v2 = v_reinterpret_as_s32(stub);
                vec16 = CV_PICK_AND_PACK4(S1, iofs1);
                v_expand(v_reinterpret_as_u8(vec16), stub, dummy);
                v3 = v_reinterpret_as_s32(stub);

                v_zip(v_load_low((int*)(wtab + FXY[x + 4] * 4)), v_load_low((int*)(wtab + FXY[x + 5] * 4)), c0, c1);
                v_zip(v_load_low((int*)(wtab + FXY[x + 6] * 4)), v_load_low((int*)(wtab + FXY[x + 7] * 4)), d0, d1);
                v_recombine(c0, d0, c2, d2);
                v3 = v_dotprod(v_reinterpret_as_s16(v3), v_reinterpret_as_s16(d2), delta);
                v2 = v_dotprod(v_reinterpret_as_s16(v2), v_reinterpret_as_s16(c2), v3);

v_pack_u_storeOPENCV_HAL_IMPL_NEON_PACK 宏定义。
v_pack 将两个值缩短(vqmovn_s32)后合并为一个较大的向量 (vcombine_s16)。
v_pack_u_store 将两个值缩短(vqmovun_s16)后存储到内存(vst1_u8)。
v0v2是对应的。

                v0 = v0 >> INTER_REMAP_COEF_BITS;
                v2 = v2 >> INTER_REMAP_COEF_BITS;
                v_pack_u_store(D + x, v_pack(v0, v2));
            }
        }

如果是三通道数据。

        else if( cn == 3 )
        {
            for( ; x <= width - 5; x += 4, D += 12 )
            {
                v_int16x8 u0, v0, u1, v1;
                v_int16x8 _xy0 = v_load(XY + x * 2);

                v_int32x4 xy0 = v_dotprod(_xy0, xy2ofs);
                v_store(iofs0, xy0);

                int offset0 = FXY[x] * 16;
                int offset1 = FXY[x + 1] * 16;
                int offset2 = FXY[x + 2] * 16;
                int offset3 = FXY[x + 3] * 16;
                v_int16x8 w00 = v_load(wtab + offset0);
                v_int16x8 w01 = v_load(wtab + offset0 + 8);
                v_int16x8 w10 = v_load(wtab + offset1);
                v_int16x8 w11 = v_load(wtab + offset1 + 8);

                CV_PICK_AND_PACK_RGB(S0, iofs0[0], u0);
                CV_PICK_AND_PACK_RGB(S1, iofs0[0], v0);
                CV_PICK_AND_PACK_RGB(S0, iofs0[1], u1);
                CV_PICK_AND_PACK_RGB(S1, iofs0[1], v1);

                v_int32x4 result0 = v_dotprod(u0, w00, v_dotprod(v0, w01, delta)) >> INTER_REMAP_COEF_BITS;
                v_int32x4 result1 = v_dotprod(u1, w10, v_dotprod(v1, w11, delta)) >> INTER_REMAP_COEF_BITS;

                result0 = v_rotate_left<1>(result0);
                v_int16x8 result8 = v_pack(result0, result1);
                v_uint8x16 result16 = v_pack_u(result8, result8);
                v_store_low(D, v_rotate_right<1>(result16));


                w00 = v_load(wtab + offset2);
                w01 = v_load(wtab + offset2 + 8);
                w10 = v_load(wtab + offset3);
                w11 = v_load(wtab + offset3 + 8);
                CV_PICK_AND_PACK_RGB(S0, iofs0[2], u0);
                CV_PICK_AND_PACK_RGB(S1, iofs0[2], v0);
                CV_PICK_AND_PACK_RGB(S0, iofs0[3], u1);
                CV_PICK_AND_PACK_RGB(S1, iofs0[3], v1);

                result0 = v_dotprod(u0, w00, v_dotprod(v0, w01, delta)) >> INTER_REMAP_COEF_BITS;
                result1 = v_dotprod(u1, w10, v_dotprod(v1, w11, delta)) >> INTER_REMAP_COEF_BITS;

                result0 = v_rotate_left<1>(result0);
                result8 = v_pack(result0, result1);
                result16 = v_pack_u(result8, result8);
                v_store_low(D + 6, v_rotate_right<1>(result16));
            }
        }
        else if( cn == 4 )
        {
            for( ; x <= width - 4; x += 4, D += 16 )
            {
                v_int16x8 _xy0 = v_load(XY + x * 2);
                v_int16x8 u0, v0, u1, v1;

                v_int32x4 xy0 = v_dotprod( _xy0, xy2ofs );
                v_store(iofs0, xy0);
                int offset0 = FXY[x] * 16;
                int offset1 = FXY[x + 1] * 16;
                int offset2 = FXY[x + 2] * 16;
                int offset3 = FXY[x + 3] * 16;

                v_int16x8 w00 = v_load(wtab + offset0);
                v_int16x8 w01 = v_load(wtab + offset0 + 8);
                v_int16x8 w10 = v_load(wtab + offset1);
                v_int16x8 w11 = v_load(wtab + offset1 + 8);
                CV_PICK_AND_PACK_RGBA(S0, iofs0[0], u0);
                CV_PICK_AND_PACK_RGBA(S1, iofs0[0], v0);
                CV_PICK_AND_PACK_RGBA(S0, iofs0[1], u1);
                CV_PICK_AND_PACK_RGBA(S1, iofs0[1], v1);

                v_int32x4 result0 = v_dotprod(u0, w00, v_dotprod(v0, w01, delta)) >> INTER_REMAP_COEF_BITS;
                v_int32x4 result1 = v_dotprod(u1, w10, v_dotprod(v1, w11, delta)) >> INTER_REMAP_COEF_BITS;
                v_int16x8 result8 = v_pack(result0, result1);
                v_pack_u_store(D, result8);

                w00 = v_load(wtab + offset2);
                w01 = v_load(wtab + offset2 + 8);
                w10 = v_load(wtab + offset3);
                w11 = v_load(wtab + offset3 + 8);
                CV_PICK_AND_PACK_RGBA(S0, iofs0[2], u0);
                CV_PICK_AND_PACK_RGBA(S1, iofs0[2], v0);
                CV_PICK_AND_PACK_RGBA(S0, iofs0[3], u1);
                CV_PICK_AND_PACK_RGBA(S1, iofs0[3], v1);

                result0 = v_dotprod(u0, w00, v_dotprod(v0, w01, delta)) >> INTER_REMAP_COEF_BITS;
                result1 = v_dotprod(u1, w10, v_dotprod(v1, w11, delta)) >> INTER_REMAP_COEF_BITS;
                result8 = v_pack(result0, result1);
                v_pack_u_store(D + 8, result8);
            }
        }

        return x;
    }

参考资料:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

OpenCV 中的 remap 函数 的相关文章

  • 英特尔的最后分支记录功能是英特尔处理器独有的吗?

    最后分支记录是指存储与最近执行的分支相关的源地址和目标地址的寄存器对 MSR 的集合 它们受英特尔酷睿 2 英特尔至强和英特尔凌动处理器系列的支持 http css csail mit edu 6 858 2012 readings ia3
  • 图像梯度角计算

    我实际上是按照论文的说明进行操作的 输入应该是二进制 边缘 图像 输出应该是一个新图像 并根据论文中的说明进行了修改 我对指令的理解是 获取边缘图像的梯度图像并对其进行修改 并使用修改后的梯度创建一个新图像 因此 在 MATLAB Open
  • opencv水印周围的轮廓

    我想在图像中的水印周围画一个框 我已经提取了水印并找到了轮廓 但是 不会在水印周围绘制轮廓 轮廓是在我的整个图像上绘制的 请帮我提供正确的代码 轮廓坐标的输出为 array 0 0 0 634 450 634 450 0 dtype int
  • 开放简历fisherfaces

    我有这个问题 当我使用 vs2010 调试 opencv 2 4 0 facetec demo c 运行时 程序出现此错误 OpenCV错误 未知函数中图像步长错误 矩阵不连续 因此其行数无法更改 文件 src opencv modul e
  • 如何检测斑点并将其裁剪成 png 文件?

    我一直在开发一个网络应用程序 我陷入了一个有问题的问题 我会尝试解释我想要做什么 在这里您看到第一个大图像 其中有绿色形状 我想要做的是将这些形状裁剪成不同的 png 文件 并使它们的背景透明 就像大图像下面的示例裁剪图像一样 第一张图像将
  • 如何加速 svm.predict?

    我正在编写一个滑动窗口来提取特征并将其输入到 CvSVM 的预测函数中 然而 我偶然发现 svm predict 函数相对较慢 基本上 窗口以固定的步幅长度在图像比例上滑动穿过图像 遍历图像加上提取每个图像特征的速度 窗口大约需要 1000
  • 在骨架图像中查找线 OpenCV python

    我有以下图片 我想找到一些线来进行一些计算 平均长度等 我尝试使用HoughLinesP 但它找不到线 我能怎么做 这是我的代码 sk skeleton mask rows cols sk shape imgOut np zeros row
  • 让网络摄像头在 OpenCV 中工作

    我正在尝试让我的网络摄像头在 Windows 7 64 位中的 OpenCV 版本 2 2 中捕获视频 但是 我遇到了一些困难 OpenCV 附带的示例二进制文件都无法检测到我的网络摄像头 最近我发现这篇文章表明答案在于重新编译一个文件 o
  • opencv中如何去除二值图像噪声?

    将图像转换为二值图像 黑白 后如果有任何噪音怎么办 我消除了那些不需要的噪音 您可以看到下图的黑色区域内有一些白噪声 我该如何去除噪声 使用opencv http img857 imageshack us img857 999 blackn
  • 如何用OpenCV解决图像处理相机IO延迟

    我有一个 OpenCV 程序 其工作原理如下 VideoCapture cap 0 Mat frame while true cap gt gt frame myprocess frame 问题是如果myprocess耗时较长 超过相机的I
  • 如何选择图像插值方法? (Emgu/OpenCV)

    Emgu OpenCV的 net包装器 提供的图像调整大小功能可以使用四种插值方法中的任意一种 http www emgu com wiki files 1 4 0 0 html 596dd03d 301e d3c6 4c53 c42855
  • GCC:如何在 MCU 上完全禁用堆使用?

    我有一个在基于 ARM Cortex M 的 MCU 上运行并用 C 和 C 编写的应用程序 我用gcc and g 编译它并希望完全禁用任何堆使用 在 MCU 启动文件中 堆大小已设置为 0 除此之外 我还想禁止代码中意外使用堆 换句话说
  • 静态 OpenCV 库中未定义的引用

    我有一个使用 OpenCV 3 1 的 C 项目 并且使用共享库可以正常工作 但现在我想使用静态库 位于项目目录中的文件夹中 来编译它 因为我希望能够在未安装 OpenCV 的情况下导出它 如果需要还可以编辑和重新编译 这次我重新编译了 O
  • OpenCV 地板分割检测

    我正在研究一种检测图像中地板的方法 我试图通过将图像缩小为颜色区域然后假设最大区域是地板来实现此目的 我们对机器人的运行环境做出一些相当广泛的假设 我正在寻找一些关于适合这个问题的算法的建议 任何帮助将不胜感激 编辑 具体来说 我正在寻找一
  • 将 CvSeq 保存到数组

    我对 OpenCV 文档有点迷失 我想将 cvFindContours 返回的 CvSeq 保存到一个数组中 据我了解它将返回 CvContour 的 seq 但我找不到它包含的内容 我应该保存其中的哪些部分 稍后我可以迭代它并说调用 cv
  • 在openCV内部调用Gstreamer

    我需要在 openCV 代码中调用 Gstremaer 本质上是打开摄像机 当我查看源代码时 modules highgui src cap gstreamer cpp似乎是我正在寻找的文件 我用 Gstreamer 标志编译了 OpenC
  • 在Spyder(Python 3.6)中导入cv2时出现导入错误

    我已经在Windows操作系统中安装了opencv 3 0 0 我已运行该应用程序并已成功将其安装在C 驱动器并还复制了cv2 pyd文件输入C Python27 Lib site packages正如我在几个教程视频中看到的那样 在我的
  • OpenCV SVM 给出奇怪的预测结果

    我对 OpenCV 和支持向量机都很陌生 我想使用 SVM 训练具有两个标签的数据集 然后预测给定集合的标签 我当前的集合包含大约 600 行 具有相等的类分布 1 为 300 行 1 为 300 行 包含 34 列 这是我当前用于设置 O
  • opencv不失真图像有一个奇怪的圆圈

    我尝试使用 opencv 针孔模型来计算校准参数 然后使图像不失真 问题是 未失真的图像中有一个奇怪的圆圈 如下所示 代码 原始图像和结果图像是here https github com wennycooper A004 pinhole 任
  • 使用 Brew 安装 OpenCV 永远不会完成

    所以我尝试使用 Homebrew 安装 opencv 但它不起作用 我用了brew tap homebrew science进而brew install opencv发生的情况是 gt Installing opencv from home

随机推荐

  • 将yml文件导入conda,并连接到jupyter和pyCharm

    在GitHub上找了一篇文献 想复现一下 记录一下其中遇到的环境配置问题 我也不是很懂相关的操作 只是用来记录 如果我的操作有什么不必要或错误的地方 辛苦大家耐心指正 1 把yml文件安装到conda安装路径下的envs文件夹下 把cmd转
  • 加密算法(一)

    加密算法 参考 https blog csdn net qq 31878855 article details 69396791 加密算法分类 常见的加密算法可以分成两类 对称加密算法和非对称加密算法 对称加密算法 加密和解密用的是同一串密
  • 反卷积(Transposed Convolution, Fractionally Strided Convolution or Deconvolution)

    反卷积 Deconvolution 的概念第一次出现是Zeiler在2010年发表的论文 Deconvolutional networks 中 但是并没有指定反卷积这个名字 反卷积这个术语正式的使用是在其之后的工作中 Adaptive de
  • 记工作日常 - mysql 使用json格式 被dba拒绝执行

    首先 mysql版本在5 7以下是不支持json格式存储的 若使用json格式存储数据 在使用生成实体类的工具得到的文件字段类型为 object 场景 今天准备项目上线 在archery sql审核查询平台提交sql并进行审查 审查结果为
  • 微信小程序中的网络请求

    微信小程序中的网络请求 为了安全 微信小程序只能请求http类型接口 请求其他接口时必须将接口的域名添加到新人列表中 配置request合法域名 登录微信小程序管理后台添加链接描述 gt 开发 gt 开发设置 gt 服务器域名 gt 修改
  • Linux yum 命令介绍

    原文地址 http blog csdn net tianlesoftware article details 5092720 参考地址 http blog csdn net tianlesoftware article details 53
  • 小练习:三级菜单

    要求 1 运行程序输出第一级菜单 2 选择一级菜单某项 输出二级菜单 同理输出三级菜单 3 输入出错重新选择 4 进入每一层支持返回上一级和退出 5 入最后一层提醒 menu 001 011 111 1 1 1 211 2 2 2 311
  • GBase8s创建主键失败:[42000][-201] 发生语法错误 [00000]

    GBase8s创建主键失败 42000 201 发生语法错误 00000 问题描述 原因分析 解决方案 其他 参考链接 问题描述 场景描述 修改已存在表 添加主键失败 执行SQL ALTER TABLE 表名 ADD CONSTRAINT
  • 解决kali打开部分文件夹权限不够的问题

    其实非常简单 如图 右键 选择以root用户身份打开即可
  • GD32E23x的USART被断点打断后重新运行,会一直进入中断的问题

    GD32E23x的USART被断点打断后重新运行 会一直进入中断的问题 GD32E230K8单片机USART0连接一个从机芯片 该芯片每100ms发来一串16Bytes的数据 MCU中断接收 没有开启FIFO 只开启了RBNE 接收缓存非空
  • 关于STM32.. Error: L6218E: Undefined symbol xxxx(referred from xxxx.o).问题解决

    自己在编写有关利用CAN通讯 使用STM32控制电机旋转的代码中遇到了这一问题 错误应该是Can Send Msg这个函数在main中使用时没有定义 通过查找资料发现出现这一问题的主要原因有两个 1 未将头文件的路径导入 解决方法 将含有函
  • 人脸检测初级心得——分享一些比较易懂的经验与方法

    一 最重要的是一定一定 要谨慎一些 尤其是在安装配置深度学习环境时 尽量找一些比较好的教程跟着来 不要换教程 因为可能方法都不一样 导致环境安装失败 很头疼 二 我建议大家可以先不要着急 可以先看一些理论的知识 然后慢慢去实现 这样会理解的
  • 判断数组的方式(原型链,instanceOf)

    var arr 1 2 3 Array isArray arr true Object prototype toString call arr object Array arr constructor Array true arr inst
  • linux 提高文件读写速度 mmap,linux读写文件速度测试

    一 文件一次读入速度 linux下读文件这东西最后都是要通过系统调用sys read fd buf count 来实现的 所以如果要提高速度 就是最简单地调用sys read的封装 比如直接用read 或fread 下面是我在linux下的
  • 记一次javaMetaspace导致CPU200%的排查

    记一次javaMetaspace导致CPU200 的排查 1 场景 2 装arthas 3 分析代码 4 罪魁祸首 1 场景 insertMotionDataByWxCallBack方法并发多 其实也没多少 可能就3个 就导致CPU200
  • 校园网自动登录、断网重连

    校园网自动登录 断网重连 适用北航 其他学校可以照着模板自行修改 在ubuntu测试 windows应该也能用 需要下载chrome以及对应版本的chrome driver from selenium import webdriver fr
  • 数控加工插补功能指令

    1 G00 快速定位指令 格式 G00 XYZ 格式含义 G00 指令使刀具以点位控制方式从刀具当前点以最快速度运动到另一点 其轨 迹不一定是两点一线 有可能是一条折线 须知 1 刀具从上向下移动时 G00 XY Z 先定 XY 面 然后
  • vue.js -- 组件传值校验及单项数据流

    目录 组件间传值 静态传值 动态传值 params传值 传值校验 type校验 required校验 default校验 validator校验 单项数据流 总结 组件间传值 父子组件间传值 子组件通过props这个属性接受父组件传递过来的
  • 二十九、java版 SpringCloud分布式微服务云架构之Java 数据结构

    Java 数据结构 Java工具包提供了强大的数据结构 在Java中的数据结构主要包括以下几种接口和类 枚举 Enumeration 位集合 BitSet 向量 Vector 栈 Stack 字典 Dictionary 哈希表 Hashta
  • OpenCV 中的 remap 函数

    上一篇文章中提到 warpAffine 会分块处理 将坐标映射和插值系数分别存储下来 然后借助 remap 来实现最终的映射 而 remap 会根据映射关系取源像素并加权计算出目的像素值 其最核心的计算为 RemapVec 8u cv re