Qt 6.9.0 + OpenCV 4.10.0:Win10图像开发环境从零搭建

发布时间:2026/10/2 6:50:58
Qt 6.9.0 + OpenCV 4.10.0:Win10图像开发环境从零搭建 在Win10上折腾图像处理开发Qt 6.9.0配合OpenCV 4.10.0是我个人非常推荐的一套组合。Qt负责界面、事件循环和跨平台封装OpenCV负责图像算法和视觉处理两者配合起来既能快速做出带界面的工具又不至于在底层图像计算上重复造轮子。这篇文章会从零开始把我在Win10平台下配置这套开发环境、跑通第一个程序、再到做几个实用功能的过程中踩过的坑和总结出来的经验全部写出来适合刚入门计算机视觉方向、或者想在Qt项目里引入OpenCV的同学直接参考。环境配置部分有大量细节容易劝退新手但只要按步骤来基本一次就能跑通。1. 搭建前的准备工作系统清理与组件选择1.1 Win10系统基础环境检查与优化动手之前先把系统环境理顺能省掉后面大半的麻烦。我建议先确认你的Win10是64位版本因为OpenCV官方发布包的x64库在32位系统上是用不了的。确认方法很简单右键“此电脑”选“属性”在“系统类型”里能看到。另外建议把系统更新装到最新状态尤其是Visual C运行库相关补丁因为OpenCV 4.10.0的预编译DLL依赖这些运行库缺了会在程序启动时报0xc000007b之类的错误。编译类开发环境对杀毒软件比较敏感。Windows自带的实时防护会逐个扫描生成的中间文件和DLL项目大的时候编译速度肉眼可见地变慢甚至偶尔会把刚编译出来的exe当成可疑文件隔离掉。我个人的做法是开发期间把Windows安全中心的实时保护临时关掉或者至少把项目目录添加为排除项。关闭路径是“设置 — 隐私和安全性 — Windows安全中心 — 病毒和威胁防护 — 管理设置”。另外电源计划也建议切到“高性能”模式免得CPU频率波动导致编译时长忽高忽低。1.2 Qt 6.9.0安装在线安装器与套件选择Qt 6.9.0是Qt 6系列里的新版本C17已经完全成为默认标准对OpenCV这种第三方库的适配也比较友好。安装时建议直接到Qt官网下载在线安装器它会自动拉取需要的组件。Qt账户登录这一步需要耐心另外安装组件时要注意选择不需要把全套模块都装上否则占用空间非常恐怖。在“选择组件”页面里重点勾选以下内容Qt 6.9.0节点下选择对应的编译器套件我建议安装MSVC 2019/2022 64-bit如果不想装Visual Studio也可以勾选MinGW 11.2.0 64-bit套件。“Additional Libraries”里记得勾选Qt Image Formats这样后续显示OpenCV处理过的图像时对各种格式兼容性会更好。“Developer and Designer Tools”里勾选CMake和Ninja后面如果用CMake方式构建项目会用到即使现在不用后面学深了也逃不掉。确定编译器套件时最核心的决策是你打算用MSVC还是MinGW。这两个工具链在Windows上不兼容对方的编译产物。如果选MSVCOpenCV预编译库可以直接用官方提供的vc16/vc15版本如果选MinGWOpenCV官方在这些版本里不直接提供MinGW格式的预编译库可能需要自己用CMake重新编译麻烦不少。所以我个人强烈建议如果你不是对MinGW有特别的执念就直接用MSVC套件。这也是很多人在配置阶段反复折腾浪费时间的主要原因。1.3 OpenCV 4.10.0获取预编译包还是源码编译OpenCV 4.10.0的获取路径有两条一是从OpenCV官网Releases页面下载Windows自解压包约200多MB二是在GitHub上下载源代码后用CMake自行编译。对绝大多数入门到进阶阶段的开发者直接下载预编译包就够了别在编译上浪费时间。下载下来的opencv-4.10.0-windows.exe实际上是一个自解压程序双击后会解压出一个opencv文件夹。把它放到一个路径简单、没有中文和空格的目录下比如D:\ThirdParty\opencv后面配置路径时能省心很多。解压完成后目录结构大致是这样的build\include头文件OpenCV要用到的cv.hpp等都在这里build\x64\vc16\bin运行时要加载的DLLopencv_world410d.dll是Debug版opencv_world410.dll是Release版build\x64\vc16\lib链接时用的.lib文件Debug版是opencv_world410d.libRelease版是opencv_world410.lib很多教程说要把build\x64\vc16\bin加入系统PATH环境变量我建议不要只图省事加系统PATH而是后面把DLL直接放到exe同目录或者通过Qt Creator的运行环境设置来指定这样项目的可移植性更好后面部署到别的机器也方便。2. 开发环境配置让Qt Creator正确识别OpenCV2.1 环境变量配置与目录整理虽然我不建议把OpenCV的bin目录加进系统PATH但把目录整理规范、把基础路径记牢是必须的。我自己的习惯是长期开发之前先建一个统一的第三方库目录比如D:\ThirdParty\opencv再在Qt Creator的“工具 — 选项 — 环境 — 系统”里添加一个名为OPENCV_DIR的自定义变量指向D:\ThirdParty\opencv\build。设置这个变量的好处是后面在.pro文件或者CMakeLists.txt里引用时可以用类似$$(OPENCV_DIR)的语法不用写死一长串绝对路径。如果哪天OpenCV版本升级了或者换到别的目录只需要改这一个地方就行项目代码不用大动。这个习惯我推荐所有做C/Qt开发的读者都养成不光是OpenCVFFmpeg、CUDA这类第三方库都适用。2.2 在.pro文件中完成头文件与库的链接用Qt Creator新建项目时如果选择qmake构建系统那么所有第三方库的配置都在.pro文件里完成。以我的项目为例.pro文件里关于OpenCV的配置部分长这样# 指定OpenCV头文件路径 INCLUDEPATH $$(OPENCV_DIR)/include # 链接OpenCV库路径 LIBS -L$$(OPENCV_DIR)/x64/vc16/lib # 这里链接的是Release版本的OpenCV world库 # 如果要在Debug模式下调试需要把后面的410改成410d CONFIG(debug, debug|release) { LIBS -lopencv_world410d } else { LIBS -lopencv_world410 }这里有几个非常容易踩的坑。第一个坑是Debug和Release的库不能混用如果Debug模式下链接了Release版的opencv_world410.lib链接阶段可能不报错但运行时大概率崩溃。第二个坑是Qt 6.9.0默认会给项目生成两个构建目录Shadow BuildDebug构建目录会去链接Debug库Release构建目录链接Release库所以.pro文件里用CONFIG(debug, debug|release)来做条件区分是最稳妥的做法。还有一个更隐蔽的坑如果只写“LIBS -L$$(OPENCV_DIR)/x64/vc16/lib -lopencv_world410”qmake生成的Makefile会把编译和链接步骤都指向这个库。这种写法没问题但要注意在项目设置里“Build”页面下的Shadow Build目录不要包含中文和空格否则编译器在解析路径时会出一些非常奇怪的错误。2.3 使用CMake方式构建项目的配置写法如果你用的是Qt 6.9.0自带的CMake构建支持或者项目本身就是CMake工程那么配置OpenCV的方式略有不同。在CMakeLists.txt里需要先设置OpenCV的安装路径然后通过find_package找到库。我的做法是先在CMakeLists.txt顶部加一行set(OpenCV_DIR D:/ThirdParty/opencv/build)这里指向的路径要特别注意OpenCV的CMake配置文件不是在include或lib目录而是在build目录下的x64/vc16/lib子目录里文件名是OpenCVConfig.cmake。所以更精确的写法是set(OpenCV_DIR D:/ThirdParty/opencv/build/x64/vc16/lib)设置完成后通过find_package导入find_package(OpenCV REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS}) target_link_libraries(my_project PRIVATE ${OpenCV_LIBS})如果find_package始终找不到OpenCV可以直接用手动指定头文件和库文件的方式。总之不管是.pro还是CMake核心思路是一样的把OpenCV的头文件目录告诉编译器把OpenCV的库目录和库文件名告诉链接器。只要这两个路径对了后续写代码就顺了。3. 第一个OpenCV程序从读图到显示3.1 创建Qt Widgets项目并设计界面环境配置完成之后咱们来写第一个真正能跑起来的程序。打开Qt Creator选择“New Project — Application — Qt Widgets Application”项目名称可以叫OpenCVTest构建系统选择qmake或者CMake都行先跑通一个后面再切换另一种。在生成好的界面文件里界面上只需要放两个核心控件一个QLabel用于显示图像一个QPushButton用于触发选择图片的操作。布局上建议把按钮放上面QLabel放下面并让QLabel的缩放模式设置为KeepAspectRatio这样不同尺寸的图片都能完整显示。界面的代码可以在Design模式直接拖拽完成生成的ui_mainwindow.h会在编译时自动加上。为了在按钮点击事件里响应OpenCV调用需要在MainWindow类中添加一个私有槽函数名字可以叫on_btnOpen_clicked这个命名规则是Qt的autoconnect机制编译器会自动把ui里btnOpen这个按钮的clicked信号连接到这个槽函数非常方便。3.2 核心代码加载图像并转为可显示格式按钮点击事件的核心功能是弹出文件选择对话框选择一个图像文件用OpenCV的imread读入然后转成QImage最后在QLabel上显示。代码如下void MainWindow::on_btnOpen_clicked() { // 弹出文件选择对话框过滤掉非图片格式 QString filePath QFileDialog::getOpenFileName( this, 选择一张图片, ., Images (*.png *.jpg *.bmp *.jpeg)); if (filePath.isEmpty()) return; // 用OpenCV读取图像默认是BGR三通道 cv::Mat mat cv::imread(filePath.toStdString()); if (mat.empty()) { QMessageBox::warning(this, 提示, 图像加载失败); return; } // BGR转RGB因为Qt显示需要的是RGB顺序 cv::Mat rgbMat; cv::cvtColor(mat, rgbMat, cv::COLOR_BGR2RGB); // 将cv::Mat包装为QImage QImage image(rgbMat.data, rgbMat.cols, rgbMat.rows, static_castint(rgbMat.step), QImage::Format_RGB888); // 深拷贝避免mat释放后image悬空 QImage imgCopy image.copy(); // 显示到QLabel上 ui-labelImage-setPixmap(QPixmap::fromImage(imgCopy)); ui-labelImage-setScaledContents(false); }这段代码看着长但逻辑并不复杂。大多数初学者在这里遇到的最大问题不是程序逻辑而是混淆了cv::Mat和QImage两个类型。cv::Mat的像素数据是连续存储在内存里的QImage也是类似的存储结构所以理论上可以共享同一块内存。但需要注意QT中QImage构造函数的传入参数必须包含stride每行字节数否则当图像的宽度不是4字节对齐时显示出来会出现斜切和花屏的怪相。上面代码中用了rgbMat.step来指定stride这是最标准的做法。3.3 深入理解cv::Mat与QImage的转换前面代码里cv::Mat转QImage用了三步先BGR转RGB再包装QImage最后copy。很多人不理解为什么一定要转RGB因为OpenCV的imread读取彩色图像时默认通道顺序是BGR蓝绿红而Qt的QImage在Format_RGB888格式下期望的数据顺序是RGB。如果不做转换直接按RGB888解释BGR数据图像中蓝色和红色就会互换整个人物或风景的颜色会严重失真看起来蓝色天空变成橘红色。还有一点值得注意QImage的Format_RGB888格式对每行数据有对齐要求有些图像的行字节数不是4的倍数这时如果直接传入rgbMat.data而不告诉QImage实际的strideQImage会按默认4字节对齐去解析结果就是图像像被撕开一样错位排布。解决方案就是在构造函数里显式传入rgbMat.step作为第四项参数这一点务必记住。最后一步的深拷贝image.copy()也非常关键。因为QImage构造函数只是引用传递它并不拥有mat的数据所有权。当函数返回后cv::Mat对象析构释放内存而QImage还在引用一块已经释放的内存程序后面访问数据极大概率崩溃。虽然Qt可能有文档说明某些情况下会做深拷贝但为了稳妥手动copy是最保险的做法。3.4 用QLabel显示图像的几种姿势代码里用setPixmap把QImage设置到QLabel上这里也有两个常见问题值得展开聊。第一个是图像和QLabel尺寸不一致时的显示体验如果不做处理大图会被直接截断小图会显示得偏小。经验做法是在放大缩小图片之前通过QImageReader读取原始图像尺寸然后用QLabel的sizeHint或动态调整窗口大小来适配。简单点就直接把QLabel的setScaledContents设为trueUI属性里叫scaledContents让图片自动缩放填满QLabel但这样会破坏宽高比最好配合上文提到的KeepAspectRatio缩放模式使用。第二个问题是图像缩放导致的质量下降。如果需要在QLabel里对小图放大显示缩放方式建议使用平滑变换先将QImage转换为QPixmap再用QPixmap::scaled配合Qt::SmoothTransformation模式缩放。直接靠QLabel拉伸会看到明显的马赛克和锯齿视觉效果很差QPixmap pix QPixmap::fromImage(imgCopy); QPixmap scaledPix pix.scaled(ui-labelImage-size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); ui-labelImage-setPixmap(scaledPix);处理好这两个显示问题第一个程序就算真正跑通了。能做到这一步你已经有能力把OpenCV处理完的图像显示到Qt界面上整个链路里最难啃的骨头也已经啃下来了。4. 常用功能实战摄像头采集与图像处理4.1 实时摄像头画面显示入门程序跑通之后下一步最有意思的需求就是打开摄像头做实时显示。OpenCV处理摄像头采集主要依靠cv::VideoCapture而Qt这边需要引入QTimer来定时刷新画面这样才能形成流畅的视频流。思路其实很简单先创建一个QTimer对象设置其超时时间为50毫秒左右对应20帧左右并在每次超时时从VideoCapture读取一帧转换后显示到界面上。需要注意摄像头资源是全局唯一的打开后要妥善管理结束时会话。核心代码逻辑大概是这样// 打开摄像头0 cv::VideoCapture cap(0); if (!cap.isOpened()) { QMessageBox::critical(this, 错误, 无法打开摄像头); return; } // 每隔50ms抓取一帧 QTimer *timer new QTimer(this); connect(timer, QTimer::timeout, this, []() { cv::Mat frame; cap frame; if (frame.empty()) return; cv::Mat rgbFrame; cv::cvtColor(frame, rgbFrame, cv::COLOR_BGR2RGB); QImage image(rgbFrame.data, rgbFrame.cols, rgbFrame.rows, static_castint(rgbFrame.step), QImage::Format_RGB888); ui-labelImage-setPixmap(QPixmap::fromImage(image.copy())); }); timer-start(50);这段代码跑起来后你会发现问题来了画面会有明显的卡顿和延迟。原因在于QTimer的触发频率和摄像头帧率不一定匹配而且每次转换和拷贝都要花费时间。我后来把定时器间隔调到33毫秒再把显示部分优化了一下为了减少拷贝还尝试过让QImage直接引用frame的数据但在Qt事件循环中如果上一个画面还没有绘制完成就开始处理下一帧就会造成资源竞争。所以我的建议是用一个互斥锁保护frame访问并且把图像拷贝放在业务线程中界面线程只负责显示已经就绪的新帧。等入门之后再进阶多线程但现阶段用QTimer加copy已经足够顺畅。4.2 图像灰度化、模糊与边缘检测摄像头能实时显示其实就已经有了做很多视觉应用的底座。最常见的图像处理三件套就是灰度化、高斯模糊和边缘检测。以边缘检测为例经典流程是先转为灰度图再用高斯模糊降噪最后用Canny算子提取边缘。代码非常简洁cv::Mat gray, blurred, edges; cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 1.5); cv::Canny(blurred, edges, 50, 150);这里需要解释一个很多人问过的问题为什么Canny之前一定要先做高斯模糊因为Canny算子是基于图像梯度来计算边缘的噪点会产生伪梯度让结果出现大量错误边缘。高斯模糊的作用是抑制这些高频噪声让边缘检测结果更干净。两个阈值参数50和150也不是随便拍的Canny内部会使用滞后双阈值梯度高于150的确定为边缘低于50的丢弃位于两者之间的像素只在与确定边缘相邻时才保留。实际参数要根据图像噪声水平动态调整光照变化大的场景下还可以用滑动条实时调节效果非常直观。在界面上我一般会把原始图和边缘检测结果分成两栏显示同时摆两张QLabel方便对比观察参数变化对结果的影响。这样做的好处是调参时能够即时反馈也方便将来扩展功能。4.3 视频文件读取与处理视频文件处理和摄像头采集在接口上非常相似都是VideoCapture。区别主要体现在输出逻辑上摄像头是实时流没法回退到上一帧视频文件需要控制播放速度、暂停、进度跳转和帧率匹配。最基础的实现方式同样是QTimer驱动每隔一定时间读取一帧然后处理显示。代码上只需要把打开摄像头那行换成cv::VideoCapture cap(D:/Videos/test.mp4);打开后可以通过cap.get(cv::CAP_PROP_FPS)获取视频的帧率再把QTimer的间隔设为1000.0 / fps这样播放速度和原视频保持一致。如果需要暂停只需timer-stop()要回到指定位置用cap.set(cv::CAP_PROP_POS_FRAMES, frameIndex)即可。这里有个小坑cap.get返回的是double类型直接把1000.0 / fps转换成int时如果帧率是29.97这种非整数四舍五入误差会导致每秒钟多读或少读一两帧时间久了画面和声音就开始漂移。比较稳妥的做法是用qRound取整并计算一个动态延时来补偿误差但这个只是对播放进度的精细要求入门阶段用固定延时问题不大。实际上视频文件处理这个场景很适合做视频抽帧工具、视频切片工具、或者基础的运动目标检测演示。如果你以后做行人检测、车牌识别大概率也是基于这样的视频流框架往里嵌算法。4.4 图像保存与批处理小工具做图像处理的时候经常需要把处理结果保存下来。OpenCV自带的imwrite函数是最直接的保存方式cv::imwrite(output.jpg, edges);需要注意imwrite的输出格式是根据文件扩展名自动判断的保存jpg时还能通过第三个参数控制压缩质量std::vectorint params; params.push_back(cv::IMWRITE_JPEG_QUALITY); params.push_back(95); // 95表示高质量低压缩 cv::imwrite(output.jpg, img, params);如果要扩展成批处理小工具思路也不复杂。遍历一个文件夹下所有图片对每张图执行灰度化、二值化、滤波等固定操作然后按命名规则输出到另一个目录。Qt里用QDir和QFileInfoList可以方便地遍历目录文件配合OpenCV的imread/imwrite形成处理管线。我早期做过一个给一批照片批量添加水印、批量调整亮度的小工具核心逻辑就是把这里讲的读图、处理后保存串起来也就一两百行代码的事。从学习角度讲自己动手做一个小工具比单纯看教程要记忆深刻得多。5. 高频问题排查让环境稳如老狗5.1 编译报错找不到头文件 / 无法打开包含文件新手最常见的报错是“fatal error: opencv2/opencv.hpp: No such file or directory”或者“无法打开包含文件: opencv2/opencv.hpp”。出现这个问题99%的原因是INCLUDEPATH路径配错了。请确认.pro或CMakeLists里指定的路径最终能够找到opencv2这个文件夹。注意opencv2文件夹的位置是build/include/opencv2而不是build/include/opencv2/opencv2。排查方法很直接先打开系统的文件资源管理器导航到设置的头文件路径下确认opencv2文件夹确实存在于该路径。如果路径中有中文、空格比如D:\软件\opencv build编译器解析时容易出错建议把所有第三方库目录统一放在无中文无空格路径下这也是我前面反复强调的原因。5.2 链接失败undefined reference / LNK2019编译通过但链接失败报错形如“undefined reference to cv::imread”或“LNK2019 unresolved external symbol”说明编译器找到了头文件但链接器没找到对应的库实现。常见原因有三个一是LIBS里忘了加-lopencv_world410或设置了错误的库文件名二是Debug和Release混用比如Debug模式下连了Release版的410库三是库文件和目标文件的架构不匹配比如64位程序链接了x86的OpenCV库。我个人遇到最多的就是Debug和Release混用问题。Qt Creator在调试模式CtrlR下编译时默认会定义DEBUG宏这时.pro里的CONFIG(debug, debug|release)分支会生效。如果你在.pro里硬编码只链接了opencv_world410不带d链接阶段虽然能过但运行时经常莫名其妙崩溃。反之亦然。所以最好的方式就是严格按照第2.2节的条件编译方式配置。5.3 运行时崩溃找不到opencv_world410.dll编译链接都通过了程序运行时却弹窗提示“无法启动此程序因为计算机中丢失opencv_world410.dll”。这是因为exe启动时需要加载OpenCV的DLL但在默认的DLL搜索路径里找不到它。解决办法有三种把D:\ThirdParty\opencv\build\x64\vc16\bin目录加入系统PATH环境变量然后重启Qt Creator。最简单但不够干净。把需要的opencv_world410.dll复制到exe生成目录下。注意Debug模式下需要复制opencv_world410d.dllRelease模式复制opencv_world410.dll两者名字差一个d很容易搞混。在Qt Creator的项目运行设置里添加“额外环境变量”或把DLL目录指定为工作目录。这个方式比复制DLL更灵活适合开发阶段。我长期实践下来最平衡的方案是开发阶段用第3种项目运行设置里指定PATH发布阶段用windeployqt工具配合手动拷贝OpenCV DLL到exe目录。这样既不用污染系统PATH项目换电脑时也不会因为少配环境变量而无法运行。5.4 图像颜色不对BGR与RGB的故事前面已经反复提到OpenCV默认的彩色图像通道顺序是BGR而Qt的QImage的RGB888格式是RGB顺序。如果显示出来的图像蓝红互换八成就是忘了在转换前调用cvtColor。这也是我第一次写这种转换代码时踩的坑当时显示了一朵红花结果花变成蓝色叶子变成橘红色第一反应还以为是摄像头坏掉了后来才意识到是通道顺序问题。颜色问题还有另一个变体如果用的是cv::imread的默认方式读取灰度图第二参数传1得到的Mat是单通道灰度图这时cvtColor不仅不会修正颜色反而可能把单通道复制成三通道导致显示结果变灰。正确的做法是判断mat.channels()单通道就直接以Format_Grayscale8构造QImage三通道才需要BGR转RGB。QImage img; if (mat.channels() 1) { img QImage(mat.data, mat.cols, mat.rows, static_castint(mat.step), QImage::Format_Grayscale8).copy(); } else { cv::Mat rgbMat; cv::cvtColor(mat, rgbMat, cv::COLOR_BGR2RGB); img QImage(rgbMat.data, rgbMat.cols, rgbMat.rows, static_castint(rgbMat.step), QImage::Format_RGB888).copy(); }5.5 运行卡顿与性能优化建议如果你在摄像头实时显示或者视频处理时感觉画面不流畅这个时候就该考虑优化了。多年前我的习惯是在UI线程里直接做读取、转换、显示全部流程Qt事件循环会被阻塞画面一卡一卡的CPU占用也高得吓人。优化思路一般从这几点入手避免不必要的图像拷贝。能让QImage引用Mat数据引用展示就不要每次都深拷贝只有确实需要跨线程或保存时才做copy。把耗时的cv::cvtColor、cv::Canny等操作放到子线程里执行。Qt里用QThread或QtConcurrent::run都能实现处理完再通过信号把结果传回UI线程更新界面。降低处理分辨率。摄像头拉流进来后先cv::resize到较小的尺寸处理速度会显著提升。很多视觉算法在小分辨率下的效果差距很小但速度能快几倍。使用OpenCV的UMat配合OpenCL硬件加速。前提是显卡驱动支持OpenCV编译时开启了WITH_OPENCL选项官方预编译包默认是支持的。写法上只要把cv::Mat换成cv::UMat并把图像读入方式稍作调整即可。我在一个视频处理小项目里测试过原始1080p视频不做任何优化时每帧耗时大约80毫秒只能跑12fps把分辨率降到720p放到子线程后耗时下降到20毫秒显示端稳定在30fps以上。最后再分享一个小技巧如果你正在用一款新版本的OpenCV不妨在代码开头打印一下版本信息确保自己编译链接的确实是你想要的那个版本而不是系统里残留的旧版本库std::cout OpenCV version: CV_VERSION std::endl;Qt 6.9.0加OpenCV 4.10.0这个组合在Win10下的配置虽然有一些繁琐的细节但整个流程理顺之后后续的开发和调试其实非常顺手。我在实际配置过程中踩过的坑基本都写在这篇文章里了希望你能少走一些弯路。如果你在跟着操作时碰到了其他奇怪的问题优先检查路径、架构、Debug/Release三个基本项大概率都能解决。