欢迎访问我的博客首页。


ICP 问题之 SVD

  • 1. SVD 求 ICP
    • 1.1 构造最小二乘问题
    • 1.2 求解最小二乘问题
    • 1.3 求位姿
  • 2. 例子
    • 2.1 代码实现
    • 2.2 实例分析
    • 2.3 使用 Eigen 库
  • 3. 参考

  ICP 即 Iterative Closest Point,迭代最近点。已知三维点在两个坐标系中的坐标,求这两个坐标系的变换关系,称为 ICP 问题。

1. SVD 求 ICP


1.1 构造最小二乘问题


  假设从空间坐标系 O1O_1O1​ 到空间坐标系 O2O_2O2​ 的变换是 (R,t⃗)(R, \vec t \,)(R,t),p1ip_1^ip1i​ 是空间点在坐标系 O1O_1O1​ 中的坐标,p2ip_2^ip2i​ 是空间点在坐标系 O2O_2O2​ 中的坐标。

图 1 ICP 问题

  一对匹配的空间点经过位姿变换的误差是

ei=p2i−(R⋅p1i+t⃗)(1)e_i = p_2^i - (R \cdot p_1^i + \vec t \,) \tag{1} ei​=p2i​−(R⋅p1i​+t)(1)

使用 n 对空间点的变换误差构造最小二乘问题

min⁡R,t⃗J=12∑i=1n∣∣p2i−(R⋅p1i+t⃗)∣∣22(2)\min_{R, \vec t} J = \frac{1}{2} \sum_{i=1}^n || p_2^i - (R \cdot p_1^i + \vec t \,) ||_2^2 \tag{2} R,tmin​J=21​i=1∑n​∣∣p2i​−(R⋅p1i​+t)∣∣22​(2)

  基于 SVD 的 ICP 算法就是通过求解上面的最小二乘问题得到位姿变换 (R,t⃗)(R, \vec t \,)(R,t)。求和的每一项是一个二范数的平方,表示二范数的下标可以省略。

1.2 求解最小二乘问题


  下面求解公式 (2)。首先定义质心坐标

{p1=1n∑i=1np1ip2=1n∑i=1np2i\left\{\begin{aligned} p_1 &= \frac{1}{n} \sum_{i=1}^n p_1^i \\ p_2 &= \frac{1}{n} \sum_{i=1}^n p_2^i \end{aligned}\right. ⎩⎨⎧​p1​p2​​=n1​i=1∑n​p1i​=n1​i=1∑n​p2i​​

注意,空间点坐标与质心坐标的区别是有无上标。那么

12∑i=1n∣∣p2i−(R⋅p1i+t⃗)∣∣2=12∑i=1n∣∣p2i−R⋅p1i−t⃗∣∣2=12∑i=1n∣∣p2i−R⋅p1i−t⃗−p2+R⋅p1+p2−R⋅p1)∣∣2=12∑i=1n∣∣[p2i−p2−R⋅(p1i−p1)]+(p2−R⋅p1−t⃗)∣∣2=12∑i=1n{∣∣p2i−p2−R⋅(p1i−p1)∣∣2+∣∣p2−R⋅p1−t⃗∣∣2+2[p2i−p2−R⋅(p1i−p1)]T(p2−R⋅p1−t⃗)}=12∑i=1n[∣∣p2i−p2−R⋅(p1i−p1)∣∣2+∣∣p2−R⋅p1−t⃗∣∣2].(3)\begin{aligned} \frac{1}{2} \sum_{i=1}^n || p_2^i - (R \cdot p_1^i + \vec t) ||^2 & = \frac{1}{2} \sum_{i=1}^n || p_2^i - R \cdot p_1^i - \vec t||^2 \\ & = \frac{1}{2} \sum_{i=1}^n || p_2^i - R \cdot p_1^i - \vec t - p_2 + R \cdot p_1 + p_2 - R \cdot p_1) ||^2 \\ & = \frac{1}{2} \sum_{i=1}^n || [p_2^i - p_2 - R \cdot ( p_1^i - p_1)] + (p_2 - R \cdot p_1 - \vec t) ||^2 \\ & = \frac{1}{2} \sum_{i=1}^n \{|| p_2^i - p_2 - R \cdot ( p_1^i - p_1) ||^2 + ||p_2 - R \cdot p_1 - \vec t ||^2 \\ &+ 2[p_2^i - p_2 - R \cdot (p_1^i - p_1)]^T (p_2 - R \cdot p_1 - \vec t)\} \\ & = \frac{1}{2} \sum_{i=1}^n [|| p_2^i - p_2 - R \cdot ( p_1^i - p_1) ||^2 + ||p_2 - R \cdot p_1 - \vec t ||^2]. \end{aligned} \tag{3} 21​i=1∑n​∣∣p2i​−(R⋅p1i​+t)∣∣2​=21​i=1∑n​∣∣p2i​−R⋅p1i​−t∣∣2=21​i=1∑n​∣∣p2i​−R⋅p1i​−t−p2​+R⋅p1​+p2​−R⋅p1​)∣∣2=21​i=1∑n​∣∣[p2i​−p2​−R⋅(p1i​−p1​)]+(p2​−R⋅p1​−t)∣∣2=21​i=1∑n​{∣∣p2i​−p2​−R⋅(p1i​−p1​)∣∣2+∣∣p2​−R⋅p1​−t∣∣2+2[p2i​−p2​−R⋅(p1i​−p1​)]T(p2​−R⋅p1​−t)}=21​i=1∑n​[∣∣p2i​−p2​−R⋅(p1i​−p1​)∣∣2+∣∣p2​−R⋅p1​−t∣∣2].​(3)

