00 双目测距与三维重建的OpenCV实现问题集锦(三)立体匹配与视差计算

Post date: 2012/4/10 上午 08:26:38

chenyusiyuan » 2010-10-26 6:35

四、双目匹配与视差计算

立体匹配主要是通过找出每对图像间的对应关系,根据三角测量原理,得到视差图;在获得了视差信息后,根据投影模型很容易地可以得到原始图像的深度信息和三维信息。立体匹配技术被普遍认为是立体视觉中最困难也是最关键的问题,主要是以下因素的影响:

  • (1) 光学失真和噪声(亮度、色调、饱和度等失衡)
  • (2) 平滑表面的镜面反射
  • (3) 投影缩减(Foreshortening)
  • (4) 透视失真(Perspective distortions)
  • (5) 低纹理(Low texture)
  • (6) 重复纹理(Repetitive/ambiguous patterns)
  • (7) 透明物体
  • (8) 重叠和非连续

目前立体匹配算法是计算机视觉中的一个难点和热点,算法很多,但是一般的步骤是:

A、 匹配代价计算

匹配代价计算是整个立体匹配算法的基础,实际是对不同视差下进行灰度相似性测量。常见的方法有灰度差的平方SD(squared intensity differences),灰度差的绝对值AD(absolute intensity differences)等。另外,在求原始匹配代价时可以设定一个上限值,来减弱叠加过程中的误匹配的影响。以AD法求匹配代价为例,可用下式进行计算,其中T为设定的阈值。

18.jpg (15 KiB) 被浏览 4483 次

图18

B、 匹配代价叠加

一般来说,全局算法基于原始匹配代价进行后续算法计算。而区域算法则需要通过窗口叠加来增强匹配代价的可靠性,根据原始匹配代价不同,可分为:

19.jpg (60.77 KiB) 被浏览 4483 次

图19

C、 视差获取

对于区域算法来说,在完成匹配代价的叠加以后,视差的获取就很容易了,只需在一定范围内选取叠加匹配代价最优的点(SAD和SSD取最小值,NCC取最大值)作为对应匹配点,如胜者为王算法WTA(Winner-take-all)。而全局算法则直接对原始匹配代价进行处理,一般会先给出一个能量评价函数,然后通过不同的优化算法来求得能量的最小值,同时每个点的视差值也就计算出来了。

D、 视差细化(亚像素级)

大多数立体匹配算法计算出来的视差都是一些离散的特定整数值,可满足一般应用的精度要求。但在一些精度要求比较高的场合,如精确的三维重构中,就需要在初始视差获取后采用一些措施对视差进行细化,如匹配代价的曲线拟合、图像滤波、图像分割等。

有关立体匹配的介绍和常见匹配算法的比较,推荐大家看看Stefano Mattoccia 的讲义Stereo Vision: algorithms and applications,190页的ppt,讲解得非常形象详尽。

1. opencv2.1和opencv2.0在做stereo vision方面有什么区别了?

2.1版增强了Stereo Vision方面的功能:

  • (1) 新增了 SGBM 立体匹配算法(源自Heiko Hirschmuller的《Stereo Processing by Semi-global Matching and Mutual Information》),可以获得比 BM 算法物体轮廓更清晰的视差图(但低纹理区域容易出现横/斜纹路,在 GCstate->fullDP 选项使能时可消减这种异常纹路,但对应区域视差变为0,且运行速度会有所下降),速度比 BM 稍慢, 352*288的帧处理速度大约是 5 帧/秒;
  • (2) 视差效果:BM < SGBM < GC;处理速度:BM > SGBM > GC ;
  • (3) BM 算法比2.0版性能有所提升,其状态参数新增了对左右视图感兴趣区域 ROI 的支持(roi1 和 roi2,由stereoRectify函数产生);
  • (4) BM 算法和 GC 算法的核心代码改动不大,主要是面向多线程运算方面的(由 OpenMP 转向 Intel TBB);
  • (5) cvFindStereoCorrespondenceBM 函数的disparity参数的数据格式新增了 CV_32F 的支持,这种格式的数据给出实际视差,而 2.0 版只支持 CV_16S,需要除以 16.0 才能得到实际的视差数值。

2. 用于立体匹配的图像可以是彩色的吗?

