图像边缘检测
图像边缘是指图像中灰度值发生急剧变化的位置,这些位置通常对应着物体的边界、不同区域的分界线等。图像边缘检测的目的就是找出这些灰度值突变的地方,提取出图像中物体的轮廓信息,为后续的图像分析、目标识别、图像分割等任务提供基础。边缘检测通过检测图像中局部强度变化来定位边缘,这些变化可能是由于物体的形状、光照变化、材质差异等因素引起的。
基于梯度的方法:
Sobel 算子:通过两个 3×3 的卷积核分别对图像进行水平和垂直方向的卷积,得到水平方向梯度 Gx 和垂直方向梯度 Gy,然后计算梯度幅值 G=Gx2+Gy2 和梯度方向 θ=arctan(GxGy)。该算子对噪声有一定的平滑作用,计算相对简单快速。
Prewitt 算子:同样使用两个 3×3 的卷积核分别计算水平和垂直方向的梯度,其原理与 Sobel 算子类似,但对噪声的平滑能力相对较弱。
基于二阶导数的方法:
Laplacian 算子:是一种二阶导数算子,它通过对图像进行二阶导数运算,在边缘处会产生过零点(从正到负或从负到正的变化点)。常用的 3×3 Laplacian 模板对图像进行卷积,根据过零点来确定边缘位置。由于其对噪声敏感,通常在使用前需要对图像进行平滑处理。
Canny 边缘检测:这是一种多阶段的边缘检测算法,具有较好的抗噪声性能和边缘定位精度。其步骤包括:
高斯滤波:对图像进行平滑处理,减少噪声的影响。
计算梯度:使用 Sobel 等算子计算图像的梯度幅值和方向。
非极大值抑制:在梯度方向上,只保留梯度幅值局部最大的点,抑制其他非边缘点,从而细化边缘。
双阈值检测与边缘连接:设置高阈值和低阈值,高于高阈值的点确定为边缘点,低于低阈值的点排除,介于两者之间的点根据其与已确定边缘点的连接情况来决定是否为边缘点。
Sobel 算子示例
bash
#include <QCoreApplication>
#include <QDebug>
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat image = cv::imread("1.png", cv::IMREAD_GRAYSCALE);
if (image.empty()) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
cv::Mat sobelx, sobely, sobel;
// 计算X方向梯度
cv::Sobel(image, sobelx, CV_64F, 1, 0, 3);
// 计算Y方向梯度
cv::Sobel(image, sobely, CV_64F, 0, 1, 3);
// 计算梯度幅值和方向
cv::convertScaleAbs(sobelx, sobelx);
cv::convertScaleAbs(sobely, sobely);
cv::addWeighted(sobelx, 0.5, sobely, 0.5, 0, sobel);
cv::imshow("Original Image", image);
cv::imshow("Sobel Edge Detection", sobel);
cv::waitKey(0);
return 0;
}

Laplacian 算子示例
bash
#include <QCoreApplication>
#include <QDebug>
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat image = cv::imread("D:/ljl/FPGA/QT_openCV_pro/pro_01/untitled_04/1.png", cv::IMREAD_GRAYSCALE);
if (image.empty()) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
cv::Mat laplacian;
// 计算Laplacian
cv::Laplacian(image, laplacian, CV_64F, 3);
cv::convertScaleAbs(laplacian, laplacian);
cv::imshow("Original Image", image);
cv::imshow("Laplacian Edge Detection", laplacian);
cv::waitKey(0);
return 0;
}

Canny 边缘检测示例
bash
#include <QCoreApplication>
#include <QDebug>
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat image = cv::imread("D:/ljl/FPGA/QT_openCV_pro/pro_01/untitled_04/1.png", cv::IMREAD_GRAYSCALE);
if (image.empty()) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
cv::Mat canny;
// Canny边缘检测
cv::Canny(image, canny, 50, 150);
cv::imshow("Original Image", image);
cv::imshow("Canny Edge Detection", canny);
cv::waitKey(0);
return 0;
}

