当前位置: 首页 > news >正文

使用 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) textTophat(A)=A(AcircB)
    $$
  • 黑帽变换: “闭运算”结果与原图像的差。可以有效分离出黑暗的斑点和划痕。
    textBlack−hat(A)=(AbulletB)−A\\text{Black-hat}(A) = (A \\bullet B) - A textBlackhat(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 进行划痕检测提供一个良好的起点。

http://www.dtcms.com/a/285842.html

相关文章:

  • jQuery最新js文件下载教程
  • Django母婴商城项目实践(五)
  • Python 使用期物处理并发(使用concurrent.futures模块下载)
  • 黑马Node.js全套入门教程,nodejs新教程含es6模块化+npm+express+webpack+promise等_ts对象笔记
  • MISRA C-2012准则之指针类型转换
  • build.log中的is not a subdirectory of和ScanSourceDirectories函数的关系
  • 「Java案例」方法重装求不同类型数的立方
  • MySql:索引,结构
  • Leetcode 04 java
  • cocosCreator2.4 Android 输入法遮挡
  • JAVA中StringBuilder类,StringJoiner类构造函数方法简单介绍
  • C语言基础:数组练习题
  • Zabbix安装-Server
  • 【JS笔记】Java Script学习笔记
  • 【C语言进阶】题目练习(2)
  • react控制react Popover组件显示隐藏
  • Vue3 中使用 Element Plus 实现自定义按钮的 ElNotification 提示框
  • WAF 能防御哪些攻击?
  • logback日志控制服务器日志输出
  • Leetcode刷题营第三十三题:对称二叉树
  • Gitee 远程库多人如何协作?
  • gitlab-runner配置问题记录
  • hive分区表临时加载日批数据文件
  • TapData 出席 2025 MongoDB 用户大会新加坡站,分享构建实时统一数据平台最佳实践
  • day24 力扣93.复原IP地址 力扣78.子集 力扣90.子集II
  • 【基座模型】Qwen3报告总结
  • 告别 addEventListener
  • effective python 条款11 学会对序列做切片
  • 人脸检测算法——SCRFD
  • 智能Agent场景实战指南 Day 16:Agent记忆系统设计