在OpenCV2.1中,BM和GC算法只能对8位灰度图像计算视差,SGBM算法则可以处理24位(8bits*3)彩色图像。所以在读入图像时,应该根据采用的算法来处理图像:

代码: 全选

      int color_mode = alg == STEREO_SGBM ? 1 : 0;
      //////////////////////////////////////////////////////////////////////////
      // 载入图像
      cvGrabFrame( lfCam );
      cvGrabFrame( riCam );
      frame1 = cvRetrieveFrame( lfCam );
      frame2 = cvRetrieveFrame( riCam );
      if(frame1.empty()) break;
      resize(frame1, img1, img_size, 0, 0);
      resize(frame2, img2, img_size, 0, 0);
      // 选择彩色或灰度格式作为双目匹配的处理图像
      if (!color_mode && cn>1)
      {         cvtColor(img1, img1gray, CV_BGR2GRAY);         cvtColor(img2, img2gray, CV_BGR2GRAY);         img1p = img1gray;         img2p = img2gray;      }      else      {         img1p = img1;         img2p = img2;      }

3. 怎样获取与原图像有效像素区域相同的视差图?

在OpenCV2.0及以前的版本中,所获取的视差图总是在左侧和右侧有明显的黑色区域,这些区域没有有效的视差数据。视差图有效像素区域与视差窗口(ndisp,一般取正值且能被16整除)和最小视差值(mindisp,一般取0或负值)相关,视差窗口越大,视差图左侧的黑色区域越大,最小视差值越小,视差图右侧的黑色区域越大。其原因是为了保证参考图像(一般是左视图)的像素点能在目标图像(右视图)中按照设定的视差匹配窗口匹配对应点,OpenCV 只从参考图像的第 (ndisp - 1 + mindisp) 列开始向右计算视差,第 0 列到第 (ndisp - 1 + mindisp) 列的区域视差统一设置为 (mindisp - 1) *16;视差计算到第 width + mindisp 列时停止,余下的右侧区域视差值也统一设置为 (mindisp - 1) *16。

代码: 全选

00177 static const int DISPARITY_SHIFT = 4;
00411     int ndisp = state->numberOfDisparities;
00412     int mindisp = state->minDisparity;
00413     int lofs = MAX(ndisp - 1 + mindisp, 0);
00414     int rofs = -MIN(ndisp - 1 + mindisp, 0);
00415     int width = left->cols, height = left->rows;
00416     int width1 = width - rofs - ndisp + 1;
00420     short FILTERED = (short)((mindisp - 1) << DISPARITY_SHIFT);
00466     // initialize the left and right borders of the disparity map
00467     for( y = 0; y < height; y++ )00468     {00469         for( x = 0; x < lofs; x++ )00470             dptr[y*dstep + x] = FILTERED;00471         for( x = lofs + width1; x < width; x++ )00472             dptr[y*dstep + x] = FILTERED;00473     }00474     dptr += lofs;0047500476     for( x = 0; x < width1; x++, dptr++ )…

这样的设置很明显是不符合实际应用的需求的,它相当于把摄像头的视场范围缩窄了。因此,OpenCV2.1 做了明显的改进,不再要求左右视图和视差图的大小(size)一致,允许对视差图进行左右边界延拓,这样,虽然计算视差时还是按上面的代码思路来处理左右边界,但是视差图的边界得到延拓后,有效视差的范围就能够与对应视图完全对应。具体的实现代码范例如下:

代码: 全选

      //////////////////////////////////////////////////////////////////////////
      // 对左右视图的左边进行边界延拓,以获取与原始视图相同大小的有效视差区域
      copyMakeBorder(img1r, img1b, 0, 0, m_nMaxDisp, 0, IPL_BORDER_REPLICATE);
      copyMakeBorder(img2r, img2b, 0, 0, m_nMaxDisp, 0, IPL_BORDER_REPLICATE);
      //////////////////////////////////////////////////////////////////////////
      // 计算视差
      if( alg == STEREO_BM )
      {
         bm(img1b, img2b, dispb);
         // 截取与原始画面对应的视差区域(舍去加宽的部分)
         displf = dispb.colRange(m_nMaxDisp, img1b.cols);   
      }      else if(alg == STEREO_SGBM)      {         sgbm(img1b, img2b, dispb);         displf = dispb.colRange(m_nMaxDisp, img1b.cols);      }

