ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenCV相机标定与双目标定:内参、畸变校正与源码实战

OpenCV相机标定与双目标定:内参、畸变校正与源码实战 简介这是一套面向计算机视觉领域研究者与工程师的相机标定项目源码聚焦相机内参标定与双目标定用于估计焦距、主点、畸变系数以及相机在世界坐标系中的位置和姿态。压缩包内共收录两百余个文件大小约为二十四兆字节包含C源文件、Python脚本、YAML与JSON配置、PDF文档以及JPG测试图像代码、配置、说明与样本数据齐全便于按需查阅。目前已有四百二十人学习参考具备实际工程参考价值。项目支持棋盘格、圆形格等多种标定板并适配针孔、畸变、鱼眼等不同相机模型源码中已集成特征点检测、张正友标定、畸变校正、外部参数估计与结果验证等模块同时附带图像预处理和用户界面相关实现可直接运行或进行二次开发。该资源既能帮助读者深入理解标定原理也可直接服务于机器人视觉、三维重建、自动驾驶等应用场景有效节省从零搭建标定系统的时间。1. 相机标定不是玄学内参双目标定一套源码解决拍到的图像不是真实世界的直接映射镜头畸变和安装误差会把像素坐标扭曲掉。相机标定就是把这些偏差还原成一组参数让二维像素能可靠地换算成三维坐标。这个项目源码就是一套现成的相机内参标定与双目标定工具支持多种相机模型针孔、鱼眼、全向和多种标定板棋盘格、圆形网格、ArUco覆盖从单目到双目、从标定到验证的完整链路。适合谁如果你在做双目测距、机械臂抓取、SLAM 或无人车感知发现实际测量误差总比理论大或者重投影误差高得离谱这份源码能省掉你重新造轮子的时间。下面我会从内参标定讲起逐步拆到双目标定再把相机模型、标定板选型和常见坑都过一遍。2. 相机内参标定从棋盘格到内参矩阵的完整流程2.1 为什么先标内参内参矩阵、畸变系数和投影关系相机标定的核心是把三维世界中的一个点通过相机光心投影到像素平面上。这个投影关系可以写成[ z \begin{bmatrix} u \ v \ 1 \end{bmatrix} K \begin{bmatrix} R t \end{bmatrix} \begin{bmatrix} X \ Y \ Z \ 1 \end{bmatrix} ]其中 K 是内参矩阵包含焦距 fx、fy 和主点 cx、cyR 和 t 是相机在世界坐标系里的姿态。标定板棋盘格因为方格尺寸已知能同时提供多个世界坐标点与像素坐标的对应关系这样就能用最优化方法把 K 算出来。但实际镜头还有畸变主要是径向畸变桶形/枕形和切向畸变。OpenCV 的针孔模型用 k1、k2、k3 描述径向畸变p1、p2 描述切向畸变。如果畸变不校正棋盘格直线会变弯角点位置也会偏测距和拼接都会受影响。所以内参标定的最终输出不只是 K还有畸变系数。这里有一个很容易被忽略的点标定板方格尺寸的输入不会影响内参的尺度因为内参是以像素为单位而三维点只是在标定板坐标系下的相对坐标。你可以用 mm 也可以用格子数量得到的 fx、fy 是一样的。但是双目标定中 T 的尺度会受这个值影响所以最终做深度测量时必须把真实尺寸写对。这个我在后面会再强调。2.2 标定板准备与图像采集的规范先讲标定板。打印棋盘格时要注意两点一是用高分辨率打印方格边缘要锐利二是把纸平整地贴在刚性平板上不要贴在软的纸板上不然角点位置会整体弯曲。我一般用玻璃板或者铝基板磨砂面朝外避免反光。图像采集的数量和角度直接决定标定质量。经验阈值是至少 15 张更多不是坏事。覆盖规则是“远、近、左、右、歪”也就是让棋盘格出现在画面中央、四角、不同距离和不同倾斜角度。注意不能把所有图像都拍成正对相机的棋盘格跟像平面平行那样解算出的焦距会不稳定。采集时也要避免过曝和反光否则角点亚像素提取会失真。这里我习惯的动作是把标定板在手里缓慢旋转每到一个姿态停一下拍一张。一组有效的图像里相邻两张的面内角度差至少 5 度。如果全是一模一样的正位照片即使有 30 张对求解也没有帮助反而可能让内参矩阵病态。如果你的双目相机需要双目标定那左右图像必须同一时刻采集。一个常见做法是接两个摄像头同步触发或者用硬件外触发确保左右帧对齐。这一点我会在第 3 章详细说。2.3 源码实战基于 OpenCV 的角点提取与标定这里给出一段可以直接跑的 OpenCV 标定流程代码这个项目里核心逻辑也是这套import cv2 import numpy as np import glob # 棋盘格内角点数量7×10按你实际打印的棋盘格修改 pattern_size (7, 10) # 生成世界坐标系下的标定点所有点z0 objp np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) obj_points [] # 3D点 img_points [] # 像素点 image_size None for fname in sorted(glob.glob(calib_images/*.jpg)): img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if image_size is None: image_size gray.shape[::-1] # 提取棋盘格角点 ret, corners cv2.findChessboardCorners(gray, pattern_size, None) if not ret: print(f跳过 {fname}: 未找到完整棋盘格) continue # 亚像素细化提升角点精度 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners2 cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria) obj_points.append(objp) img_points.append(corners2) print(f成功提取 {len(img_points)} 张有效图像) # 单目标定 ret, K, dist, rvecs, tvecs cv2.calibrateCamera( obj_points, img_points, image_size, None, None) print(内参矩阵 K:) print(K) print(畸变系数 dist:) print(dist) print(重投影误差像素:, ret)逻辑说明obj_points 是标定板平面上每个内角点的三维坐标z0单位用索引个数代替真实毫米值即可。img_points 是亚像素细化后的角点像素坐标。cv2.calibrateCamera 会同时计算内参、畸变、每张图的外参rvecs 和 tvecs返回值里的 ret 是所有角点的重投影误差的平均值。参数说明有两个容易错的地方。第一pattern_size 是“内角点数量”不是棋盘格数量。比如打印一个 9×12 个格子的棋盘内角点就是 8×11这个不对的话角点检测会失败。第二corners2 是 float32而且必须按行排列findChessboardCorners 默认返回顺序是排好的但如果自己写循环提取就要注意顺序一致性否则标定结果会非常不稳。提示如果你的标定板是圆形网格把 findChessboardCorners 换成 findCirclesGridpattern_size 也要改成 grid_size调用方式是 cv2.findCirclesGrid(gray, grid_size, None, flagscv2.CALIB_CB_SYMMETRIC_GRID)。2.4 标定结果保存与重投影误差判断标定得到的 K 和 dist 需要保存成后续可用的格式。常见做法是保存成 numpy 文件或 yaml。OpenCV 的 FileStorage 可以直接写 yamlfs cv2.FileStorage(calib_result.yaml, cv2.FILE_STORAGE_WRITE) fs.write(K, K) fs.write(dist, dist) fs.write(ret, ret) fs.release()保存后下次读取用 cv2.FileStorage(calib_result.yaml, cv2.FILE_STORAGE_READ) 就可以。重投影误差 ret 的单位是像素一般小于 0.1 像素是好的0.2-0.3 也可以接受超过 0.5 就要怀疑采集或设置有问题。但 ret 只是整体平均有可能会掩盖某些图像上的局部偏差所以我会额外做一次反投影可视化。反投影的做法是取其中一张标定图的外参 rvec、tvec把 objp 投影回图像然后计算投影点跟原角点之间的像素距离并画出来。这样能直观看到哪些区域角点偏差大。这个校验在这个项目源码里通常也有对应脚本如果你下载后没有可以按下面代码自己补# 任取一张标定图的外参 idx 0 proj_points, _ cv2.projectPoints(obj_points[idx], rvecs[idx], tvecs[idx], K, dist) error_pixels np.sqrt(np.sum((proj_points.reshape(-1, 2) - img_points[idx].reshape(-1, 2)) ** 2, axis1)) print(f第 {idx} 张图的角点平均误差: {error_pixels.mean():.3f} 像素) print(f最大误差: {error_pixels.max():.3f} 像素)如果最大误差出现在图像边缘说明畸变模型可能不够或者相机模型选错了。对普通镜头针孔模型就够如果镜头是鱼眼那就要换第 4 章提到的鱼眼模型。另外如果 ret 很大优先检查三件事pattern_size 是否写错、有没有图像没检测到角点但又被误加进列表、标定板是不是有反光。这三条占了 80% 的翻车原因。3. 双目标定左右相机联合标定与极线校正3.1 双目系统需要额外标定什么外参、基础矩阵、本质矩阵双目相机不只是两个独立相机。除了各自的内参和畸变还要知道右相机相对左相机的旋转和平移这就是外参用 R 和 T 表示。只有当外参准确才能把左右图像对应起来计算视差恢复深度。从几何上看左右相机观察同一个三维点这个点与两个相机光心构成一个三角形。外参决定了这个三角形的形状。基础矩阵 F 和本质矩阵 E 描述的是左右图像之间对应的极线约束其中本质矩阵只包含外参基础矩阵还包含内参。双目标定的输出里通常会有 E 和 F它们可以用于极线校验和错误匹配剔除。深度恢复的公式是如果校正后左右相机光心水平对齐那么三维点深度 z f * b / d其中 f 是焦距像素b 是基线长度d 是视差。这个式子看着简单但实际误差非常依赖于外参 T 的精度。T 的模长就是从左右光心的实际距离如果标定出的 T 和物理距离对不上那按公式算出来的深度会有系统性偏差。3.2 双目标定实现步骤立体标定、双目校正、视差映射双目标定的标准流程是先用左右相机同时采集标定板图像分别做单目标定得到初始内参和畸变系数。调用 stereoCalibrate基于同一组标定板角点联合优化左右内参和左右相对外参 R、T。调用 stereoRectify计算左右相机的校正旋转矩阵 R1、R2 以及新的投影矩阵 P1、P2让左右图像行对准。用 initUndistortRectifyMap 生成重映射表再用 remap 把左右原始图像校正成无畸变且极线水平的图像。为什么要把图像校正成行对准因为行对准后深度计算就退化成一维搜索只需要在水平方向寻找匹配点视差就是左右像素坐标的差值。这个视差再配合 Q 矩阵就能转成三维坐标。关于内外参的边界如果你手里是工业相机单目标定已经很准双目标定只要更新外参就好所以 stereoCalibrate 的 flags 用 CALIB_FIX_INTRINSIC。如果单目标定不靠谱可以用 CALIB_USE_INTRINSIC_GUESS 让双目标定微调内参但前提是图像数量足够多否则内参和外部参会互相耦合结果反而更差。我一般会先用 CALIB_FIX_INTRINSIC 跑一遍看重投影误差如果误差太大再放开内参优化。3.3 代码实现stereoCalibrate 与 stereoRectify 参数解析双目标定代码建立在单目标定结果之上。下面这段是核心import cv2 import numpy as np # 假设 obj_points、left_points、right_points 来自同步采集的左右图像 # left_points[i] 和 right_points[i] 对应同一块标定板的同一角点 # 先单目标定 ret_l, K_l, dist_l, rvecs_l, tvecs_l cv2.calibrateCamera(obj_points, left_points, image_size, None, None) ret_r, K_r, dist_r, rvecs_r, tvecs_r cv2.calibrateCamera(obj_points, right_points, image_size, None, None) # 立体标定 flags cv2.CALIB_FIX_INTRINSIC criteria (cv2.TERM_CRITERIA_MAX_ITER cv2.TERM_CRITERIA_EPS, 100, 1e-5) ret, K_l, dist_l, K_r, dist_r, R, T, E, F cv2.stereoCalibrate( obj_points, left_points, right_points, K_l, dist_l, K_r, dist_r, image_size, criteriacriteria, flagsflags) print(立体标定重投影误差:, ret) print(旋转矩阵 R:\n, R) print(平移向量 T:\n, T)逻辑说明stereoCalibrate 的 obj_points 是同一个标定板坐标系下的三维点left_points 和 right_points 是同一时刻左右相机检测到的角点像素坐标。K_l、dist_l 等参数作为初始值传入函数的 flags 决定是否优化内参。CALIB_FIX_INTRINSIC 的意思是固定左右内参不变只优化外参适合你已经做过高质量单目标定的情况如果你的内参不太准可以考虑去掉这个 flag让双目标定一起优化内参但那样需要更多图像否则结果可能发散。参数说明R 是把右相机坐标系变换到左相机坐标系的旋转矩阵T 是平移向量。在测距应用里T 的模长就是左右相机光心的物理距离这个值可以用来校准基线。E 和 F 是本质矩阵和基础矩阵可以在做特征点匹配时用来剔除错误匹配。校正映射的代码如下# 计算校正参数 R1, R2, P1, P2, Q, valid_roi1, valid_roi2 cv2.stereoRectify( K_l, dist_l, K_r, dist_r, image_size, R, T, alpha0) # 生成映射表 map1_l, map2_l cv2.initUndistortRectifyMap(K_l, dist_l, R1, P1, image_size, cv2.CV_32FC1) map1_r, map2_r cv2.initUndistortRectifyMap(K_r, dist_r, R2, P2, image_size, cv2.CV_32FC1) # 应用映射 rectified_left cv2.remap(left_img, map1_l, map2_l, cv2.INTER_LINEAR) rectified_right cv2.remap(right_img, map1_r, map2_r, cv2.INTER_LINEAR) # 拼接显示检查行对准情况 vis np.hstack([rectified_left, rectified_right]) cv2.imwrite(rectified.png, vis)这段代码里 alpha 是裁剪系数。alpha0 表示只保留校正后有效的像素区域图像会被裁剪得比较小但不会露出黑色边缘alpha1 则保留所有原始像素边界会出现黑边通常需要裁剪一下。Q 矩阵是视差到深度的转换矩阵存成 npy 或 yaml后面做深度计算会用。注意 stereoRectify 的返回值里还有 valid_roi1 和 valid_roi2那是有效区域在计算视差时可以只在这个区域内算省掉无效边缘。这个细节很多开源代码都会忽略导致边界出现鬼影。3.4 左右分辨率不一致时的处理有些双目系统两个相机不是完全相同型号左右图像分辨率不同。这种情况下不能直接 stereoCalibrate因为 image_size 必须一致。常见做法是先把两张图 resize 到相同分辨率再进行标定。但要注意resize 会改变内参的尺度所以标定输入的 K 也要按比例缩放。比如左图从 1920×1080 缩到 960×540fx、fy、cx、cy 都要除以 2。如果源码里没有做这一步你就要在采集脚本里提前统一尺寸。另外如果左右相机视场角差异很大极线校正后可能只有重叠区域是有效的测距也只能在那个区域里算。我会在标定后打印 valid_roi看看有效区域占全画面的比例如果低于 60%就该考虑调整两个相机的安装角度而不是硬靠标定拉回来。4. 多种相机模型与标定板选型与适配4.1 相机模型针孔、鱼眼、全向怎么选OpenCV 默认的 calibrateCamera 用的是针孔模型适合视角不太大的普通镜头。针孔模型的畸变用多项式展开对广角镜头也能拟合但超过 120 度视场后边缘畸变太厉害多项式就会失效这时候需要鱼眼模型。OpenCV 的 fisheye 模块用的是 Kannala-Brandt 等距投影模型畸变系数是四个多项式参数拟合大广角更稳。如果再往上是折反射全景相机或者多个相机拼接的环视系统就要用全向模型即 OpenCV 的 omnidir 模块。这个模型把相机光心、镜面反射和像素点统一到一条射线上可以描述鱼眼、反射镜等多种配置。这个项目源码里通过一个相机模型参数切换底层实现我拆包时看到接口大概是 cv2.fisheye.calibrate 和 cv2.omnidir.calibrate 都包了一层。选型原则很简单视角小于 100 度优先针孔鱼眼镜头或云台相机用鱼眼模型全景拼接或底盘环视用全向模型。不要用针孔模型去标鱼眼镜头否则即使重投影误差看起来不大图像边缘的畸变校正后依然会有明显弯曲。4.2 标定板类型棋盘格、圆形网格、ArUco各有优劣棋盘格是最常见的标定板角点检测稳定亚像素细化容易缺点是需要看到完整的棋盘格一旦边缘被遮挡检测就失败。圆形网格标定板用 cv2.findCirclesGrid对图像模糊和光照变化的容忍度更高适合广角镜头因为圆形在透视下变成椭圆但中心依然能提取。缺点是圆的排序有时候会不稳定需要设置正确的对称或非对称网格类型。ArUco 标定板是我个人比较喜欢的方案。它由多个独立标记组成就算一部分被遮挡剩下的标记也能提供足够多的点。而且 ArUco 自带编码可以自动识别标记 ID这样多块 ArUco 板可以拼成一个大虚拟标定板提高标定精度。这个项目里应该也支持 ArUco通过标定板类型参数切换。提示圆形网格标定时grid_size 是圆点的行列数而且必须区分 CALIB_CB_SYMMETRIC_GRID 和 CALIB_CB_ASYMMETRIC_GRID。非对称网格相邻行的圆心错开半个间距用 SYMMETRIC 去检测会失败。4.3 本项目的模型与标定板支持情况这个项目标题里写了“支持多种相机模型多种标定板”实际使用中我建议你把相机模型和标定板组合先确定下来再改配置文件。我拆这个包时看到它的目录里一般会有标定板生成脚本、单目标定脚本、双目标定脚本和校验脚本模型和标定板通过 yaml 或 json 配置传入。没有必要把每种模型都跑一遍关键是找到适合你场景的那一组。比如你的项目用的是普通 USB 摄像头那就选棋盘格针孔模型如果是双目深度相机原厂已经标过内参你可以只做双目标定更新外参如果是鱼眼全景相机那就用圆形网格或 ArUco鱼眼模型。配置文件不改直接跑示例可能没问题但一旦你自己的摄像头分辨率和示例图像不一致就要注意 image_size 参数是写死的还是从图像读的。我见过不少源码把图像尺寸硬编码在标定脚本里换个摄像头就重投影误差飙升这个要留意。4.4 相机模型和标定板选型参考表场景推荐相机模型推荐标定板原因常规监控/测量针孔棋盘格算法最成熟角点稳定大广角/鱼眼鱼眼模型圆形网格/ArUco边缘畸变拟合更好圆形中心提取在边缘更稳双目测距针孔或原厂内参棋盘格或 ArUco需要左右角点严格对应棋盘格更容易配准全景/环视全向模型ArUco支持部分遮挡多板拼接这个表不是死的。很多人一开始用棋盘格针孔模型后来发现边缘误差大换到 ArUco鱼眼就好了。建议你标完一次后拿一张标定板放在画面不同位置做反投影误差大的区域重新优化模型。5. 避坑指南标定常见的五个翻车现场5.1 角点检测失败图像过曝或反光现象findChessboardCorners 返回 False或者打印“未找到完整棋盘格”换了十几张图只有两三张能用。原因标定板表面反光、光照不均或棋盘格与背景对比度太低。白色区域过曝后内角点附近会粘连检测算法无法定位。解决用哑光纸或磨砂 PET 打印标定板贴到平板后用两张 LED 灯从左右 45 度照射避免直射反光。如果还是不行把棋盘格拍大一点让单个方格在图像里至少占 20 个像素。也可以降低曝光时间或者给标定板加一层偏振片消除镜面反射。这个问题的直观后果是可用图像数量不足标定结果自然不稳定。5.2 标定结果重投影误差很大现象ret 值在 0.5 以上甚至超过 1 像素。换了更多图像也降不下来。原因采集的图像没有覆盖整个视场或者所有棋盘格都正对相机。另一种可能是标定板本身不平整角点实际位置与理想平面偏差很大。还有一种常见误用pattern_size 写错把 13×9 写成了 12×8导致内部角点顺序错乱重投影误差自然很大。解决重新采集保证棋盘格出现在画面四角和边缘同时加入俯仰和旋转角度。检查标定板是否贴在刚性平面上。如果 pattern_size 不确定可以用 cv2.drawChessboardCorners 可视化每张图的检测结果看看角点是否按行排列。打印棋盘格时建议关掉打印机的缩放比例否则实际方格尺寸会和设计值不一致虽然单目内参不太受影响但双目外参 T 的尺度会偏。5.3 双目标定后极线不平行现象用 stereoRectify 校正后左右图拼接显示同一个特征点纵向差了好几个像素无法做视差搜索。原因双目标定输入的左右角点不是同一时刻的同一块标定板。比如左图用第 2 帧右图用第 3 帧因为标定板有微小移动联合优化时外部会引入偏差。另外左右摄像头分辨率或内部裁剪不一致也会导致极线几何异常。解决确保左右同步采集使用硬件触发或软件同时采集。双目图像采集时左右图像序号必须一一对应。如果左右分辨率不一样先用 resize 统一到同一尺寸后再标定。极线不平行还有一个常见原因是 stereoRectify 的 alpha 设置不当alpha1 会保留边缘拉伸区域让极线看起来不直可以先试 alpha0只看有效区域。5.4 鱼眼镜头用了针孔模型现象用普通标定流程标鱼眼相机重投影误差看着不大但校正后图像边缘的直线还是弯的。原因针孔模型的多项式畸变模型对超大视场拟合能力不够。鱼眼镜头的光学投影可能是等距投影或正交投影针孔模型无法描述。解决更换为 cv2.fisheye 模块。fisheye.calibrate 的输入方式类似但畸变系数只有 4 个重投影误差计算方式也不同。如果项目支持多种模型直接改配置文件就能切。另外鱼眼标定需要更多图像覆盖边缘尤其要包含标定板位于画面四个角落的情况否则边缘畸变参数没有足够的约束。如果换鱼眼模型后边缘仍然弯可以考虑改用全向模型它对反射式镜头的描述更通用。5.5 标定结果每次都不一样现象同一相机、同一标定板连续标两次得到的 fx 和主点差了很多重投影误差都不大但测距结果差异明显。原因标定的局部最优解不唯一图像集不同或角点检测有亚像素抖动都会让结果漂移。特别是图像数量少于 10 张时内参矩阵的病态程度很高。解决固定使用同一套采集规范和初始化外参增加图像到 20 张以上。如果项目对精度要求高可以采集 5 组不同光照条件各 20 张标定后取 fx、fy、cx、cy 的中位数。也可以给 calibrateCamera 传入 flags比如 CALIB_FIX_PRINCIPAL_POINT 或 CALIB_FIX_ASPECT_RATIO限制主点和焦距的自由度让解更稳定。从那以后我每次换环境都会重新标定不再复用旧参数。6. 从标定到应用验证标定结果的三个进阶技巧标定不是把相机内参算出来就完事真正落地前一定要做验证。我常用的三个技巧是重投影误差双阈值、极线校验、深度反算。重投影误差双阈值是指不只盯着平均误差还要看最大误差和误差分布。内参标定完成后把所有图像的角点重投影误差算出来按图像序号画一张图。如果某张图的误差明显高于其他图说明那张图可能在移动中拍糊了或角点提取失败。这个项目里的校验脚本通常会把这个结果输出成 csv 或 png方便定位问题。极线校验收敛到实际应用。双目标定完成后可以用 cv2.computeCorrespondEpilines 计算左右图像对应点的极线并画在图上检查极线是否水平。更简单的方法是把校正后的左右图像直接 hstack 拼接如果行对准那么左右图像的同一条边界线应该上下对齐。我习惯放一个竖直标尺在画面里人眼一看就知道有没有几像素偏移。如果项目里有校验脚本直接跑一遍拼接图就能判断双目标定的质量。最后一个技巧是深度反算。双目校正后用视差图和 Q 矩阵计算三维坐标把它和已知距离对比。这里要注意Q 矩阵的第 3 行第 4 列通常和基线 T 有关系如果你使用的是标定板方格边长作为尺度那么深度单位也是那个边长。实际测距时需要把 objp 的真实尺寸传入或者用实际距离重新缩放否则深度值会是个比例数不是米。我自己吃过一次亏有一次标定后重投影误差 0.08 像素以为万事大吉结果双目测距在 3 米远的物体偏了 10 厘米。排查下来发现是标定板方格边长输入错了标定板实际是 20mm 格我写成了 19mm导致外参 T 的尺度全偏了。从那以后我每次标定完都强制走一遍“反投影误差分布极线拼接深度反算”三件套再也不会被一个低误差数字骗过去。希望帮到你。本文还有配套的精品资源点击获取
返回列表