使用 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 进行划痕检测提供一个良好的起点。

相关推荐
皮卡蛋炒饭.26 分钟前
初识C++——开启新旅途
开发语言·c++
菜还不练就废了40 分钟前
7.19 Java基础 | 异常
java·开发语言
GalaxyPokemon1 小时前
全局变量与局部变量的对比
开发语言·c++
Xxtaoaooo1 小时前
手撕Spring底层系列之:注解驱动的魔力与实现内幕
java·开发语言·后端开发·spring框架·原理解析
阿斯卡码1 小时前
安装 llama-cpp-python 的CPU和GPU方法
开发语言·python·llama
远望樱花兔2 小时前
【Java】【力扣】101.对称二叉树
java·开发语言·leetcode
落笔映浮华丶2 小时前
蓝桥杯零基础到获奖-第3章 C++ 变量和常量
c++·蓝桥杯
西猫雷婶3 小时前
python学智能算法(二十三)|SVM-几何距离
开发语言·人工智能·python·算法·机器学习·支持向量机
星逝*3 小时前
Java实战:实时聊天应用开发(附GitHub链接)
java·开发语言·python
神仙别闹3 小时前
基于C#+SQlite开发(WinForm)个人日程管理系统
开发语言·jvm·c#