C++ 是否有一种简单的方法/算法来匹配2个二维点云?

C++ 是否有一种简单的方法/算法来匹配2个二维点云?,c++,matlab,image-processing,C++,Matlab,Image Processing,我想知道是否有一种简单的方法来匹配(注册)2个二维点云 假设我有一个由点表示的对象和一个由对象点和噪声(以一种无用点的方式产生的噪声)组成的杂乱的第二幅图像 基本上,对象可以进行二维旋转、平移和缩放 我知道有ICP算法,但我认为这不是一个好方法,因为噪音太大 我希望你明白我的意思。请询问是否(我肯定是)有什么不清楚的地方 欢呼> 从C++中,你可以用来做图像配准。它包括许多注册功能,可在存在噪音的情况下工作。对跟踪功能进行了分析。仿射一致性检查考虑平移、旋转和缩放。我不知道它是否对你有帮助,因

我想知道是否有一种简单的方法来匹配(注册)2个二维点云

假设我有一个由点表示的对象和一个由对象点和噪声(以一种无用点的方式产生的噪声)组成的杂乱的第二幅图像

基本上,对象可以进行二维旋转、平移和缩放

我知道有ICP算法,但我认为这不是一个好方法,因为噪音太大

我希望你明白我的意思。请询问是否(我肯定是)有什么不清楚的地方


欢呼>

从C++中,你可以用来做图像配准。它包括许多注册功能,可在存在噪音的情况下工作。

对跟踪功能进行了分析。仿射一致性检查考虑平移、旋转和缩放。我不知道它是否对你有帮助,因为你不能直接使用这个函数(计算矩形区域的仿射变换)。但是,也许您可以从文档中学习仿射变换的计算方法,并使其适应您的问题(点云而不是矩形区域)。

我相信您正在寻找类似David Lowe(尺度不变特征变换)的东西。另一种选择是(SIFT受专利保护)。OpenCV计算机库提供了一个

