目的
编程实现一个图像处理算法,本文实现了边缘检测算法。
工具
Visual Studio、C++。
代码
注意:文件路径要使用"C:\\..."或"C:/",不要使用"C:\",因为"\"在C++中是转义符号。
注意:OpenCV版本为"3.4.16",FFMPEG版本为"gyan.dev -> ffmpeg-6.1.1-full-build-shared.7z"。
注意:OpenCV、FFMPEG需要在Visual Studio中安装。
#include <opencv2/opencv.hpp>
#include <iostream>
#include <stack>
using namespace cv;
using namespace std;
// 非极大值抑制
void nonMaxSuppression(const Mat& gradientMagnitude, const Mat& gradientDirection, Mat& suppressed)
{
suppressed = Mat::zeros(gradientMagnitude.size(), CV_32F);
for (int i = 1; i < gradientMagnitude.rows - 1; ++i)
{
for (int j = 1; j < gradientMagnitude.cols - 1; ++j)
{
float angle = gradientDirection.at<float>(i, j);
float mag = gradientMagnitude.at<float>(i, j);
float mag1 = 0, mag2 = 0;
// 标准化角度到 [0,180) 范围
angle = fmod(angle, 180);
if ((angle >= 0 && angle < 22.5) || (angle >= 157.5 && angle < 180))
{
mag1 = gradientMagnitude.at<float>(i, j - 1);
mag2 = gradientMagnitude.at<float>(i, j + 1);
}
else if (angle >= 22.5 && angle < 67.5)
{
mag1 = gradientMagnitude.at<float>(i - 1, j + 1);
mag2 = gradientMagnitude.at<float>(i + 1, j - 1);
}
else if (angle >= 67.5 && angle < 112.5)
{
mag1 = gradientMagnitude.at<float>(i - 1, j);
mag2 = gradientMagnitude.at<float>(i + 1, j);
}
else if (angle >= 112.5 && angle < 157.5)
{
mag1 = gradientMagnitude.at<float>(i - 1, j - 1);
mag2 = gradientMagnitude.at<float>(i + 1, j + 1);
}
if (mag >= mag1 && mag >= mag2)
{
suppressed.at<float>(i, j) = mag;
}
// 只保留最大值
}
}
}
// 双阈值划分和边缘跟踪
void hysteresisThreshold(const Mat& suppressed, Mat& edgeImage, double lowThreshold, double highThreshold)
{
edgeImage = Mat::zeros(suppressed.size(), CV_8U);
stack<Point> edgePoints;
// 标记强边缘
for (int i = 1; i < suppressed.rows - 1; ++i)
{
for (int j = 1; j < suppressed.cols - 1; ++j)
{
float value = suppressed.at<float>(i, j);
if (value >= highThreshold)
{
edgeImage.at<uchar>(i, j) = 255;
edgePoints.push(Point(j, i));
// 栈先进后出
}
}
}
// 边缘跟踪
while (!edgePoints.empty())
{
Point p = edgePoints.top();
edgePoints.pop();
for (int dx = -1; dx <= 1; ++dx)
{
for (int dy = -1; dy <= 1; ++dy)
{
int x = p.x + dx;
int y = p.y + dy;
if (x >= 0 && x < edgeImage.cols && y >= 0 && y < edgeImage.rows)
{
if (edgeImage.at<uchar>(y, x) == 0 && suppressed.at<float>(y, x) >= lowThreshold)
{
edgeImage.at<uchar>(y, x) = 255;
edgePoints.push(Point(x, y));
}
// 在强边缘点邻接的像素点搜索,若未被标记为边缘点且大于低阈值,则标记为边缘点
}
}
}
}
}
int main()
{
VideoCapture mp4Captured("待处理的视频文件的位置");
if (!mp4Captured.isOpened())
{
cerr << "ERROR: cannot open video file" << endl;
return -1;
}
// 显示结果
while (true)
{
Mat inputImage;
if(!mp4Captured.read(inputImage))
{
cerr << "ERROR: cannot read frame from video file" << endl;
}
Mat grayImage;
cvtColor(inputImage, grayImage, COLOR_BGR2GRAY);
// 高斯滤波
cv::Mat gaussianImage;
GaussianBlur(grayImage, gaussianImage, Size(5, 5), 1.4, 1.4, BORDER_DEFAULT);
// Sobel算子求梯度
cv::Mat gradX, gradY, gradImage;
Sobel(gaussianImage, gradX, CV_32F, 1, 0, 3, 1, 0, BORDER_DEFAULT);
Sobel(gaussianImage, gradY, CV_32F, 0, 1, 3, 1, 0, BORDER_DEFAULT);
// 计算梯度幅值和方向
cv::Mat gradientMagnitude, gradientDirection;
magnitude(gradX, gradY, gradientMagnitude);
phase(gradX, gradY, gradientDirection, true);
// 转换梯度图像以显示
convertScaleAbs(gradX, gradX);
convertScaleAbs(gradY, gradY);
addWeighted(gradX, 0.5, gradY, 0.5, 0, gradImage);
// 非极大值抑制
cv::Mat nonMaxSuppressed;
nonMaxSuppression(gradientMagnitude, gradientDirection, nonMaxSuppressed);
// 双阈值划分和边缘跟踪
cv::Mat edgeImage;
double highThreshold = 150;
double lowThreshold = 50;
hysteresisThreshold(nonMaxSuppressed, edgeImage, lowThreshold, highThreshold);
// Canny函数
cv::Mat cannyImage;
Canny(inputImage, cannyImage, lowThreshold, highThreshold, 3, false);
imshow("原图", inputImage);
imshow("灰度化", grayImage);
imshow("高斯滤波", gaussianImage);
imshow("非极大值抑制", nonMaxSuppressed);
imshow("梯度", gradImage);
imshow("边缘检测", edgeImage);
imshow("使用Canny算法进行边缘检测", cannyImage);
char key = cv::waitKey(30);
if (key == 27)
break;
}
return 0;
}