4. cvFindStereoCorrespondenceBM的输出结果好像不是以像素点为单位的视差?

@scyscyao:在OpenCV2.0中,BM函数得出的结果是以16位符号数的形式的存储的,出于精度需要,所有的视差在输出时都扩大了16倍(2^4)。其具体代码表示如下:

代码: 全选

dptr[y*dstep] = (short)(((ndisp - mind - 1 + mindisp)*256 + (d != 0 ? (p-n)*128/d : 0) + 15) >> 4);

可以看到,原始视差在左移8位(256)并且加上一个修正值之后又右移了4位,最终的结果就是左移4位。

因此,在实际求距离时,cvReprojectTo3D出来的X/W,Y/W,Z/W都要乘以16 (也就是W除以16),才能得到正确的三维坐标信息。”

在OpenCV2.1中,BM算法可以用 CV_16S 或者 CV_32F 的方式输出视差数据,使用32位float格式可以得到真实的视差值,而CV_16S 格式得到的视差矩阵则需要 除以16 才能得到正确的视差。另外,OpenCV2.1另外两种立体匹配算法 SGBM 和 GC 只支持 CV_16S 格式的 disparity 矩阵。

5. 如何设置BM、SGBM和GC算法的状态参数?

(1)StereoBMState

// 预处理滤波参数

  • ——preFilterType:预处理滤波器的类型,主要是用于降低亮度失真(photometric distortions)、消除噪声和增强纹理等, 有两种可选类型:CV_STEREO_BM_NORMALIZED_RESPONSE(归一化响应) 或者 CV_STEREO_BM_XSOBEL(水平方向Sobel算子,默认类型), 该参数为 int 型;
  • ——preFilterSize:预处理滤波器窗口大小,容许范围是[5,255],一般应该在 5x5..21x21 之间,参数必须为奇数值, int 型
  • ——preFilterCap:预处理滤波器的截断值,预处理的输出值仅保留[-preFilterCap, preFilterCap]范围内的值,参数范围:1 - 31(文档中是31,但代码中是 63), int

// SAD 参数

  • ——SADWindowSize:SAD窗口大小,容许范围是[5,255],一般应该在 5x5 至 21x21 之间,参数必须是奇数,int 型
  • ——minDisparity:最小视差,默认值为 0, 可以是负值,int 型
  • ——numberOfDisparities:视差窗口,即最大视差值与最小视差值之差, 窗口大小必须是 16 的整数倍,int 型

// 后处理参数

  • ——textureThreshold:低纹理区域的判断阈值。如果当前SAD窗口内所有邻居像素点的x导数绝对值之和小于指定阈值,则该窗口对应的像素点的视差值为 0(That is, if the sum of absolute values of x-derivatives computed over SADWindowSize by SADWindowSize pixel neighborhood is smaller than the parameter, no disparity is computed at the pixel),该参数不能为负值,int 型
  • ——uniquenessRatio:视差唯一性百分比, 视差窗口范围内最低代价是次低代价的(1 + uniquenessRatio/100)倍时,最低代价对应的视差值才是该像素点的视差,否则该像素点的视差为 0 (the minimum margin in percents between the best (minimum) cost function value and the second best value to accept the computed disparity, that is, accept the computed disparity d^ only if SAD(d) >= SAD(d^) x (1 + uniquenessRatio/100.) for any d != d*+/-1 within the search range ),该参数不能为负值,一般5-15左右的值比较合适,int 型
  • ——speckleWindowSize:检查视差连通区域变化度的窗口大小, 值为 0 时取消 speckle 检查,int 型
  • ——speckleRange:视差变化阈值,当窗口内视差变化大于阈值时,该窗口内的视差清零,int 型

// OpenCV2.1 新增的状态参数

  • ——roi1, roi2:左右视图的有效像素区域,一般由双目校正阶段的 cvStereoRectify 函数传递,也可以自行设定。一旦在状态参数中设定了 roi1 和 roi2,OpenCV 会通过cvGetValidDisparityROI 函数计算出视差图的有效区域,在有效区域外的视差值将被清零。
  • ——disp12MaxDiff:左视差图(直接计算得出)和右视差图(通过cvValidateDisparity计算得出)之间的最大容许差异。超过该阈值的视差值将被清零。该参数默认为 -1,即不执行左右视差检查。int 型。注意在程序调试阶段最好保持该值为 -1,以便查看不同视差窗口生成的视差效果。具体请参见《使用OpenGL动态显示双目视觉三维重构效果示例》一文中的讨论。

