基本概念
Roberts算子是一种简单的一阶导数边缘检测算子,它通过计算图像在水平和垂直方向上的梯度来检测边缘。在OpenCV中,Roberts算子可以通过手动应用卷积核来实现。Roberts算子是一组2x2的小型滤波器,用于检测图像中的垂直和水平边缘。
Roberts算子掩模Roberts算子有两个掩模,分别用于检测水平和垂直方向上的边缘:
实现步骤
1. 应用掩模:将Roberts算子的掩模应用于图像。
2. 计算梯度:计算水平方向和垂直方向上的梯度。
3. 计算梯度幅度:通过水平梯度和垂直梯度的合成来得到最终的梯度幅度。
示例代码1
下面是基于C++的OpenCV代码示例,展示了如何使用Roberts算子来检测边缘。
步骤一:包含必要的头文件
首先,确保你的项目已经正确配置了OpenCV库,并且包含了必要的头文件。
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
步骤二:定义Roberts算子
Roberts算子的核如下所示:
Gx = [ 1 0
0 -1 ]
Gy = [ 0 1
-1 0 ]
在C++中,你可以通过创建一个Mat对象来表示这些核。
Mat kernelGx = (Mat_<float>(2, 2) <<
1, 0,
0, -1);
Mat kernelGy = (Mat_<float>(2, 2) <<
0, 1,
-1, 0);
步骤三:加载并准备输入图像
你需要读取一个图像,并将其转换为灰度图像以简化边缘检测任务。
Mat src = imread("path_to_your_image.jpg", IMREAD_GRAYSCALE);
if(src.empty())
{
std::cout << "Error: Image cannot be loaded!" << std::endl;
return -1;
}
步骤四:应用Roberts算子
由于Roberts算子的大小是2x2,我们需要特别注意边界条件。在OpenCV中,你可以使用filter2D函数来应用自定义的卷积核。这里我们分别应用Gx和Gy核。
Mat dstGx, dstGy;
// Apply the kernels to the source image.
filter2D(src, dstGx, CV_32F, kernelGx);
filter2D(src, dstGy, CV_32F, kernelGy);
// Convert back to CV_8UC1 (unsigned char) type for display.
Mat abs_dstGx, abs_dstGy;
convertScaleAbs(dstGx, abs_dstGx);
convertScaleAbs(dstGy, abs_dstGy);
步骤五:组合结果
为了获得完整的边缘检测结果,通常需要合并Gx和Gy的结果。
Mat edges;
addWeighted(abs_dstGx, 0.5, abs_dstGy, 0.5, 0, edges);
步骤六:显示结果
最后,你可以使用imshow函数来展示原始图像和边缘检测后的结果。
namedWindow("Original Image", WINDOW_AUTOSIZE);
imshow("Original Image", src);
namedWindow("Edges", WINDOW_AUTOSIZE);
imshow("Edges", edges);
waitKey(0);
以上就是使用OpenCV和C++实现Roberts算子边缘检测的一个基本示例。
请根据实际情况调整路径和参数。
示例代码
#include "pch.h"
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
int main()
{
Mat kernelGx = (Mat_<float>(2, 2) <<
1, 0,
0, -1);
Mat kernelGy = (Mat_<float>(2, 2) <<
0, 1,
-1, 0);
Mat src = imread("012.jpeg", IMREAD_GRAYSCALE);
if (src.empty())
{
std::cout << "Error: Image cannot be loaded!" << std::endl;
return -1;
}
Mat dstGx, dstGy;
// Apply the kernels to the source image.
filter2D(src, dstGx, CV_32F, kernelGx);
filter2D(src, dstGy, CV_32F, kernelGy);
// Convert back to CV_8UC1 (unsigned char) type for display.
Mat abs_dstGx, abs_dstGy;
convertScaleAbs(dstGx, abs_dstGx);
convertScaleAbs(dstGy, abs_dstGy);
Mat edges;
addWeighted(abs_dstGx, 0.5, abs_dstGy, 0.5, 0, edges);
namedWindow("Original Image", WINDOW_NORMAL);
imshow("Original Image", src);
namedWindow("Edges", WINDOW_NORMAL);
imshow("Edges", edges);
waitKey(0);
return 0;
}
运行结果1
filter2D
filter2D函数是OpenCV提供的一个非常强大的工具,用于在图像上应用任何类型的卷积操作。它可以用于实现多种图像处理功能,如模糊、锐化、边缘检测等。
下面详细介绍filter2D函数的用法以及其参数的意义。
函数原型
void filter2D(InputArray src, OutputArray dst, int ddepth,
InputArray kernel,Point anchor=Point(-1,-1),
double delta=0, int borderType=BORDER_DEFAULT);
参数说明
src: 输入图像。可以是多通道图像。
dst: 输出图像。它将具有与输入图像相同的尺寸和类型(除非指定不同的深度)。
ddepth: 指定输出图像的深度。如果设置为-1,则输出图像的深度与输入图像相同。否则,可以指定不同的深度,如CV_32F(32位浮点数)。
kernel: 卷积核(滤波器)。这是一个二维数组,用于定义卷积运算。核的大小通常是奇数,例如3x3或5x5,以便有一个中心点。
anchor: 卷积核相对于每个像素的锚点位置。默认情况下(Point(-1,-1)),锚点位于核的中心。你可以改变锚点的位置来控制卷积核相对于图像像素的作用方式。
delta: 可选的常数值,将在卷积操作后加到每一个像素上。
borderType: 边界处理类型。当卷积核覆盖图像的边界时,需要指定如何处理边界外的数据。OpenCV提供了多种边界处理方式,例如:
BORDER_CONSTANT: 使用常数值填充边界外区域。
BORDER_REPLICATE: 复制边界像素。
BORDER_REFLECT: 镜像反射边界。
BORDER_WRAP: 边界环绕(类似于纹理坐标)。
BORDER_REFLECT_101 或 BORDER_DEFAULT: 默认的边界反射方式。
示例代码2
下面是一个简单的例子,演示如何使用filter2D函数来应用一个3x3的Sobel核来检测水平边缘。
#include "pch.h"
#include <opencv2/opencv.hpp>
#include <iostream>
int main(int argc, char** argv)
{
// 加载图像
cv::Mat src = cv::imread("033.jpeg", cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cout << "Error: Image cannot be loaded!" << std::endl;
return -1;
}
// 定义Sobel核
cv::Mat kernel = (cv::Mat_<float>(3, 3) <<
-1, -2, -1,
0, 0, 0,
1, 2, 1);
// 创建输出图像
cv::Mat dst;
// 应用filter2D函数
cv::filter2D(src, dst, -1, kernel);
// 显示结果
cv::namedWindow("Original Image", cv::WINDOW_NORMAL);
cv::imshow("Original Image", src);
cv::namedWindow("Filtered Image", cv::WINDOW_NORMAL);
cv::imshow("Filtered Image", dst);
cv::waitKey(0);
return 0;
}
在这个例子中,我们使用了一个Sobel核来检测水平边缘。如果你想要检测垂直边缘,可以相应地改变核的值。此外,如果需要调整输出图像的深度,可以修改ddepth参数。例如,使用CV_32F可以得到浮点数输出,这有助于避免整数溢出的问题。
运行结果2
示例代码3
下面是一个使用OpenCV C++实现Roberts算子进行边缘检测的示例代码:
#include "pch.h"
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
// 定义Roberts算子掩模
static const float robertsHorizontal[] = { 1, 0, 0, -1 };// 定义Roberts交叉算子水平方向的内核
static const float robertsVertical[] = { 0, 1, -1, 0 };
void detectEdgesWithRoberts(const Mat &src, Mat &horizontal, Mat &vertical, Mat &magnitude)
{
// 将数据复制到一个std::vector中
std::vector<float> data(robertsHorizontal, robertsHorizontal + 4);
// 创建一个2x2的单通道浮点型矩阵
Mat kernelHorizontal = Mat(2, 2, CV_32F, data.data());
// 将数据复制到一个std::vector中
std::vector<float> data2(robertsVertical, robertsVertical + 4);
// 创建一个2x2的单通道浮点型矩阵
Mat kernelVertical = Mat(2, 2, CV_32F, data2.data());
// 水平方向的边缘检测
filter2D(src, horizontal, CV_32F, kernelHorizontal, Point(-1, -1), 0, BORDER_REPLICATE);
// 垂直方向的边缘检测
filter2D(src, vertical, CV_32F, kernelVertical, Point(-1, -1), 0, BORDER_REPLICATE);
//
// 计算梯度幅度
//magnitude = sqrt(horizontal.mul(horizontal) + vertical.mul(vertical));
//一下代码来代替
//在 OpenCV 中,cv::Mat 类没有提供 mul 成员函数用于矩阵元素级别的乘法。要执行逐元素的乘法,您应该使用 cv::multiply 函数。同时,您需要确保 magnitude 矩阵的类型适合进行平方和开方运算。
//使用 cv::multiply 函数来计算水平和垂直梯度的平方。
//使用 cv::sqrt 函数来计算平方和的平方根,得到梯度的幅度。
//使用 cv::convertScaleAbs 函数将结果转换为 8 位无符号整数格式。
//
// 创建一个临时矩阵来保存平方和
cv::Mat tmp;
// 计算水平和垂直梯度的平方和
cv::multiply(horizontal, horizontal, tmp);
cv::multiply(vertical, vertical, tmp, 1.0);
cv::sqrt(tmp, magnitude);
//
// 将结果转换为8位无符号整数
convertScaleAbs(magnitude, magnitude);
}
int main(int argc, char** argv)
{
/*if (argc != 2)
{
cout << "Usage: ./RobertsEdgeDetection <Image Path>" << endl;
return -1;
}*/
// 加载图像
Mat img = imread("27.png", IMREAD_GRAYSCALE);
if (!img.data)
{
cout << "Error opening image" << endl;
return -1;
}
// 初始化输出矩阵
Mat horizontal, vertical, magnitude;
// 执行Roberts边缘检测
detectEdgesWithRoberts(img, horizontal, vertical, magnitude);
// 显示结果
namedWindow("Original Image", WINDOW_NORMAL);
imshow("Original Image", img);
namedWindow("Roberts Horizontal", WINDOW_NORMAL);
imshow("Roberts Horizontal", horizontal);
namedWindow("Roberts Vertical", WINDOW_NORMAL);
imshow("Roberts Vertical", vertical);
namedWindow("Magnitude", WINDOW_NORMAL);
imshow("Magnitude", magnitude);
waitKey(0);
destroyAllWindows();
return 0;
}
代码解释
1. 定义Roberts算子掩模:使用静态数组来定义Roberts算子的两个掩模。
2. 边缘检测:使用 filter2D 函数分别计算水平方向和垂直方向上的边缘。
3. 计算梯度幅度:通过计算水平方向和垂直方向边缘的平方和的平方根来得到梯度幅度。
4. 转换数据类型:使用 convertScaleAbs 函数将浮点型数据转换为8位无符号整数,以便于显示。
5. 显示结果:使用 imshow 函数显示原始图像、水平方向边缘、垂直方向边缘以及最终的梯度幅度图。
注意事项
•噪声敏感性:Roberts算子对噪声非常敏感,因此在实际应用中,通常需要对图像进行平滑处理(如使用高斯滤波器)来减少噪声的影响。
•边界处理:在使用 filter2D 时,需要注意边界处理方式。在这个示例中,使用了 BORDER_REPLICATE 边界条件来处理边界像素。
•掩模大小:Roberts算子的掩模大小为2x2,这意味着它只考虑了图像中的局部信息,因此对于精细的边缘检测可能不够精确。
通过这个示例,你应该能够理解如何在OpenCV中使用C++实现Roberts算子进行边缘检测。
运行结果3
cv::Mat kernelHorizontal(2, 2, CV_32F, robertsHorizontal); 该行代码还是错误
如果 cv::Mat(2, 2, CV_32F, robertsHorizontal) 这样的构造方式仍然报错,可能是因为 OpenCV 的 cv::Mat 构造函数并不直接接受一个外部的浮点数数组作为参数。在这种情况下,您可以先将数据复制到一个 std::vector<float> 中,然后使用 cv::Mat 的构造函数从 std::vector 创建矩阵,或者手动创建一个 cv::Mat 并使用 cv::Mat::operator=() 或 cv::Mat::copyTo() 方法来填充数据。
以下是两种可能的解决方案:
解决方案 1: 使用 std::vector<float>
#include <opencv2/opencv.hpp>
#include <vector>
int main() {
// 定义Roberts交叉算子水平方向的内核
static const float robertsHorizontal[] = { 1, 0, 0, -1 };
// 将数据复制到一个std::vector中
std::vector<float> data(robertsHorizontal, robertsHorizontal + 4);
// 创建一个2x2的单通道浮点型矩阵
cv::Mat kernelHorizontal = cv::Mat(2, 2, CV_32F, data.data());
// 检查是否成功创建了内核
if (kernelHorizontal.empty()) {
std::cout << "Error: Matrix is empty!" << std::endl;
} else {
std::cout << "Kernel:\n" << kernelHorizontal << std::endl;
}
return 0;
}
解决方案 2: 手动创建并填充 cv::Mat
#include <opencv2/opencv.hpp>
int main() {
// 定义Roberts交叉算子水平方向的内核
static const float robertsHorizontal[] = { 1, 0, 0, -1 };
// 创建一个2x2的单通道浮点型矩阵
cv::Mat kernelHorizontal = cv::Mat::zeros(2, 2, CV_32F);
// 填充矩阵
kernelHorizontal.at<float>(0, 0) = robertsHorizontal[0];
kernelHorizontal.at<float>(0, 1) = robertsHorizontal[1];
kernelHorizontal.at<float>(1, 0) = robertsHorizontal[2];
kernelHorizontal.at<float>(1, 1) = robertsHorizontal[3];
// 检查是否成功创建了内核
if (kernelHorizontal.empty()) {
std::cout << "Error: Matrix is empty!" << std::endl;
} else {
std::cout << "Kernel:\n" << kernelHorizontal << std::endl;
}
return 0;
}
这两种方法都可以解决您的问题。选择哪种取决于您的具体需求和个人偏好。第一种方法更简洁一些,而第二种方法则提供了更多的控制权。
示例代码4
#include "pch.h"
#include <iostream>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include <opencv2\imgproc\types_c.h>
#include<iostream>
using namespace std;
using namespace cv;
//#pragma comment(lib,"opencv_world450d.lib")
//Roberts算子实现
Mat roberts(Mat srcImage)
{
Mat dstImage = srcImage.clone();
int nRows = dstImage.rows;
int nCols = dstImage.cols;
for (int i = 0; i < nRows - 1; i++) {
for (int j = 0; j < nCols - 1; j++) {
//根据公式计算
int t1 = (srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i + 1, j + 1))*
(srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i + 1, j + 1));
int t2 = (srcImage.at<uchar>(i + 1, j) -
srcImage.at<uchar>(i, j + 1))*
(srcImage.at<uchar>(i + 1, j) -
srcImage.at<uchar>(i, j + 1));
//计算g(x,y)
dstImage.at<uchar>(i, j) = (uchar)sqrt(t1 + t2);
}
}
return dstImage;
}
void main()
{
Mat srcImage = imread("356.jpeg");
if (!srcImage.data) {
cout << "falied to read" << endl;
system("pause");
return;
}
Mat srcGray;
cvtColor(srcImage, srcGray, CV_BGR2GRAY);
//高斯滤波
GaussianBlur(srcGray, srcGray, Size(3, 3),
0, 0, BORDER_DEFAULT);
Mat dstImage = roberts(srcGray);
namedWindow("源图", WINDOW_NORMAL);
namedWindow("边缘图", WINDOW_NORMAL);
imshow("源图", srcImage);
imshow("边缘图", dstImage);
waitKey(0);
}