我将尝试使用距离几何体(http://en.wikipedia.org/wiki/Distance_geometry)为此

通过将每个点到特定半径内所有相邻点的距离相加,为每个点生成标量。虽然不是完美的,但这将是每个点的好鉴别器

然后将所有标量放在一个地图中,该地图允许一个点(p)通过其标量加/减一些增量来检索
M(s+delta)=p(例如K-D树)(http://en.wikipedia.org/wiki/Kd-tree)

将所有二维点的参考集放置在地图中

在另一组(测试)2D点上:
foreach测试缩放(特别是如果您对典型缩放值有很好的了解的话)
…按S缩放每个点
…重新计算测试点集的标量
……对于测试集中的每个点P(或可能是更快方法的样本)
……在某些增量内的参考标量映射中查找点
如果未找到映射,则丢弃P
发现每个P'点的其他值
……检查P的邻域,查看它们是否在某个增量内的参考地图中具有相应的标量(即参考点具有近似相同值的邻域)
......... 如果所有测试点在参考集中都有映射,则您已找到测试点P到参考点P'->的映射记录测试点到参考点的映射 ……如果未记录映射,则放弃缩放

请注意,这在几个不同的地方被简单地并行化

这是我多年前做的研究得出的结论。它缺乏精细的细节,但总体思路很清楚:在嘈杂的(测试)图中找到距离其最近邻居大致与参考集相同的点。有噪声的图形测量距离的允许误差要大于无噪声的图形

该算法适用于无噪声的图


编辑:对算法进行了改进,不需要查看不同的缩放比例。计算每个点的标量时,请改用相对距离度量。这将是变换不变的

让我首先确保我正确地解释了您的问题。您有两组2D点,其中一组包含与某个感兴趣的对象对应的所有“好”点,另一组包含在仿射变换下添加噪声点的点。对吧?

如果这是正确的,那么有一个相当可靠和有效的方法来拒绝噪声点和确定感兴趣点之间的转换。通常用于拒绝噪声点(“异常值”)的算法称为,用于确定转换的算法可以有多种形式,但最新的技术状态称为五点算法,可以找到——可以找到MATLAB实现

不幸的是,我不知道这两者结合的成熟实现;您可能需要自己做一些工作来实现RANSAC,并将其与五点算法集成

编辑:


事实上,OpenCV的实现对于您的任务来说是过度的(这意味着它可以工作,但需要比必要的更多的时间),但是已经准备好开箱即用。调用感兴趣的函数。

这里是查找平移和旋转的函数。对缩放、加权点和RANSAC的概括是直接的。我使用openCV库进行可视化和SVD。下面的功能结合了数据生成、单元测试和实际解决方案

 // rotation and translation in 2D from point correspondences
 void rigidTransform2D(const int N) {

// Algorithm: http://igl.ethz.ch/projects/ARAP/svd_rot.pdf

const bool debug = false;      // print more debug info
const bool add_noise = true; // add noise to imput and output
srand(time(NULL));           // randomize each time

/*********************************
 * Creat data with some noise
 **********************************/

// Simulated transformation
Point2f T(1.0f, -2.0f);
float a = 30.0; // [-180, 180], see atan2(y, x)
float noise_level = 0.1f;
cout<<"True parameters: rot = "<<a<<"deg., T = "<<T<<
        "; noise level = "<<noise_level<<endl;

// noise
vector<Point2f> noise_src(N), noise_dst(N);
for (int i=0; i<N; i++) {
    noise_src[i] = Point2f(randf(noise_level), randf(noise_level));
    noise_dst[i] = Point2f(randf(noise_level), randf(noise_level));
}

// create data with noise
vector<Point2f> src(N), dst(N);
float Rdata = 10.0f; // radius of data
float cosa = cos(a*DEG2RAD);
float sina = sin(a*DEG2RAD);
for (int i=0; i<N; i++) {

    // src
    float x1 = randf(Rdata);
    float y1 = randf(Rdata);
    src[i] = Point2f(x1,y1);
    if (add_noise)
        src[i] += noise_src[i];

    // dst
    float x2 = x1*cosa - y1*sina;
    float y2 = x1*sina + y1*cosa;
    dst[i] = Point2f(x2,y2) + T;
    if (add_noise)
        dst[i] += noise_dst[i];

    if (debug)
        cout<<i<<": "<<src[i]<<"---"<<dst[i]<<endl;
}

// Calculate data centroids
Scalar centroid_src = mean(src);
Scalar centroid_dst = mean(dst);
Point2f center_src(centroid_src[0], centroid_src[1]);
Point2f center_dst(centroid_dst[0], centroid_dst[1]);
if (debug)
    cout<<"Centers: "<<center_src<<", "<<center_dst<<endl;

/*********************************
 * Visualize data
 **********************************/

// Visualization
namedWindow("data", 1);
float w = 400, h = 400;
Mat Mdata(w, h, CV_8UC3); Mdata = Scalar(0);
Point2f center_img(w/2, h/2);

float scl = 0.4*min(w/Rdata, h/Rdata); // compensate for noise
scl/=sqrt(2); // compensate for rotation effect
Point2f dT = (center_src+center_dst)*0.5; // compensate for translation

for (int i=0; i<N; i++) {
    Point2f p1(scl*(src[i] - dT));
    Point2f p2(scl*(dst[i] - dT));
    // invert Y axis
    p1.y = -p1.y; p2.y = -p2.y;
    // add image center
    p1+=center_img; p2+=center_img;
    circle(Mdata, p1, 1, Scalar(0, 255, 0));
    circle(Mdata, p2, 1, Scalar(0, 0, 255));
    line(Mdata, p1, p2, Scalar(100, 100, 100));

}

/*********************************
 * Get 2D rotation and translation
 **********************************/

markTime();

// subtract centroids from data
for (int i=0; i<N; i++) {
    src[i] -= center_src;
    dst[i] -= center_dst;
}

// compute a covariance matrix
float Cxx = 0.0, Cxy = 0.0, Cyx = 0.0, Cyy = 0.0;
for (int i=0; i<N; i++) {
    Cxx += src[i].x*dst[i].x;
    Cxy += src[i].x*dst[i].y;
    Cyx += src[i].y*dst[i].x;
    Cyy += src[i].y*dst[i].y;
}
Mat Mcov = (Mat_<float>(2, 2)<<Cxx, Cxy, Cyx, Cyy);
if (debug)
    cout<<"Covariance Matrix "<<Mcov<<endl;

// SVD
cv::SVD svd;
svd = SVD(Mcov, SVD::FULL_UV);
if (debug) {
    cout<<"U = "<<svd.u<<endl;
    cout<<"W = "<<svd.w<<endl;
    cout<<"V transposed = "<<svd.vt<<endl;
}

// rotation = V*Ut
Mat V = svd.vt.t();
Mat Ut = svd.u.t();
float det_VUt = determinant(V*Ut);
Mat W = (Mat_<float>(2, 2)<<1.0, 0.0, 0.0, det_VUt);
float rot[4];
Mat R_est(2, 2, CV_32F, rot);
R_est = V*W*Ut;
if (debug)
    cout<<"Rotation matrix: "<<R_est<<endl;

float cos_est = rot[0];
float sin_est = rot[2];
float ang = atan2(sin_est, cos_est);

// translation = mean_dst - R*mean_src
Point2f center_srcRot = Point2f(
        cos_est*center_src.x - sin_est*center_src.y,
        sin_est*center_src.x + cos_est*center_src.y);
Point2f T_est = center_dst - center_srcRot;

// RMSE
double RMSE = 0.0;
for (int i=0; i<N; i++) {
    Point2f dst_est(
            cos_est*src[i].x - sin_est*src[i].y,
            sin_est*src[i].x + cos_est*src[i].y);
    RMSE += SQR(dst[i].x - dst_est.x) + SQR(dst[i].y - dst_est.y);
}
if (N>0)
    RMSE = sqrt(RMSE/N);

// Final estimate msg
cout<<"Estimate = "<<ang*RAD2DEG<<"deg., T = "<<T_est<<"; RMSE = "<<RMSE<<endl;

// show image
printTime(1);
imshow("data", Mdata);
waitKey(-1);

return;
} // rigidTransform2D()

// --------------------------- 3DOF

// calculates squared error from two point mapping; assumes rotation around Origin.
inline float sqErr_3Dof(Point2f p1, Point2f p2,
        float cos_alpha, float sin_alpha, Point2f T) {

    float x2_est = T.x + cos_alpha * p1.x - sin_alpha * p1.y;
    float y2_est = T.y + sin_alpha * p1.x + cos_alpha * p1.y;
    Point2f p2_est(x2_est, y2_est);
    Point2f dp = p2_est-p2;
    float sq_er = dp.dot(dp); // squared distance

    //cout<<dp<<endl;
    return sq_er;
}

// calculate RMSE for point-to-point metrics
float RMSE_3Dof(const vector<Point2f>& src, const vector<Point2f>& dst,
        const float* param, const bool* inliers, const Point2f center) {

    const bool all_inliers = (inliers==NULL); // handy when we run QUADRTATIC will all inliers
    unsigned int n = src.size();
    assert(n>0 && n==dst.size());

    float ang_rad = param[0];
    Point2f T(param[1], param[2]);
    float cos_alpha = cos(ang_rad);
    float sin_alpha = sin(ang_rad);

    double RMSE = 0.0;
    int ninliers = 0;
    for (unsigned int i=0; i<n; i++) {
        if (all_inliers || inliers[i]) {
            RMSE += sqErr_3Dof(src[i]-center, dst[i]-center, cos_alpha, sin_alpha, T);
            ninliers++;
        }
    }

    //cout<<"RMSE = "<<RMSE<<endl;
    if (ninliers>0)
        return sqrt(RMSE/ninliers);
    else
        return LARGE_NUMBER;
}

// Sets inliers and returns their count
inline int setInliers3Dof(const vector<Point2f>& src, const vector <Point2f>& dst,
        bool* inliers,
        const float* param,
        const float max_er,
        const Point2f center) {

    float ang_rad = param[0];
    Point2f T(param[1], param[2]);

    // set inliers
    unsigned int ninliers = 0;
    unsigned int n = src.size();
    assert(n>0 && n==dst.size());

    float cos_ang = cos(ang_rad);
    float sin_ang = sin(ang_rad);
    float max_sqErr = max_er*max_er; // comparing squared values

    if (inliers==NULL) {

        // just get the number of inliers (e.g. after QUADRATIC fit only)
        for (unsigned int i=0; i<n; i++) {

            float sqErr = sqErr_3Dof(src[i]-center, dst[i]-center, cos_ang, sin_ang, T);
            if ( sqErr < max_sqErr)
                ninliers++;
        }
    } else  {

        // get the number of inliers and set them (e.g. for RANSAC)
        for (unsigned int i=0; i<n; i++) {

            float sqErr = sqErr_3Dof(src[i]-center, dst[i]-center, cos_ang, sin_ang, T);
            if ( sqErr < max_sqErr) {
                inliers[i] = 1;
                ninliers++;
            } else {
                inliers[i] = 0;
            }
        }
    }

    return ninliers;
}

// fits 3DOF (rotation and translation in 2D) with least squares.
float fit3DofQUADRATICold(const vector<Point2f>& src, const vector<Point2f>& dst,
        float* param, const bool* inliers, const Point2f center) {

    const bool all_inliers = (inliers==NULL); // handy when we run QUADRTATIC will all inliers
    unsigned int n = src.size();
    assert(dst.size() == n);

    // count inliers
    int ninliers;
    if (all_inliers) {
        ninliers = n;
    } else {
        ninliers = 0;
        for (unsigned int i=0; i<n; i++){
            if (inliers[i])
                ninliers++;
        }
    }

    // under-dermined system
    if (ninliers<2) {
        //      param[0] = 0.0f; // ?
        //      param[1] = 0.0f;
        //      param[2] = 0.0f;
        return LARGE_NUMBER;
    }

    /*
     * x1*cosx(a)-y1*sin(a) + Tx = X1
     * x1*sin(a)+y1*cos(a) + Ty = Y1
     *
     * approximation for small angle a (radians) sin(a)=a, cos(a)=1;
     *
     * x1*1 - y1*a + Tx = X1
     * x1*a + y1*1 + Ty = Y1
     *
     * in matrix form M1*h=M2
     *
     *  2n x 4       4 x 1   2n x 1
     *
     * -y1 1 0 x1  *   a   =  X1
     *  x1 0 1 y1      Tx     Y1
     *                 Ty
     *                 1=Z
     *  ----------------------------
     *  src1         res      src2
     */

    //  4 x 1
    float res_ar[4]; // alpha, Tx, Ty, 1
    Mat res(4, 1, CV_32F, res_ar); // 4 x 1

    // 2n x 4
    Mat src1(2*ninliers, 4, CV_32F); // 2n x 4

    // 2n x 1
    Mat src2(2*ninliers, 1, CV_32F); // 2n x 1: [X1, Y1, X2, Y2, X3, Y3]'

    for (unsigned int i=0, row_cnt = 0; i<n; i++) {

        // use inliers only
        if (all_inliers || inliers[i]) {

            float x = src[i].x - center.x;
            float y = src[i].y - center.y;

            // first row

            // src1
            float* rowPtr = src1.ptr<float>(row_cnt);
            rowPtr[0] = -y;
            rowPtr[1] = 1.0f;
            rowPtr[2] = 0.0f;
            rowPtr[3] = x;

            // src2
            src2.at<float> (0, row_cnt) = dst[i].x - center.x;

            // second row
            row_cnt++;

            // src1
            rowPtr = src1.ptr<float>(row_cnt);
            rowPtr[0] = x;
            rowPtr[1] = 0.0f;
            rowPtr[2] = 1.0f;
            rowPtr[3] = y;

            // src2
            src2.at<float> (0, row_cnt) = dst[i].y - center.y;
        }
    }

    cv::solve(src1, src2, res, DECOMP_SVD);

    // estimators
    float alpha_est;
    Point2f T_est;

    // original
    alpha_est = res.at<float>(0, 0);
    T_est = Point2f(res.at<float>(1, 0), res.at<float>(2, 0));

    float Z = res.at<float>(3, 0);
    if (abs(Z-1.0) > 0.1) {
        //cout<<"Bad Z in fit3DOF(), Z should be close to 1.0 = "<<Z<<endl;
        //return LARGE_NUMBER;
    }
    param[0] = alpha_est; // rad
    param[1] = T_est.x;
    param[2] = T_est.y;

    // calculate RMSE
    float RMSE = RMSE_3Dof(src, dst, param, inliers, center);
    return RMSE;
} // fit3DofQUADRATICOLd()

// fits 3DOF (rotation and translation in 2D) with least squares.
float fit3DofQUADRATIC(const vector<Point2f>& src_, const vector<Point2f>& dst_,
        float* param, const bool* inliers, const Point2f center) {

    const bool debug = false;                   // print more debug info
    const bool all_inliers = (inliers==NULL);   // handy when we run QUADRTATIC will all inliers
    assert(dst_.size() == src_.size());
    int N = src_.size();

    // collect inliers
    vector<Point2f> src, dst;
    int ninliers;
    if (all_inliers) {
        ninliers = N;
        src = src_; // copy constructor
        dst = dst_;
    } else {
        ninliers = 0;
        for (int i=0; i<N; i++){
            if (inliers[i]) {
                ninliers++;
                src.push_back(src_[i]);
                dst.push_back(dst_[i]);
            }
        }
    }
    if (ninliers<2) {
        param[0] = 0.0f; // default return when there is not enough points
        param[1] = 0.0f;
        param[2] = 0.0f;
        return LARGE_NUMBER;
    }

    /* Algorithm: Least-Square Rigid Motion Using SVD by Olga Sorkine
     * http://igl.ethz.ch/projects/ARAP/svd_rot.pdf
     *
     * Subtract centroids, calculate SVD(cov),
     * R = V[1, det(VU')]'U', T = mean_q-R*mean_p
     */

    // Calculate data centroids
    Scalar centroid_src = mean(src);
    Scalar centroid_dst = mean(dst);
    Point2f center_src(centroid_src[0], centroid_src[1]);
    Point2f center_dst(centroid_dst[0], centroid_dst[1]);
    if (debug)
        cout<<"Centers: "<<center_src<<", "<<center_dst<<endl;

    // subtract centroids from data
    for (int i=0; i<ninliers; i++) {
        src[i] -= center_src;
        dst[i] -= center_dst;
    }

    // compute a covariance matrix
    float Cxx = 0.0, Cxy = 0.0, Cyx = 0.0, Cyy = 0.0;
    for (int i=0; i<ninliers; i++) {
        Cxx += src[i].x*dst[i].x;
        Cxy += src[i].x*dst[i].y;
        Cyx += src[i].y*dst[i].x;
        Cyy += src[i].y*dst[i].y;
    }
    Mat Mcov = (Mat_<float>(2, 2)<<Cxx, Cxy, Cyx, Cyy);
    Mcov /= (ninliers-1);
    if (debug)
        cout<<"Covariance-like Matrix "<<Mcov<<endl;

    // SVD of covariance
    cv::SVD svd;
    svd = SVD(Mcov, SVD::FULL_UV);
    if (debug) {
        cout<<"U = "<<svd.u<<endl;
        cout<<"W = "<<svd.w<<endl;
        cout<<"V transposed = "<<svd.vt<<endl;
    }

    // rotation (V*Ut)
    Mat V = svd.vt.t();
    Mat Ut = svd.u.t();
    float det_VUt = determinant(V*Ut);
    Mat W = (Mat_<float>(2, 2)<<1.0, 0.0, 0.0, det_VUt);
    float rot[4];
    Mat R_est(2, 2, CV_32F, rot);
    R_est = V*W*Ut;
    if (debug)
        cout<<"Rotation matrix: "<<R_est<<endl;

    float cos_est = rot[0];
    float sin_est = rot[2];
    float ang = atan2(sin_est, cos_est);

    // translation (mean_dst - R*mean_src)
    Point2f center_srcRot = Point2f(
            cos_est*center_src.x - sin_est*center_src.y,
            sin_est*center_src.x + cos_est*center_src.y);
    Point2f T_est = center_dst - center_srcRot;

    // Final estimate msg
    if (debug)
        cout<<"Estimate = "<<ang*RAD2DEG<<"deg., T = "<<T_est<<endl;

    param[0] = ang; // rad
    param[1] = T_est.x;
    param[2] = T_est.y;

    // calculate RMSE
    float RMSE = RMSE_3Dof(src_, dst_, param, inliers, center);
    return RMSE;
} // fit3DofQUADRATIC()

// RANSAC fit in 3DOF: 1D rot and 2D translation (maximizes the number of inliers)
// NOTE: no data normalization is currently performed
float fit3DofRANSAC(const vector<Point2f>& src, const vector<Point2f>& dst,
        float* best_param,  bool* inliers,
        const Point2f center ,
        const float inlierMaxEr,
        const int niter) {

    const int ITERATION_TO_SETTLE = 2;    // iterations to settle inliers and param
    const float INLIERS_RATIO_OK = 0.95f;  // stopping criterion

    // size of data vector
    unsigned int N = src.size();
    assert(N==dst.size());

    // unrealistic case
    if(N<2) {
        best_param[0] = 0.0f; // ?
        best_param[1] = 0.0f;
        best_param[2] = 0.0f;
        return LARGE_NUMBER;
    }

    unsigned int ninliers;                 // current number of inliers
    unsigned int best_ninliers = 0;     // number of inliers
    float best_rmse = LARGE_NUMBER;         // error
    float cur_rmse;                         // current distance error
    float param[3];                         // rad, Tx, Ty
    vector <Point2f> src_2pt(2), dst_2pt(2);// min set of 2 points (1 correspondence generates 2 equations)
    srand (time(NULL));

    // iterations
    for (int iter = 0; iter<niter; iter++) {

#ifdef DEBUG_RANSAC
        cout<<"iteration "<<iter<<": ";
#endif

        // 1. Select a random set of 2 points (not obligatory inliers but valid)
        int i1, i2;
        i1 = rand() % N; // [0, N[
        i2 = i1;
        while (i2==i1) {
            i2 = rand() % N;
        }
        src_2pt[0] = src[i1]; // corresponding points
        src_2pt[1] = src[i2];
        dst_2pt[0] = dst[i1];
        dst_2pt[1] = dst[i2];
        bool two_inliers[] = {true, true};

        // 2. Quadratic fit for 2 points
        cur_rmse = fit3DofQUADRATIC(src_2pt, dst_2pt, param, two_inliers, center);

        // 3. Recalculate to settle params and inliers using a larger set
        for (int iter2=0; iter2<ITERATION_TO_SETTLE; iter2++) {
            ninliers = setInliers3Dof(src, dst, inliers, param, inlierMaxEr, center);   // changes inliers
            cur_rmse = fit3DofQUADRATIC(src, dst, param, inliers, center);              // changes cur_param
        }

        // potential ill-condition or large error
        if (ninliers<2) {
#ifdef DEBUG_RANSAC
            cout<<" !!! less than 2 inliers "<<endl;
#endif
            continue;
        } else {
#ifdef DEBUG_RANSAC
            cout<<" "<<ninliers<<" inliers; ";
#endif
        }


#ifdef DEBUG_RANSAC
        cout<<"; recalculate: RMSE = "<<cur_rmse<<", "<<ninliers <<" inliers";
#endif


        // 4. found a better solution?
        if (ninliers > best_ninliers) {
            best_ninliers = ninliers;
            best_param[0] = param[0];
            best_param[1] = param[1];
            best_param[2] = param[2];
            best_rmse = cur_rmse;


#ifdef DEBUG_RANSAC
            cout<<" --- Solution improved: "<<
                    best_param[0]<<", "<<best_param[1]<<", "<<param[2]<<endl;
#endif
            // exit condition
            float inlier_ratio = (float)best_ninliers/N;
            if (inlier_ratio > INLIERS_RATIO_OK) {
#ifdef DEBUG_RANSAC
                cout<<"Breaking early after "<< iter+1<<
                        " iterations; inlier ratio = "<<inlier_ratio<<endl;
#endif
                break;
            }
        } else {
#ifdef DEBUG_RANSAC
            cout<<endl;
#endif
        }


    } // iterations

    // 5. recreate inliers for the best parameters
    ninliers = setInliers3Dof(src, dst, inliers, best_param, inlierMaxEr, center);

    return best_rmse;
} // fit3DofRANSAC()
//点对应的二维旋转和平移
void rigidTransform2D(常量int N){
//算法:http://igl.ethz.ch/projects/ARAP/svd_rot.pdf
const bool debug=false;//打印更多调试信息
const bool add_noise=true;//将噪声添加到输入和输出
srand(time(NULL));//每次随机
/*********************************
*用一些噪声创建数据
**********************************/
//模拟变换
点2f T(1.0f,-2.0f);
浮点数a=30.0;//[-180,180],见表2(y,x)
浮子噪声水平=0.1f;

cout你想要Denton-Beveridge点匹配算法。源代码在下面链接的页面底部,还有一篇文章解释了该算法,以及为什么Ransac在这个问题上是一个不好的选择


你想匹配的点是什么?只是位置(x,y)?听起来真的很难。光是平移就够了,噪音太大了,但是旋转甚至缩放?这基本上就是