图像分割
定义
图像分割旨在把一幅图像分成若干互不重叠的区域,并使这些区域对应于实际场景中的不同物体、部分或有意义的实体。每个分割出的区域内部具有一致性,而相邻区域之间具有显著的区别。例如,在一张自然风景图像中,可能分割出天空、山脉、河流、树木等不同区域;在医学图像中,可分割出不同的器官组织。
目的
简化图像表示:将复杂的图像转化为相对简单且更易于处理的多个部分,降低后续分析的复杂度。例如,在对卫星图像进行分析时,分割出不同的地貌类型(城市、农田、森林等),使对图像的理解和信息提取更加直观和高效。
目标识别与分析:通过分割出不同的目标物体,为目标识别、分类和测量提供基础。在工业检测中,分割出产品的各个部件,进而检测部件是否存在缺陷、测量部件的尺寸等。
图像理解:帮助计算机模拟人类视觉系统对图像内容的理解,是实现高级计算机视觉任务(如场景理解、自动驾驶中的环境感知等)的关键步骤。例如,自动驾驶系统通过图像分割识别道路、车辆、行人等元素,从而做出决策。
作用
计算机视觉:是许多计算机视觉任务(如目标检测、图像分类、语义分割、实例分割等)的预处理步骤。准确的图像分割能够提高这些任务的准确性和效率。例如,在人脸识别系统中,首先通过图像分割提取人脸区域,再进行特征提取和匹配,可提高识别精度。
医学图像处理:用于辅助医学诊断和手术规划。例如,在脑部 MRI 图像中分割出不同的脑组织,帮助医生检测肿瘤、分析病变区域的位置和大小;在手术规划中,分割出需要操作的器官和组织,为医生提供详细的解剖结构信息。
机器人视觉:使机器人能够理解周围环境,识别感兴趣的物体,规划运动路径。例如,服务机器人通过图像分割识别房间中的家具、障碍物等,从而在环境中自主导航。
遥感图像处理:在卫星遥感和航空遥感图像中,分割出不同的地物类型(如城市、水体、植被等),用于资源调查、土地利用监测、灾害评估等。例如,通过分割分析洪水前后的遥感图像,确定洪水淹没区域的范围和变化。
基本原理
图像分割基于图像中不同区域在灰度、颜色、纹理等特征上的差异。常见的原理包括:
基于灰度值:利用图像中不同区域的灰度分布特性。例如,在灰度图像中,物体与背景的灰度值往往不同,通过设定合适的阈值,可以将图像分为前景和背景两个区域。
基于颜色:在彩色图像中,不同物体或区域通常具有不同的颜色特征。通过将图像转换到合适的颜色空间(如 RGB、HSV 等),利用颜色的分布和差异进行分割。例如,在一幅水果图像中,通过颜色特征可以分割出不同颜色的水果。
基于纹理:不同物体或区域可能具有不同的纹理特征,如粗糙度、方向性等。通过纹理分析方法(如灰度共生矩阵、小波变换等)提取纹理特征,进而实现图像分割。例如,在检测布料表面缺陷时,正常区域和缺陷区域的纹理不同,可利用纹理特征进行分割。
方法分类
基于阈值的分割方法:
根据图像的灰度特性,选择一个或多个阈值,将图像的像素分为不同的类别。包括全局阈值法(如 Otsu 法),通过计算图像的灰度直方图自动确定一个最佳阈值;局部阈值法,根据图像局部区域的灰度特性自适应地确定阈值。
基于区域的分割方法:
将具有相似特征的像素合并为区域。例如,区域生长法,从一个或多个种子点开始,根据一定的相似性准则(如灰度相似性、颜色相似性等),将相邻的像素逐步合并到种子区域;区域分裂合并法,先将图像分成若干大的区域,然后根据相似性准则对区域进行分裂和合并操作,直到满足一定的终止条件。
基于边缘的分割方法:
检测图像中灰度值变化剧烈的边缘,通过连接这些边缘形成物体的轮廓,从而实现图像分割。常用的边缘检测算子有 Sobel 算子、Canny 算子等。例如,在一幅建筑物图像中,通过边缘检测提取建筑物的轮廓,进而分割出建筑物区域。
基于聚类的分割方法:
将图像中的像素视为数据点,根据它们的特征(如灰度、颜色等)进行聚类分析,将相似的像素聚为一类,实现图像分割。例如,K - means 聚类算法可应用于图像分割,将图像像素分为 K 个类别,每个类别对应一个分割区域。
基于深度学习的分割方法:
近年来,深度学习在图像分割领域取得了显著成果。基于卷积神经网络(CNN)的方法,如 U - Net、Mask R - CNN 等,能够自动学习图像的特征,实现高精度的图像分割。这些方法通过大量的标注数据进行训练,模型可以学习到不同物体或区域的特征表示,从而对图像进行准确分割。
固定阈值分割