公式 (3) 的推导用到了二范数。交叉项 2[p2i−p2−R⋅(p1i−p1)]T(p2−R⋅p1−t⃗)2[p_2^i - p_2 - R \cdot (p_1^i - p_1)]^T (p_2 - R \cdot p_1 - \vec t)2[p2i​−p2​−R⋅(p1i​−p1​)]T(p2​−R⋅p1​−t) 累加后为 0,因此被舍弃。

  公式 (3) 可以看成两项相加的形式:第 1 项是第一个二范数累加,这一项只与旋转矩阵 RRR 有关;第 2 项是第二个二范数累加,这一项与 RRR 和平移向量 t⃗\vec tt 都有关。因此可以令第 1 项为 0 求得 RRR,再令第 2 项为 0 求得 t⃗\vec tt。

1.3 求位姿


  假设 p1ip_1^ip1i​ 和 p2ip_2^ip2i​ 的去质心坐标分别是

{q1i=p1i−p1q2i=p2i−p2\left\{\begin{aligned} q_1^i &= p_1^i - p_1 \\ q_2^i &= p_2^i - p_2 \end{aligned}\right. {q1i​q2i​​=p1i​−p1​=p2i​−p2​​

那么,令公式 (3) 的第 1 项为 0,就是

12∑i=1n∣∣q2i−R⋅q1i∣∣2=0\frac{1}{2} \sum_{i=1}^n || q_2^i - R \cdot q_1^i||^2 = 0 21​i=1∑n​∣∣q2i​−R⋅q1i​∣∣2=0

于是,求旋转矩阵就是求最小二乘问题

R∗=argmin⁡R12∑i=1n∣∣q2i−R⋅q1i∣∣2R^* = arg \min_{R} \frac{1}{2} \sum_{i=1}^n ||q_2^i - R \cdot q_1^i||^2 R∗=argRmin​21​i=1∑n​∣∣q2i​−R⋅q1i​∣∣2

  求解 R∗R^*R∗ 的推理比较复杂,这里只给出计算方法:

{W=∑i=1n(q2i⋅q1iT)W=UΣVTR∗=UVT\left\{\begin{aligned} W &= \sum_{i=1}^{n} (q_2^i \cdot {q_1^i}^T) \\ W &= U \Sigma V^T \\ R^* &= U V^T \end{aligned}\right. ⎩⎨⎧​WWR∗​=i=1∑n​(q2i​⋅q1i​T)=UΣVT=UVT​

即,对求得的 W 进行 SVD 分解,得到 U 和 V,进而得到 R∗R^*R∗。然后令公式 (3) 的第二项为 0 即可求得平移向量

t∗=p2−R∗⋅p1t^* = p_2 - R^* \cdot p_1 t∗=p2​−R∗⋅p1​

2. 例子


2.1 代码实现


  下面是 ORB_SLAM2 的 EPnP 算法中,使用 SVD 求解 ICP 的代码:

// SVD 求解 ICP 问题:世界坐标系到相机坐标系的变换。
void PnPsolver::estimate_R_and_t(double R[3][3], double t[3]) {// 1. 求质心坐标 pc0、pw0。double pc0[3], pw0[3];pc0[0] = pc0[1] = pc0[2] = 0.0;pw0[0] = pw0[1] = pw0[2] = 0.0;for (int i = 0; i < number_of_correspondences; i++) {const double *pc = pcs + 3 * i;const double *pw = pws + 3 * i;for (int j = 0; j < 3; j++) {pc0[j] += pc[j];pw0[j] += pw[j];}}for (int j = 0; j < 3; j++) {pc0[j] /= number_of_correspondences;pw0[j] /= number_of_correspondences;}// 2. W = pc x pw^T.double abt[3 * 3];CvMat ABt = cvMat(3, 3, CV_64F, abt);cvSetZero(&ABt);for (int i = 0; i < number_of_correspondences; i++) {double *pc = pcs + 3 * i;double *pw = pws + 3 * i;for (int j = 0; j < 3; j++) {abt[3 * j + 0] += (pc[j] - pc0[j]) * (pw[0] - pw0[0]);abt[3 * j + 1] += (pc[j] - pc0[j]) * (pw[1] - pw0[1]);abt[3 * j + 2] += (pc[j] - pc0[j]) * (pw[2] - pw0[2]);}}// 3. ABt = ABt_U x ABt_D x ABt_V^T.double abt_d[3], abt_u[3 * 3], abt_v[3 * 3];CvMat ABt_D = cvMat(3, 1, CV_64F, abt_d);CvMat ABt_U = cvMat(3, 3, CV_64F, abt_u);CvMat ABt_V = cvMat(3, 3, CV_64F, abt_v);cvSVD(&ABt, &ABt_D, &ABt_U, &ABt_V, CV_SVD_MODIFY_A);// R = ABt_U x ABt_V^T.for (int i = 0; i < 3; i++)for (int j = 0; j < 3; j++)R[i][j] = dot(abt_u + 3 * i, abt_v + 3 * j);// 旋转矩阵的行列式要大于 0。const double det =R[0][0] * R[1][1] * R[2][2] + R[0][1] * R[1][2] * R[2][0] + R[0][2] * R[1][0] * R[2][1] -R[0][2] * R[1][1] * R[2][0] - R[0][1] * R[1][0] * R[2][2] - R[0][0] * R[1][2] * R[2][1];if (det < 0) {R[2][0] = -R[2][0];R[2][1] = -R[2][1];R[2][2] = -R[2][2];}// t = pc0 - R x pw0.t[0] = pc0[0] - dot(R[0], pw0);t[1] = pc0[1] - dot(R[1], pw0);t[2] = pc0[2] - dot(R[2], pw0);
}

2.2 实例分析


  下面我们具体分析一个例子。假设从坐标系 O1O_1O1​ 到坐标系 O2O_2O2​ 的位姿变换是

R=[010−100001]t⃗=[0−10]R = \left[ \begin{matrix} 0 & 1 & 0 \\ -1 & 0 & 0 \\ 0 & 0 & 1 \end{matrix} \right] \quad \quad \vec t = \left[ \begin{matrix} 0 \\ -1 \\ 0 \end{matrix} \right] R=⎣⎡​0−10​100​001​⎦⎤​t=⎣⎡​0−10​⎦⎤​

  1. 空间点在变换前后的坐标系中的坐标:
O1O_1O1​ O1O_1O1​
p11=(−4,2,1)Tp_1^1=(-4,2,1)^Tp11​=(−4,2,1)T p21=(2,3,1)Tp_2^1=(2,3,1)^Tp21​=(2,3,1)T
p12=(1,2,3)Tp_1^2=(1,2,3)^Tp12​=(1,2,3)T p22=(2,−2,3)Tp_2^2=(2,-2,3)^Tp22​=(2,−2,3)T
p13=(1,3,2)Tp_1^3=(1,3,2)^Tp13​=(1,3,2)T p23=(3,−2,2)Tp_2^3=(3,-2,2)^Tp23​=(3,−2,2)T
p14=(2,1,1)Tp_1^4=(2,1,1)^Tp14​=(2,1,1)T p24=(1,−3,1)Tp_2^4=(1, -3, 1)^Tp24​=(1,−3,1)T
p15=(−1,4,2)Tp_1^5=(-1,4,2)^Tp15​=(−1,4,2)T p25=(4,0,2)Tp_2^5=(4, 0, 2)^Tp25​=(4,0,2)T
p16=(7,0,3)Tp_1^6=(7,0,3)^Tp16​=(7,0,3)T p26=(0,−8,3)Tp_2^6=(0, -8, 3)^Tp26​=(0,−8,3)T
  1. 空间点的质心坐标:
O1O_1O1​ O2O_2O2​
p1=(1,2,2)Tp_1=(1,2,2)^Tp1​=(1,2,2)T p2=(2,−2,2)Tp_2=(2,-2,2)^Tp2​=(2,−2,2)T
  1. 空间点的去质心坐标:
O1O_1O1​ O2O_2O2​
q11=(−5,0,−1)Tq_1^1=(-5,0,-1)^Tq11​=(−5,0,−1)T q21=(0,5,−1)Tq_2^1=(0,5,-1)^Tq21​=(0,5,−1)T
q12=(0,0,1)Tq_1^2=(0,0,1)^Tq12​=(0,0,1)T q22=(0,0,1)Tq_2^2=(0,0,1)^Tq22​=(0,0,1)T
q13=(0,1,0)Tq_1^3=(0,1,0)^Tq13​=(0,1,0)T q23=(1,0,0)Tq_2^3=(1,0,0)^Tq23​=(1,0,0)T
q14=(1,−1,−1)Tq_1^4=(1,-1,-1)^Tq14​=(1,−1,−1)T q24=(−1,−1,−1)Tq_2^4=(-1,-1,-1)^Tq24​=(−1,−1,−1)T
q15=(−2,2,0)Tq_1^5=(-2,2,0)^Tq15​=(−2,2,0)T q25=(2,2,0)Tq_2^5=(2,2,0)^Tq25​=(2,2,0)T
q16=(6,−2,1)Tq_1^6=(6,-2,1)^Tq16​=(6,−2,1)T q26=(−2,−6,1)Tq_2^6=(-2,-6,1)^Tq26​=(−2,−6,1)T
  1. 矩阵 WWW:

W=∑i=16q2i⋅q1iT=[−1710−1−6617−1010−14]W = \sum_{i=1}^6 q_2^i \cdot {q_1^i}^T = \left[ \begin{matrix} -17 & 10 & -1 \\ -66 & 17 & -10 \\ 10 & -1 & 4 \end{matrix} \right] W=i=1∑6​q2i​⋅q1i​T=⎣⎡​−17−6610​1017−1​−1−104​⎦⎤​

  1. 矩阵 WWW 奇异值分解:

W=[−0.26310.8835−0.3876−0.9540−0.17840.24100.14380.43320.8898]⋅[72.19470006.07780001.7275]⋅[0.9540−0.26310.14380.17840.88350.4332−0.2410−0.38760.8898]TW = \left[ \begin{matrix} -0.2631 & 0.8835 & -0.3876 \\ -0.9540 & -0.1784 & 0.2410 \\ 0.1438 & 0.4332 & 0.8898 \end{matrix} \right] \cdot \left[ \begin{matrix} 72.1947 & 0 & 0 \\ 0 & 6.0778 & 0 \\ 0 & 0& 1.7275 \end{matrix} \right] \cdot \left[ \begin{matrix} 0.9540 & -0.2631 & 0.1438 \\ 0.1784 & 0.8835 & 0.4332 \\ -0.2410 & -0.3876 & 0.8898 \end{matrix} \right] ^T W=⎣⎡​−0.2631−0.95400.1438​0.8835−0.17840.4332​−0.38760.24100.8898​⎦⎤​⋅⎣⎡​72.194700​06.07780​001.7275​⎦⎤​⋅⎣⎡​0.95400.1784−0.2410​−0.26310.8835−0.3876​0.14380.43320.8898​⎦⎤​T

  1. 旋转矩阵 RRR:

R=[−0.26310.88350.3876−0.9540−0.1784−0.24100.14380.4332−0.8898]⋅[0.9540−0.26310.14380.17840.88350.43320.24100.3876−0.8898]T=[010−100001]⋅R= \left[ \begin{matrix} -0.2631 & 0.8835 & 0.3876 \\ -0.9540 & -0.1784& -0.2410 \\ 0.1438 & 0.4332& -0.8898 \end{matrix} \right] \cdot \left[ \begin{matrix} 0.9540 & -0.2631 & 0.1438 \\ 0.1784 & 0.8835 & 0.4332 \\ 0.2410 & 0.3876& -0.8898 \end{matrix} \right] ^T = \left[ \begin{matrix} 0 & 1 & 0 \\ -1 & 0 & 0 \\ 0 & 0& 1 \end{matrix} \right] \cdot R=⎣⎡​−0.2631−0.95400.1438​0.8835−0.17840.4332​0.3876−0.2410−0.8898​⎦⎤​⋅⎣⎡​0.95400.17840.2410​−0.26310.88350.3876​0.14380.4332−0.8898​⎦⎤​T=⎣⎡​0−10​100​001​⎦⎤​⋅

  1. 平移向量 t⃗\vec tt:

t⃗=p−R⋅p′=(0,−1,0)T\vec t = p - R \cdot p' = (0,-1,0)^Tt=p−R⋅p′=(0,−1,0)T

  上面就是用三维点在两个坐标系中的坐标,通过奇异值分解,求两个坐标系变换关系的过程。

2.3 使用 Eigen 库


  Eigen 是一个开源的C++线性代数库,这里使用它帮助我们进行矩阵运算。

#include <iostream>
#include <iomanip>
#include <opencv2/opencv.hpp>
#include <Eigen/SVD>using namespace std;
using namespace cv;void pose_estimation_3d3d(const vector<Point3f>& pts1,const vector<Point3f>& pts2) {// 求质心坐标Point3f p1, p2;int N = pts1.size();for(int i=0; i<N; i++) {p1 += pts1[i];p2 += pts2[i];}p1 = Point3f(Vec3f(p1) / N);p2 = Point3f(Vec3f(p2) / N);// 求去质心坐标vector<Point3f> q1(N), q2(N);for(int i=0; i<N; i++) {q1[i] = pts1[i] - p1;q2[i] = pts2[i] - p2;}// 求和: W = E(q1 * q2^T)Eigen::Matrix3d W = Eigen::Matrix3d::Zero();for(int i=0; i<N; i++) {W += Eigen::Vector3d(q1[i].x, q1[i].y, q1[i].z) *Eigen::Vector3d(q2[i].x, q2[i].y, q2[i].z).transpose();}cout << "W=" << endl << W << endl << endl;// 对W进行SVD分解Eigen::JacobiSVD<Eigen::Matrix3d> svd(W, Eigen::ComputeFullU|Eigen::ComputeFullV);Eigen::Matrix3d U = svd.matrixU();Eigen::Matrix3d V = svd.matrixV();cout << "U=" << endl << U << endl << endl;cout << "V=" << endl << V << endl << endl;// 求REigen::Matrix3d R = U * (V.transpose());cout << "R=" << endl << fixed << setprecision(2) << R << endl << endl;// 求tEigen::Vector3d t = Eigen::Vector3d(p1.x, p1.y, p1.z) - R * Eigen::Vector3d(p2.x, p2.y, p2.z);cout << "t=" << endl << t << endl;
}int main (int argc, char** argv) {vector<Point3f> pts1, pts2;pts1.push_back(Point3f(2, 3, 1));pts1.push_back(Point3f(2, -2, 3));pts1.push_back(Point3f(3, -2, 2));pts1.push_back(Point3f(1, -3, 1));pts1.push_back(Point3f(4, 0, 2));pts1.push_back(Point3f(0, -8, 3));pts2.push_back(Point3f(-4, 2, 1));pts2.push_back(Point3f(1, 2, 3));pts2.push_back(Point3f(1, 3, 2));pts2.push_back(Point3f(2, 1, 1));pts2.push_back(Point3f(-1, 4, 2 ));pts2.push_back(Point3f(7, 0, 3));pose_estimation_3d3d(pts1, pts2);
}

  如果使用 CMake 编译,可以参考这个 CMakeLists.txt。

cmake_minimum_required(VERSION 2.8)
project(SVD)find_package(OpenCV 3.1 REQUIRED)include_directories( ${OpenCV_INCLUDE_DIRS} "/usr/include/eigen3/"
)add_executable(svd svd.cpp)
target_link_libraries(svd ${OpenCV_LIBS}
)

3. 参考


  1. 奇异值分解求旋转矩阵,知乎专栏。

ICP 问题之 SVD相关推荐

  1. 六、3D-3D ICP问题线性SVD解法与非线性BA解法

    ICP(Iterative Closest Point)求解点云数据对的变换矩阵 误差模型 线性解SVD,非线性解BA #include <iostream> #include <o ...

  2. 【动手学MVG】ICP算法原理和代码实现

    介绍 本文介绍了相同尺度的点云ICP算法,若点云之间尺度不同,则需要估计尺度,可以看一下另一篇博文<通过ICP配准两组尺度不同点云, 统一坐标系(附代码)> 本文介绍的ICP问题,是在3D ...

  3. 点云配准NDT+ICP

    点云配准NDT+ICP #include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types. ...

  4. SAC-IA粗配准+ICP精配准

    最近一直在看点云配准相关的算法,在这里记录一下我试验过的配准算法. 第一弹:SAC-IA粗配准+ICP精配准 采样一致性初始配准算法(Sample Consensus Initial Aligment ...

  5. python 点云配准_点云配准(Registration)算法——以PCL为例

    本文为PCL官方教程的Registration模块的中文简介版. An Overview of Pairwise Registration 点云配准包括以下步骤: from a set of poin ...

  6. SLAM系列——视觉里程计(特征法)

    目录 1 特征点法 1.1 特征点 1.2 ORB特征 1.3 特征匹配 2 特征提取与匹配 3 2D-2D:对极几何 3.1 对极约束 3.2 本质矩阵 3.3 单应矩阵 讨论 4 实践:对极约束求 ...

  7. 视觉SLAM十四讲——ch7

    视觉SLAM十四讲--ch7 ch7视觉里程计 本章目标: 1.理解图像特征点的意义,并掌握在单副图像中提取出特征点及多副图像中匹配特征点的方法 2.理解对极几何的原理,利用对极几何的约束,恢复出图像 ...

  8. ICP算法概述以及使用SVD进行算法推导

    本文转载于B站UP主 摆烂世家的视频ICP算法概述及使用SVD推导(组会录像) 作者:苏浩田 (注:如有侵权,私信我下立马删咯) 1 ICP 算法   三维点云拼接技术在不同场合亦被称为重定位.配准或 ...

  9. 【手写ICP】ICP -SVD 手动实现与例程(上)

    文章目录 1. 写在前面 1. 配和代码的 SVD-ICP 流程梳理 1.1 SVD-ICP 算法 5 步走 1.2 预处理 1.3 点云变换 1.4 通过 KD-tree 找最近匹配点 1.5 SV ...

最新文章

  1. android 背景切换动画效果代码,关于Android shape gradient背景渐变
  2. Code Hunt SECTOR 10 - 11(Jagged Arrays Arrays 2)
  3. LINUX下配置bashrc文件/ PATH环境变量修改/别名/提示符
  4. Java学习笔记之:Java引用数据类型之字符串
  5. 固定ui大小_Material Design 响应式UI
  6. Atitit nodejs js 获取图像分辨率 尺寸 大小 宽度 高度
  7. java爬虫抓取起点小说,手把手带你爬虫 | 爬取起点小说网
  8. Linux --配置网络(通过网络访问服务器)
  9. Frequency bin
  10. LaTeX使用tikz-imagelabels宏包在图片上添加标签、文字等
  11. 这样的也可以发.....
  12. Facebook POP 动画框架 进阶指南
  13. Java 学习API(一)
  14. 用html画布做扇形,jquery canvas 画扇形
  15. 腾讯云开发小程序课程笔记
  16. android adb调试技巧
  17. Three.js音乐可视化
  18. 为永嘉路故店合弛的宣扬口号毛病援用“法租界”做替店址的描述
  19. windows应用商店或者其他微软应用打不开的修复办法
  20. 【DGL教程】第4章 图数据集

热门文章

  1. C++STL之stack栈容器
  2. 计算机网络知识整理-计算机网络概述(计算机网络入门参考指南)
  3. 【软件无线电】基于QCustomPlot 实现频谱图、瀑布图、星座图、比特图、音频图
  4. PS制作圆角透明图片
  5. 盲目跟风,害的是你自己
  6. JavaScript高级(一)
  7. 沙特阿拉伯重新开放对于持有美国、英国和申根签证旅行者落地签
  8. Ping和traceroute的原理
  9. 做一个九宫格诗词答题小程序 (二)倒计时功能实现
  10. The multi-part request contained parameter data (excluding uploaded files)