使用 C++ 和 OpenCV 进行表面划痕检测
使用 C++ 和 OpenCV 进行表面划痕检测
在工业自动化生产中,产品表面的质量控制至关重要。划痕作为一种常见的表面缺陷,其检测是许多领域(如金属、玻璃、塑料制造)质量保证流程中的一个关键环节。本文将介绍如何使用 C++ 和强大的计算机视觉库 OpenCV 来实现一个基本的表面划痕检测算法。
核心思路
划痕通常在图像中表现为具有以下一个或多个特征的区域:
- 高对比度的线性结构:划痕区域的像素强度通常会与其周围背景有明显的差异。
- 局部梯度突变:在划痕的边缘,像素值会发生急剧变化。
- 特定的形态特征:划痕通常是细长的。
基于这些特征,我们可以设计一个图像处理流程来定位并标识出这些划痕。一个常见的处理流程包括:图像预处理、核心特征提取和缺陷标识。
环境准备
在开始之前,请确保你已经配置好了 C++ 开发环境,并正确安装了 OpenCV 库。
- IDE: Visual Studio, CLion, VS Code with C++ extension, etc.
- 编译器: GCC, MSVC, Clang
- OpenCV: 从 OpenCV 官网 下载并配置到你的项目中。
CMakeLists.txt 示例 (推荐使用 CMake 管理项目):
cmake_minimum_required(VERSION 3.10)
project(ScratchDetection)set(CMAKE_CXX_STANDARD 14)# --- 寻找OpenCV ---
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})# --- 添加可执行文件 ---
add_executable(ScratchDetection main.cpp)# --- 链接OpenCV库 ---
target_link_libraries(ScratchDetection ${OpenCV_LIBS})
实现步骤与代码
我们将通过一个具体的例子来展示如何检测金属表面上的划痕。
(注意:请将 example-scratch.jpg
替换为你的实际图片路径)
1. 图像预处理
预处理的目的是增强划痕特征,并消除噪声干扰。
- 灰度转换: 颜色信息对于划痕检测通常不是必需的,转换为灰度图可以简化计算。
- 高斯模糊: 使用高斯滤波器可以平滑图像,去除随机噪声,避免其被误检为划痕。
#include <iostream>
#include <opencv2/opencv.hpp>int main() {// 1. 读取源图像cv::Mat src = cv::imread("path/to/your/image.jpg");if (src.empty()) {std::cerr << "Error: Could not read the image." << std::endl;return -1;}// 2. 转换为灰度图cv::Mat gray;cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);// 3. 高斯模糊以减少噪声cv::Mat blurred;cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);// ... 后续处理
}
2. 核心算法:形态学操作与阈值处理
对于对比度明显的划痕,我们可以使用图像阈值和形态学操作的组合来提取。特别是,顶帽(Top-hat) 和 黑帽(Black-hat) 变换对于提取比周围环境更亮或更暗的小区域非常有效。
- 顶帽变换: 原图像与“开运算”结果的差。可以有效分离出明亮的斑点和划痕。
textTop−hat(A)=A−(AcircB)\\text{Top-hat}(A) = A - (A \\circ B) textTop−hat(A)=A−(AcircB)
$$ - 黑帽变换: “闭运算”结果与原图像的差。可以有效分离出黑暗的斑点和划痕。
textBlack−hat(A)=(AbulletB)−A\\text{Black-hat}(A) = (A \\bullet B) - A textBlack−hat(A)=(AbulletB)−A
$$我们将结合这两种方法。
// ... 接上文// 4. 形态学操作
// 创建一个用于形态学操作的结构元素(内核)
// 矩形内核可能更适合检测线性划痕
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(15, 3)); cv::Mat tophat, blackhat;// 顶帽操作,用于检测亮划痕
cv::morphologyEx(blurred, tophat, cv::MORPH_TOPHAT, kernel);// 黑帽操作,用于检测暗划痕
cv::morphologyEx(blurred, blackhat, cv::MORPH_BLACKHAT, kernel);// 可以将两者结合,或者根据实际情况选择其一
// 这里我们假设划痕主要是暗色的,所以主要使用 blackhat
// 如果亮暗划痕都可能存在,可以将 tophat 和 blackhat 相加
cv::Mat combined = blackhat; // 或者 cv::add(tophat, blackhat);// 5. 图像增强和二值化
// 对形态学变换后的结果进行阈值处理,得到二值图像
cv::Mat thresholded;
cv::threshold(combined, thresholded, 40, 255, cv::THRESH_BINARY);// 可选:进行一些形态学闭操作,连接断开的划痕区域
cv::Mat closingKernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7));
cv::morphologyEx(thresholded, thresholded, cv::MORPH_CLOSE, closingKernel);
参数调整说明:
getStructuringElement
中的Size
:内核的尺寸非常关键。对于细长的划痕,应使用一个方向上长,另一个方向上短的内核(如Size(15, 3)
)。尺寸需要根据实际图像中划痕的粗细进行调整。threshold
中的thresh
值(这里是40
):这个阈值决定了划痕的敏感度。值越低,越容易将微弱的瑕疵检测出来,但也可能引入更多噪声。可以通过cv::THRESH_OTSU
方法让程序自动寻找最优阈值。
3. 结果分析与可视化
最后一步是在原始图像上将检测到的划痕标识出来。我们可以通过查找二值图像中的轮廓来实现。
// ... 接上文// 6. 查找轮廓
std::vector<std::vector<cv::Point>> contours;
cv::findContours(thresholded, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);// 7. 绘制轮廓
// 在原始图像上绘制红色的边界框来标识划痕
for (size_t i = 0; i < contours.size(); ++i) {// 可以根据轮廓的面积或长宽比进行一次过滤,排除过小的噪声点double area = cv::contourArea(contours[i]);if (area > 50) { // 过滤掉面积小于50的轮廓cv::drawContours(src, contours, static_cast<int>(i), cv::Scalar(0, 0, 255), 2);// 或者绘制一个包围矩形// cv::Rect boundingBox = cv::boundingRect(contours[i]);// cv::rectangle(src, boundingBox, cv::Scalar(0, 0, 255), 2);}
}// 8. 显示结果
cv::imshow("Source Image", src);
cv::imshow("Gray Image", gray);
cv::imshow("Detected Scratches (Binary)", thresholded);
cv::imshow("Final Result", src);cv::waitKey(0);
return 0;
完整代码示例
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>int main(int argc, char** argv) {// --- 1. 参数检查和图像加载 ---if (argc != 2) {std::cout << "Usage: " << argv[0] << " <image_path>" << std::endl;return -1;}cv::Mat src = cv::imread(argv[1]);if (src.empty()) {std::cerr << "Error: Could not read the image from " << argv[1] << std::endl;return -1;}// --- 2. 预处理 ---cv::Mat gray;cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);cv::Mat blurred;cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);// --- 3. 核心检测算法 ---// 使用形态学黑帽操作来突出暗色划痕// 内核尺寸需要根据实际划痕的尺度来调整cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(25, 5));cv::Mat blackhat;cv::morphologyEx(blurred, blackhat, cv::MORPH_BLACKHAT, kernel);// --- 4. 二值化 ---cv::Mat thresholded;// 使用大津法自动确定最佳阈值cv::threshold(blackhat, thresholded, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);// --- 5. 形态学后处理 ---// 使用闭操作连接断续的划痕cv::Mat closingKernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(10, 10));cv::morphologyEx(thresholded, thresholded, cv::MORPH_CLOSE, closingKernel);// --- 6. 查找并绘制轮廓 ---std::vector<std::vector<cv::Point>> contours;cv::findContours(thresholded, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);cv::Mat result = src.clone();for (size_t i = 0; i < contours.size(); ++i) {double area = cv::contourArea(contours[i]);// 过滤掉面积过小的区域,防止噪声干扰if (area > 100) {cv::drawContours(result, contours, static_cast<int>(i), cv::Scalar(0, 0, 255), 2);}}// --- 7. 显示结果 ---cv::imshow("Source Image", src);cv::imshow("Blackhat Transform", blackhat);cv::imshow("Binary Mask", thresholded);cv::imshow("Detected Scratches", result);cv::waitKey(0);cv::destroyAllWindows();return 0;
}
总结与展望
本文介绍了一种基于形态学变换的划痕检测方法。该方法简单、直观,对于背景相对均匀、划痕对比度明显的场景非常有效。
方法的局限性:
- 参数敏感: 形态学内核的尺寸和阈值需要根据具体应用场景进行仔细调整。
- 复杂背景失效: 如果背景纹理复杂,该方法可能会产生大量误报。
- 光照敏感: 不均匀的光照会严重影响检测效果。
未来改进方向:
- 自适应阈值: 使用
cv::adaptiveThreshold
来处理光照不均的问题。 - 频域分析: 对于周期性纹理背景下的划痕,可以尝试使用傅里叶变换,在频域中进行滤波。
- 边缘检测: 结合 Canny 等边缘检测算子,利用划痕的梯度信息。
- 机器学习/深度学习: 对于更复杂和多变的场景,可以收集大量的正负样本,训练一个分类器(如 SVM)或一个深度学习模型(如 U-Net、YOLO)来实现更鲁棒和智能的缺陷检测。
希望这篇文章能为你使用 OpenCV 进行划痕检测提供一个良好的起点。