bash
#include <QCoreApplication>
#include <QDebug>
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat image = cv::imread("D:/ljl/FPGA/QT_openCV_pro/pro_01/untitled_04/1.png", cv::IMREAD_GRAYSCALE);
if (image.empty()) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
cv::Mat thresholded;
// 固定阈值分割,127为阈值,255为最大值,THRESH_BINARY为阈值类型
cv::threshold(image, thresholded, 127, 255, cv::THRESH_BINARY);
cv::imshow("Original Image", image);
cv::imshow("Fixed Threshold Segmented Image", thresholded);
cv::waitKey(0);
return 0;
}
手动实现自适应阈值分割

bash
#include <QCoreApplication>
#include <QDebug>
#include <opencv2/opencv.hpp>
#include <iostream>
cv::Mat manualAdaptiveThreshold(const cv::Mat& image, int blockSize, double C) {
cv::Mat result = cv::Mat::zeros(image.size(), image.type());
int halfBlock = blockSize / 2;
for (int y = 0; y < image.rows; ++y) {
for (int x = 0; x < image.cols; ++x) {
int sum = 0;
int count = 0;
for (int i = -halfBlock; i <= halfBlock; ++i) {
for (int j = -halfBlock; j <= halfBlock; ++j) {
int ni = y + i;
int nj = x + j;
if (ni >= 0 && ni < image.rows && nj >= 0 && nj < image.cols) {
sum += image.at<uchar>(ni, nj);
count++;
}
}
}
double localThreshold = sum / count - C;
if (image.at<uchar>(y, x) > localThreshold) {
result.at<uchar>(y, x) = 255;
} else {
result.at<uchar>(y, x) = 0;
}
}
}
return result;
}
int main() {
cv::Mat image = cv::imread("D:/ljl/FPGA/QT_openCV_pro/pro_01/untitled_04/1.png", cv::IMREAD_GRAYSCALE);
if (image.empty()) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
cv::Mat manualAdaptive;
manualAdaptive = manualAdaptiveThreshold(image, 11, 2);
cv::imshow("Original Image", image);
cv::imshow("Manual Adaptive Threshold Segmented Image", manualAdaptive);
cv::waitKey(0);
return 0;
}
直接用 HSV 体系进行颜色分割
bash
#include <QCoreApplication>
#include <QDebug>
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat image = cv::imread("D:/ljl/FPGA/QT_openCV_pro/pro_01/untitled_04/1.png");
if (image.empty()) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
cv::Mat hsv;
cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);
// 定义蓝色的HSV范围
cv::Scalar lowerBlue(100, 100, 100);
cv::Scalar upperBlue(130, 255, 255);
cv::Mat mask;
cv::inRange(hsv, lowerBlue, upperBlue, mask);
cv::Mat result;
cv::bitwise_and(image, image, result, mask);
cv::imshow("Original Image", image);
cv::imshow("HSV Color Segmented Image", result);
cv::waitKey(0);
return 0;
}
grabCut 算法分割图像