在上述参数中,对视差生成效果影响较大的主要参数是 SADWindowSize、numberOfDisparities 和 uniquenessRatio 三个,一般只需对这三个参数进行调整,其余参数按默认设置即可。

在OpenCV2.1中,BM算法有C和C++ 两种实现模块。

(2)StereoSGBMState

SGBM算法的状态参数大部分与BM算法的一致,下面只解释不同的部分:

  • ——SADWindowSize:SAD窗口大小,容许范围是[1,11],一般应该在 3x3 至 11x11 之间,参数必须是奇数,int 型
  • ——P1, P2:控制视差变化平滑性的参数。P1、P2的值越大,视差越平滑。P1是相邻像素点视差增/减 1 时的惩罚系数;P2是相邻像素点视差变化值大于1时的惩罚系数。P2必须大于P1。OpenCV2.1提供的例程 stereo_match.cpp 给出了 P1 和 P2 比较合适的数值。
  • ——fullDP:布尔值,当设置为 TRUE 时,运行双通道动态编程算法(full-scale 2-pass dynamic programming algorithm),会占用O(W*H*numDisparities)个字节,对于高分辨率图像将占用较大的内存空间。一般设置为 FALSE。

注意OpenCV2.1的SGBM算法是用C++ 语言编写的,没有C实现模块。与H. Hirschmuller提出的原算法相比,主要有如下变化:

  • A. 算法默认运行单通道DP算法,只用了5个方向,而fullDP使能时则使用8个方向(可能需要占用大量内存)。
  • B. 算法在计算匹配代价函数时,采用块匹配方法而非像素匹配(不过SADWindowSize=1时就等于像素匹配了)。
  • C. 匹配代价的计算采用BT算法("Depth Discontinuities by Pixel-to-Pixel Stereo" by S. Birchfield and C. Tomasi),并没有实现基于互熵信息的匹配代价计算。
  • D. 增加了一些BM算法中的预处理和后处理程序。

(3)StereoGCState

GC算法的状态参数只有两个:numberOfDisparities 和 maxIters ,并且只能通过 cvCreateStereoGCState 在创建算法状态结构体时一次性确定,不能在循环中更新状态信息。GC算法并不是一种实时算法,但可以得到物体轮廓清晰准确的视差图,适用于静态环境物体的深度重构。

注意GC算法只能在C语言模式下运行,并且不能对视差图进行预先的边界延拓,左右视图和左右视差矩阵的大小必须一致。

6. 如何实现视差图的伪彩色显示?

首先要将16位符号整形的视差矩阵转换为8位无符号整形矩阵,然后按照一定的变换关系进行伪彩色处理。我的实现代码如下:

代码: 全选

// 转换为 CV_8U 格式,彩色显示
dispLfcv = displf,      dispRicv = dispri,      disp8cv = disp8;
if (alg == STEREO_GC)
{
   cvNormalize( &dispLfcv, &disp8cv, 0, 256, CV_MINMAX );
} 
else
{
   displf.convertTo(disp8, CV_8U, 255/(m_nMaxDisp*16.));
   }
   F_Gray2Color(&disp8cv, vdispRGB);

灰度图转伪彩色图的代码,主要功能是使灰度图中 亮度越高的像素点,在伪彩色图中对应的点越趋向于 红色;亮度越低,则对应的伪彩色越趋向于 蓝色;总体上按照灰度值高低,由红渐变至蓝,中间色为绿色。其对应关系如下图所示:

20.JPG (8.56 KiB) 被浏览 4483 次

图20

代码: 全选

