使用 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 复制代码
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. 图像预处理

预处理的目的是增强划痕特征,并消除噪声干扰。

  • 灰度转换: 颜色信息对于划痕检测通常不是必需的,转换为灰度图可以简化计算。
  • 高斯模糊: 使用高斯滤波器可以平滑图像,去除随机噪声,避免其被误检为划痕。
cpp 复制代码
#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) 变换对于提取比周围环境更亮或更暗的小区域非常有效。

  • 顶帽变换 : 原图像与"开运算"结果的差。可以有效分离出明亮的斑点和划痕。
    t e x t T o p − h a t ( A ) = A − ( A c i r c B ) \\text{Top-hat}(A) = A - (A \\circ B) textTop−hat(A)=A−(AcircB)

    t e x t B l a c k − h a t ( A ) = ( A b u l l e t B ) − 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. 结果分析与可视化

最后一步是在原始图像上将检测到的划痕标识出来。我们可以通过查找二值图像中的轮廓来实现。

```cpp
// ... 接上文

// 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;

完整代码示例

cpp 复制代码
#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 进行划痕检测提供一个良好的起点。

相关推荐
插件开发5 小时前
在VS2019编辑器环境中使用c++打造window服务程序基础框架详细步骤
c++·编辑器·服务程序
東雪木5 小时前
Java 基础语法与核心数据类型 专属复习笔记
java·开发语言·笔记·java面试
ch.ju5 小时前
Java程序设计(第3版)第四章——方法的重载
java·开发语言
ch.ju5 小时前
Java Programming Chapter 4——Overloading of method
java·开发语言
Teable任意门互动5 小时前
拆解 Teable 背后研发主体,开源多维表格平台实力与落地案例
开发语言·开源·excel·飞书·开源软件
吃好睡好便好5 小时前
用直接输入的方式创建矩阵
开发语言·人工智能·学习·线性代数·算法·matlab·矩阵
NiKick5 小时前
理解C++中的构造函数如何影响对象初始化
开发语言·c++
海上彼尚5 小时前
Nodejs也能写Agent - 9.Mastra篇 - Mastra客户端
开发语言·前端·javascript·人工智能·node.js
2401_833269305 小时前
Java异常处理入门
java·开发语言
极客小云5 小时前
【用 Go 写一个统一的 LLM Token 统计库:tokencalc 的设计与实现】
开发语言·后端·golang