bash
#include <QCoreApplication>
#include <QDebug>
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat image = cv::imread("D:/ljl/FPGA/QT_openCV_pro/pro_01/untitled_04/1.png");
if (image.empty()) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
cv::Mat mask(image.size(), CV_8UC1, cv::Scalar(cv::GC_BGD));
cv::Rect rect(100, 100, 300, 300);
cv::Mat bgModel, fgModel;
cv::grabCut(image, mask, rect, bgModel, fgModel, 5, cv::GC_INIT_WITH_RECT);
cv::compare(mask, cv::GC_PR_FGD, mask, cv::CMP_EQ);
cv::Mat result;
image.copyTo(result, mask);
cv::imshow("Original Image", image);
cv::imshow("GrabCut Segmented Image", result);
cv::waitKey(0);
return 0;
}
floodFill(漫水填充)分割

bash
#include <QCoreApplication>
#include <QDebug>
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat image = cv::imread("D:/ljl/FPGA/QT_openCV_pro/pro_01/untitled_04/1.png");
if (image.empty()) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
cv::Mat floodfill = image.clone();
cv::Point seedPoint(100, 100);
cv::Scalar newVal(255, 0, 0);
cv::Rect rect;
cv::floodFill(floodfill, seedPoint, newVal, &rect, cv::Scalar(50, 50, 50), cv::Scalar(50, 50, 50), cv::FLOODFILL_FIXED_RANGE);
cv::imshow("Original Image", image);
cv::imshow("Flood Fill Segmented Image", floodfill);
cv::waitKey(0);
return 0;
}
分水岭分割法

bash
#include <QCoreApplication>
#include <QDebug>
#include <QCoreApplication>
#include <QDebug>
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat image = cv::imread("D:/ljl/FPGA/QT_openCV_pro/pro_01/untitled_04/1.png");
if (image.empty()) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
cv::Mat gray, thresh;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
cv::threshold(gray, thresh, 0, 255, cv::THRESH_BINARY_INV + cv::THRESH_OTSU);
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
cv::Mat opening;
cv::morphologyEx(thresh, opening, cv::MORPH_OPEN, kernel, cv::Point(-1, -1), 2);
cv::Mat sure_bg;
cv::dilate(opening, sure_bg, kernel, cv::Point(-1, -1), 3);
cv::Mat dist_transform;
cv::distanceTransform(opening, dist_transform, cv::DIST_L2, 5);
cv::normalize(dist_transform, dist_transform, 0, 1.0, cv::NORM_MINMAX);
double maxVal;
cv::minMaxLoc(dist_transform, nullptr, &maxVal);
cv::Mat sure_fg;
cv::threshold(dist_transform, sure_fg, 0.7 * maxVal, 255, cv::THRESH_BINARY);
// 检查并转换sure_fg的数据类型为CV_8U
if (sure_fg.depth() != CV_8U) {
sure_fg.convertTo(sure_fg, CV_8U);
}
cv::Mat unknown;
cv::subtract(sure_bg, sure_fg, unknown, cv::Mat(), CV_8U);
cv::Mat markers;
cv::connectedComponents(sure_fg, markers);
markers = markers + 1;
markers.setTo(0, unknown);
cv::watershed(image, markers);
cv::Mat watershedResult = cv::Mat::zeros(image.size(), CV_8UC3);
for (int i = 0; i < markers.rows; i++) {
for (int j = 0; j < markers.cols; j++) {
if (markers.at<int>(i, j) == 1) {
watershedResult.at<cv::Vec3b>(i, j) = cv::Vec3b(255, 255, 255);
} else {
watershedResult.at<cv::Vec3b>(i, j) = cv::Vec3b(markers.at<int>(i, j) * 10 % 256, markers.at<int>(i, j) * 10 % 256, markers.at<int>(i, j) * 10 % 256);
}
}
}
cv::imshow("Original Image", image);
cv::imshow("Watershed Segmented Image", watershedResult);
cv::waitKey(0);
return 0;
}