F_Gray2Color(CvMat* gray_mat, CvMat* color_mat)
{
   if(color_mat)
      cvZero(color_mat);
      
   int stype = CV_MAT_TYPE(gray_mat->type), dtype = CV_MAT_TYPE(color_mat->type);
   int rows = gray_mat->rows, cols = gray_mat->cols;
   // 判断输入的灰度图和输出的伪彩色图是否大小相同、格式是否符合要求
   if (CV_ARE_SIZES_EQ(gray_mat, color_mat) && stype == CV_8UC1 && dtype == CV_8UC3)
   {
      CvMat* red = cvCreateMat(gray_mat->rows, gray_mat->cols, CV_8U);
      CvMat* green = cvCreateMat(gray_mat->rows, gray_mat->cols, CV_8U);      CvMat* blue = cvCreateMat(gray_mat->rows, gray_mat->cols, CV_8U);      CvMat* mask = cvCreateMat(gray_mat->rows, gray_mat->cols, CV_8U);      // 计算各彩色通道的像素值      cvSubRS(gray_mat, cvScalar(255), blue);         // blue(I) = 255 - gray(I)      cvCopy(gray_mat, red);                     // red(I) = gray(I)      cvCopy(gray_mat, green);                  // green(I) = gray(I),   if gray(I) < 128      cvCmpS(green, 128, mask, CV_CMP_GE );         // green(I) = 255 - gray(I), if gray(I) >= 128      cvSubRS(green, cvScalar(255), green, mask);      cvConvertScale(green, green, 2.0, 0.0);      // 合成伪彩色图      cvMerge(blue, green, red, NULL, color_mat);      cvReleaseMat( &red );      cvReleaseMat( &green );      cvReleaseMat( &blue );      cvReleaseMat( &mask );   }}

7. 如何将视差数据保存为 txt 数据文件以便在 Matlab 中读取分析?

由于OpenCV本身只支持 xml、yml 的数据文件读写功能,并且其xml文件与构建网页数据所用的xml文件格式不一致,在Matlab中无法读取。我们可以通过以下方式将视差数据保存为txt文件,再导入到Matlab中。

代码: 全选

void saveDisp(const char* filename, const Mat& mat)      
{
   FILE* fp = fopen(filename, "wt");
   fprintf(fp, "%02d\n", mat.rows);
   fprintf(fp, "%02d\n", mat.cols);
   for(int y = 0; y < mat.rows; y++)
   {
      for(int x = 0; x < mat.cols; x++)
      {
         short disp = mat.at<short>(y, x);   // 这里视差矩阵是CV_16S 格式的,故用 short 类型读取
         fprintf(fp, "%d\n", disp); // 若视差矩阵是 CV_32F 格式,则用 float 类型读取
      }
   }   fclose(fp);}

相应的Matlab代码为:

代码: 全选

function img = txt2img(filename)
data = importdata(filename);
r = data(1);    % 行数
c = data(2);    % 列数
disp = data(3:end); % 视差
vmin = min(disp);
vmax = max(disp);
disp = reshape(disp, [c,r])'; % 将列向量形式的 disp 重构为 矩阵形式
%  OpenCV 是行扫描存储图像,Matlab 是列扫描存储图像
%  故对 disp 的重新排列是首先变成 c 行 r 列的矩阵,然后再转置回 r 行 c 列
img = uint8( 255 * ( disp - vmin ) / ( vmax - vmin ) );
mesh(disp);
set(gca,'YDir','reverse');  % 通过 mesh 方式绘图时,需倒置 Y 轴方向axis tight; % 使坐标轴显示范围与数据范围相贴合,去除空白显示区

显示效果如下:

21.jpg (57.13 KiB) 被浏览 4483 次

图21

chenyusiyuan

OpenCV本科生

帖子: 144

注册: 2009-09-23 10:40

seaok » 2010-11-03 9:43

我这也在做双目立体视觉方面,现在遇到问题,就是求出的视差都是一块一块的,估计应该是亚像素的精确度不够高,不知道你在视差细化方面是如何处理的?

seaok

OpenCV小学生

帖子: 6

注册: 2010-07-21 9:15

chenyusiyuan » 2010-11-04 11:55

seaok 写道:

我这也在做双目立体视觉方面,现在遇到问题,就是求出的视差都是一块一块的,估计应该是亚像素的精确度不够高,不知道你在视差细化方面是如何处理的?

你也是用块匹配方法做的吧?块匹配求出的视差图精度和边界清晰度都是差一点的,可以结合边缘检测、图像分割和插值滤波等方法来细化。不妨贴一张你的视差图看看。

chenyusiyuan

OpenCV本科生

帖子: 144

注册: 2009-09-23 10:40

seaok » 2010-11-04 12:40

我现在在外面倒是没法上图,倒是采用gc 的方法发现一层一层的,bm的方法倒是还行。虽然我们的项目主要是三维重建的,在实时性上要求不是很高,不过gc这种全局性的算法时间上太长,无法容忍,所以暂时还是采用bm这种,自己写过ncc倒是也是出现这种一层层的现象。还有就是不知道楼主sgbm的算法是怎么调用的?

seaok

OpenCV小学生

帖子: 6

注册: 2010-07-21 9:15

chenyusiyuan » 2010-11-04 22:26

seaok 写道:

我现在在外面倒是没法上图,倒是采用gc 的方法发现一层一层的,bm的方法倒是还行。虽然我们的项目主要是三维重建的,在实时性上要求不是很高,不过gc这种全局性的算法时间上太长,无法容忍,所以暂时还是采用bm这种,自己写过ncc倒是也是出现这种一层层的现象。还有就是不知道楼主sgbm的算法是怎么调用的?

SGBM算法是OpenCV2.1版推出的,效果和角度比BM要好,速度比bm稍慢,但尚可接受,288*352 的图像每秒可以处理5帧左右(Release版本,Debug版是1-2帧/秒),不过在低纹理区域要比BM算法容易产生误匹配。

chenyusiyuan

OpenCV本科生

帖子: 144

注册: 2009-09-23 10:40

seaok » 2010-11-05 13:24

今天我试了下sgbm,sgbm(left , right , left_disp_); 我很奇怪为什么opencv中sgbm的第三个矩阵类型是mat类型,想请问下楼主你是怎么处理mat和cvmat类型之间转换?

seaok

OpenCV小学生

帖子: 6

注册: 2010-07-21 9:15

chenyusiyuan » 2010-11-05 17:43

seaok 写道:

今天我试了下sgbm,sgbm(left , right , left_disp_); 我很奇怪为什么opencv中sgbm的第三个矩阵类型是mat类型,想请问下楼主你是怎么处理mat和cvmat类型之间转换?

OpenCV2.1的 SGBM 模块是用 C++ 语言编写的,Mat 类型是 OpenCV2.1 的 C++ API 之一,转换为 CvMat 很简单,例如:

代码: 全选

Mat disp;
CvMat disp_mat = disp;

这里 disp_mat 只是 disp 的一个 CvMat header,并没有复制实际数据。对 disp_mat的任何操作都相当于对 disp 的操作,会直接改变 disp 的数据。

类似地,mat 转换为 IplImage 也是一行代码就行:

代码: 全选

IplImage disp_ipl = disp;

chenyusiyuan

OpenCV本科生

帖子: 144

注册: 2009-09-23 10:40

seaok » 2010-11-06 9:05

感谢楼主,昨天随便拉了张图片来测试,发现sgbm确实效果比bm的要来的好点,实时性上也没差bm很多。

seaok

OpenCV小学生

帖子: 6

注册: 2010-07-21 9:15

seaok » 2010-11-07 17:51

楼主留个email吧,以后想跟你多多交流视觉上的问题哈哈。

seaok

OpenCV小学生

帖子: 6

注册: 2010-07-21 9:15

chenyusiyuan » 2010-11-07 20:36

seaok 写道:

楼主留个email吧,以后想跟你多多交流视觉上的问题哈哈。

呵呵,我 Email 是 chenyusiyuan<at>126.com,博客(blog.csdn.net/chenyusiyuan)上也有一些 OpenCV 的笔记,欢迎一起交流讨论。

chenyusiyuan

OpenCV本科生

帖子: 144

注册: 2009-09-23 10:40

ollydbg23 » 2010-11-09 9:14

这个帖子非常不错,我也收藏一下,赞!!!

本人不接受站内私人短信讨论技术问题,有问题请发贴讨论!!!

提问的智慧 OpenCV论坛中文搜索 论坛精华区

ollydbg23

OpenCV教授

帖子: 3111

注册: 2008-04-13 9:16

huzai2008

OpenCV小学生

帖子: 9

注册: 2011-03-17 15:26

tanxingxiang

OpenCV幼儿园宝宝

帖子: 1

注册: 2011-10-10 17:26

birthfly » 2012-03-06 10:39

请问一下各位:

我做出来后矫正图像都完全正确,很理想,为什么我求出的矩阵Q是病态的,如下:请问这是什么情况,能介绍一下么?ps:矫正图像已经行对准了

附件

`ARNJ}S5HLEOU%YGW@~M47K.jpg (19.65 KiB) 被浏览 493 次