LoG的基本概念
LoG(Laplacian of Gaussian)算子是一种结合了高斯模糊和平滑处理的边缘检测方法。它通过先对图像应用高斯滤波器来去除噪声,然后再对结果应用拉普拉斯算子来检测边缘。LoG算子的主要优点是可以检测图像中的边缘和其他重要特征,同时减少噪声的影响。
LoG算子原理
LoG算子的基本思想是先使用高斯滤波器对图像进行平滑处理,然后再应用Laplacian算子。高斯滤波器可以去除图像中的噪声,而Laplacian算子则可以检测图像中的边缘。LoG算子可以看作是Laplacian算子与高斯核的卷积。
LoG算子结合了高斯平滑和拉普拉斯算子的优点。高斯平滑可以有效去除图像中的噪声,而拉普拉斯算子则能够检测图像中的边缘。
LoG算子的操作可以分为两步:
1.高斯平滑:使用高斯核对图像进行卷积,以去除噪声。
2.拉普拉斯算子:对平滑后的图像应用拉普拉斯算子,以检测边缘。
在OpenCV中,可以先使用cv::GaussianBlur对图像进行高斯平滑处理,然后使用cv::Laplacian进行拉普拉斯变换。但是,为了直接使用LoG算子,可以预先计算一个LoG核,然后使用cv::filter2D函数进行卷积操作。
示例代码1
以下是一个使用OpenCV C++实现LoG边缘检测的示例代码:
#include <opencv2/opencv.hpp>
#include <iostream>
int main(int argc, char** argv)
{
if (argc != 2) {
std::cout << "Usage: " << argv[0] << " <Image Path>" << std::endl;
return -1;
}
// 读取图像
cv::Mat src = cv::imread(argv[1], cv::IMREAD_GRAYSCALE);
if (!src.data) {
std::cout << "Error: Image cannot be loaded!" << std::endl;
return -1;
}
// 定义高斯核的尺寸和标准差
int kernel_size = 5; // 一般选择奇数
double sigma = 1.4; // 高斯核的标准差
// 构建LoG核
cv::Mat log_kernel = cv::getGaussianKernel(kernel_size, sigma, CV_32F);
cv::Mat log_kernel_2d;
log_kernel.multiply(log_kernel.t(), log_kernel_2d);
cv::Mat laplacian_kernel = (cv::Mat_<float>(3, 3) <<
0, 1, 0,
1, -4, 1,
0, 1, 0);
cv::filter2D(log_kernel_2d, log_kernel_2d, -1, laplacian_kernel);
// 对图像应用LoG核
cv::Mat dst;
cv::filter2D(src, dst, CV_32F, log_kernel_2d);
// 将结果转换为8位图像以便于显示
cv::Mat abs_dst;
cv::convertScaleAbs(dst, abs_dst);
// 显示原始图像和LoG边缘检测的结果
cv::namedWindow("Original Image", cv::WINDOW_AUTOSIZE);
cv::imshow("Original Image", src);
cv::namedWindow("LoG Edge Detection", cv::WINDOW_AUTOSIZE);
cv::imshow("LoG Edge Detection", abs_dst);
cv::waitKey();
return 0;
}
在这个示例中,我们首先构建了一个离散的LoG核,并使用该核对图像进行卷积操作。这样可以直接得到LoG边缘检测的结果。注意,实际应用中可能需要进一步调整高斯核的尺寸和标准差,以适应不同的图像和场景。
在OpenCV中,cv::Mat 类确实没有名为 multiply 的成员函数。你可能是想实现一个矩阵与其转置的乘积,但这应该通过其他方式来完成。cv::Mat 类有一个 mulTransposed 方法,但它并不适用于你的情况,因为你实际上是想做外积(即矩阵与其转置的乘积)。你可以使用点乘(Hadamard乘积)或者直接构造一个二维高斯核并应用Laplacian算子来构建LoG核。以下是修正后的代码:#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
cv::Mat createLogKernel(int kernel_size, double sigma) {
// 获取一维高斯核
cv::Mat kernel1D = getGaussianKernel(kernel_size, sigma, CV_32F);
// 通过点乘来构建二维高斯核
cv::Mat kernel2D = Mat::zeros(kernel_size, kernel_size, CV_32F);
for (int i = 0; i < kernel_size; ++i) {
for (int j = 0; j < kernel_size; ++j) {
kernel2D.at<float>(i, j) = kernel1D.at<float>(i) * kernel1D.at<float>(j);
}
}
// 构建Laplacian核
cv::Mat laplacian_kernel = (cv::Mat_<float>(3, 3) <<
0, 1, 0,
1, -4, 1,
0, 1, 0);
// 将二维高斯核与Laplacian核相乘
cv::Mat logKernel = Mat::zeros(kernel_size, kernel_size, CV_32F);
for (int i = 0; i < kernel_size; ++i) {
for (int j = 0; j < kernel_size; ++j) {
logKernel.at<float>(i, j) = kernel2D.at<float>(i, j) * laplacian_kernel.at<float>((i % 3), (j % 3));
}
}
return logKernel;
}
int main(int argc, char** argv) {
int kernel_size = 5;
double sigma = 1.4;
cv::Mat logKernel = createLogKernel(kernel_size, sigma);
// 输出LoG核
cout << "LoG Kernel:\n" << logKernel << endl;
return 0;
}
代码解释1. 获取一维高斯核:使用 getGaussianKernel 函数获得一维高斯核。2. 构建二维高斯核:通过将一维高斯核的每一个元素与其他所有元素相乘来构建二维高斯核。3. 构建Laplacian核:定义一个3x3的Laplacian核。4. 构建LoG核:通过将二维高斯核与Laplacian核相乘来构建LoG核。注意事项•外积:这里的二维高斯核是通过一维高斯核的外积来实现的,即每个元素与另一个向量中的每个元素相乘。•Laplacian核:Laplacian核是一个固定的3x3核,它用于计算二阶导数。•LoG核:通过将二维高斯核与Laplacian核相乘来构建LoG核。如果你只需要构建一个LoG核而不关心具体实现细节,可以直接使用OpenCV的 filter2D 函数结合 getGaussianKernel 和 Laplacian 算子来实现:#include <opencv2/opencv.hpp>
using namespace cv;
cv::Mat createLogKernel(int kernel_size, double sigma) {
// 获取一维高斯核
cv::Mat kernel1D = getGaussianKernel(kernel_size, sigma, CV_32F);
// 构建二维高斯核
cv::Mat kernel2D = kernel1D * kernel1D.t();
// 应用Laplacian算子
cv::Mat laplacianKernel = (cv::Mat_<float>(3, 3) <<
0, 1, 0,
1, -4, 1,
0, 1, 0);
cv::Mat logKernel;
cv::filter2D(kernel2D, logKernel, -1, laplacianKernel);
return logKernel;
}
这种方式更加简洁,但也需要保证 kernel_size 至少为3,否则 filter2D 可能会失败。
#include <opencv2/opencv.hpp>
#include <iostream>
int main(int argc, char** argv)
{
/*if (argc != 2) {
std::cout << "Usage: " << argv[0] << " <Image Path>" << std::endl;
return -1;
}*/
// 读取图像
cv::Mat src = cv::imread("07.jpg", cv::IMREAD_GRAYSCALE);
if (!src.data) {
std::cout << "Error: Image cannot be loaded!" << std::endl;
return -1;
}
// 定义高斯核的尺寸和标准差
int kernel_size = 5; // 一般选择奇数
double sigma = 1.4; // 高斯核的标准差
// 构建LoG核
//cv::Mat log_kernel = cv::getGaussianKernel(kernel_size, sigma, CV_32F);
//cv::Mat log_kernel_2d;
//log_kernel.multiply(log_kernel.t(), log_kernel_2d);
//cv::Mat laplacian_kernel = (cv::Mat_<float>(3, 3) <<
// 0, 1, 0,
// 1, -4, 1,
// 0, 1, 0);
//cv::filter2D(log_kernel_2d, log_kernel_2d, -1, laplacian_kernel);
// 获取一维高斯核
// cv::Mat kernel1D = getGaussianKernel(kernel_size, sigma, CV_32F);
cv::Mat log_kernel = cv::getGaussianKernel(kernel_size, sigma, CV_32F);
// 构建二维高斯核
//cv::Mat kernel2D = log_kernel * log_kernel.t();
cv::Mat log_kernel_2d = log_kernel * log_kernel.t();
// 应用Laplacian算子
cv::Mat laplacian_kernel = (cv::Mat_<float>(3, 3) <<
0, 1, 0,
1, -4, 1,
0, 1, 0);
// cv::Mat logKernel;
// cv::filter2D(log_kernel_2d, logKernel, -1, laplacian_kernel);
cv::filter2D(log_kernel_2d, log_kernel_2d, -1, laplacian_kernel);
// 对图像应用LoG核
cv::Mat dst;
cv::filter2D(src, dst, CV_32F, log_kernel_2d);
// 将结果转换为8位图像以便于显示
cv::Mat abs_dst;
cv::convertScaleAbs(dst, abs_dst);
// 显示原始图像和LoG边缘检测的结果
cv::namedWindow("Original Image", cv::WINDOW_NORMAL);
cv::imshow("Original Image", src);
cv::namedWindow("LoG Edge Detection", cv::WINDOW_NORMAL);
cv::imshow("LoG Edge Detection", abs_dst);
cv::waitKey();
return 0;
}
运行结果1
好像有错误!
示例代码2
以下是一个使用OpenCV C++实现LoG边缘检测的示例:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void detectEdgesWithLog(const Mat &src, Mat &dst, double sigma) {
// 首先应用高斯滤波器
Mat blurred;
GaussianBlur(src, blurred, Size(0, 0), sigma);
// 应用Laplacian算子
Mat laplacian;
Laplacian(blurred, laplacian, CV_16S);
// 将结果转换为8位无符号整数
convertScaleAbs(laplacian, dst);
}
int main(int argc, char** argv) {
// 加载图像
Mat img = imread("08.png", IMREAD_GRAYSCALE);
if (!img.data) {
cout << "Error opening image" << endl;
return -1;
}
// 设置高斯核的标准差
double sigma = 1.4;
// 检测边缘
Mat edges;
detectEdgesWithLog(img, edges, sigma);
// 显示结果
namedWindow("Original Image", WINDOW_NORMAL);
imshow("Original Image", img);
namedWindow("LoG Edges", WINDOW_NORMAL);
imshow("LoG Edges", edges);
waitKey(0);
destroyAllWindows();
return 0;
}
代码解释
1. 加载图像: 使用 imread 函数以灰度模式加载图像。
2. 高斯滤波: 使用 GaussianBlur 函数对图像进行高斯滤波处理,以去除噪声。
3. Laplacian算子: 使用 Laplacian 函数计算图像的Laplacian梯度。
4. 转换数据类型: 使用 convertScaleAbs 函数将16位有符号整数转换为8位无符号整数,以便于显示。
5. 显示结果: 使用 imshow 函数显示原始图像和LoG边缘检测的结果。
注意事项
•选择合适的高斯核大小: 在实际应用中,高斯核的大小和标准差(sigma)的选择会影响最终的边缘检测效果。较大的sigma值会检测到更大范围内的边缘,而较小的sigma值则会检测到更多的细节。
•参数调整: 可以尝试调整高斯核的标准差sigma来观察不同的边缘检测效果。
•性能考虑: LoG算子由于需要进行两次卷积操作(一次高斯滤波,一次Laplacian),因此计算量相对较大。在处理大尺寸图像时,可能会有一定的延迟。
运行结果2
示例代码3
#include <opencv2/opencv.hpp>
#include <iostream>
int main(int argc, char** argv)
{
/*if (argc != 2) {
std::cout << "Usage: " << argv[0] << " <Image Path>" << std::endl;
return -1;
}*/
// 读取图像
cv::Mat src = cv::imread("09.png", cv::IMREAD_GRAYSCALE);
if (!src.data) {
std::cout << "Error: Image cannot be loaded!" << std::endl;
return -1;
}
// 定义高斯核的尺寸和标准差
int kernel_size = 5; // 一般选择奇数
double sigma = 1.4; // 高斯核的标准差
// 使用高斯核进行平滑处理
cv::Mat blurred;
cv::GaussianBlur(src, blurred, cv::Size(kernel_size, kernel_size), sigma);
// 使用Laplacian算子进行边缘检测
cv::Mat dst;
cv::Laplacian(blurred, dst, CV_16S, 3); // 使用3x3的内核
// 将结果转换为8位图像以便于显示
cv::Mat abs_dst;
cv::convertScaleAbs(dst, abs_dst);
// 显示原始图像和LoG边缘检测的结果
cv::namedWindow("Original Image", cv::WINDOW_NORMAL);
cv::imshow("Original Image", src);
cv::namedWindow("LoG Edge Detection", cv::WINDOW_NORMAL);
cv::imshow("LoG Edge Detection", abs_dst);
cv::waitKey();
return 0;
}
解释
1.读取图像:从命令行参数读取图像路径,并将其加载为灰度图像。
2.高斯模糊:使用cv::GaussianBlur函数对图像进行高斯模糊处理,目的是减少图像中的噪声。这里定义了高斯核的尺寸和标准差。
3.拉普拉斯算子:对模糊后的图像应用拉普拉斯算子,使用cv::Laplacian函数。这里选择了一个3x3的内核大小。
4.转换为8位图像:因为拉普拉斯算子的结果可能是负值,我们需要将其转换为8位无符号整型图像以便于显示。使用cv::convertScaleAbs函数可以完成这个转换。
5.显示结果:使用cv::imshow函数分别显示原始图像和LoG边缘检测的结果。
参数调整
kernel_size:高斯核的尺寸,一般选择奇数,较大的尺寸会使得平滑效果更好,但也可能导致边缘变得模糊。
sigma:高斯核的标准差,决定了平滑的程度。较小的sigma值会保留更多的细节,但可能会引入更多的噪声;较大的sigma值会使图像更平滑,但可能会丢失一些细节。
运行结果3
实验代码4
#include "pch.h"
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/types_c.h>
#include <opencv2/highgui/highgui_c.h>
using namespace cv;
using namespace std;
//#pragma comment(lib,"opencv_world450d.lib")
//x,y方向联合实现获取高斯模板
void generateGaussMask(cv::Mat& Mask, cv::Size wsize, double sigma)
{
Mask.create(wsize, CV_64F);
int h = wsize.height;
int w = wsize.width;
int center_h = (h - 1) / 2;
int center_w = (w - 1) / 2;
double sum = 0.0;
double x, y;
for (int i = 0; i < h; ++i) {
y = pow(i - center_h, 2);
for (int j = 0; j < w; ++j) {
x = pow(j - center_w, 2);
//因为最后都要归一化的,常数部分可以不计算,也减少了运算量
double g = exp(-(x + y) / (2 * sigma*sigma));
Mask.at<double>(i, j) = g;
sum += g;
}
}
Mask = Mask / sum;
}
//按二维高斯函数实现高斯滤波
///
void GaussianFilter(cv::Mat& src, cv::Mat& dst, cv::Mat window)
{
int hh = (window.rows - 1) / 2;
int hw = (window.cols - 1) / 2;
dst = cv::Mat::zeros(src.size(), src.type());
//边界填充
cv::Mat Newsrc;
cv::copyMakeBorder(src, Newsrc, hh, hh, hw, hw, cv::BORDER_REPLICATE);//边界复制
//高斯滤波
for (int i = hh; i < src.rows + hh; ++i) {
for (int j = hw; j < src.cols + hw; ++j) {
double sum[3] = { 0 };
for (int r = -hh; r <= hh; ++r) {
for (int c = -hw; c <= hw; ++c) {
if (src.channels() == 1) {
sum[0] = sum[0] + Newsrc.at<uchar>(i + r, j + c) * window.at<double>(r + hh, c + hw);
}
else if (src.channels() == 3) {
cv::Vec3b rgb = Newsrc.at<cv::Vec3b>(i + r, j + c);
sum[0] = sum[0] + rgb[0] * window.at<double>(r + hh, c + hw);//B
sum[1] = sum[1] + rgb[1] * window.at<double>(r + hh, c + hw);//G
sum[2] = sum[2] + rgb[2] * window.at<double>(r + hh, c + hw);//R
}
}
}
for (int k = 0; k < src.channels(); ++k) {
if (sum[k] < 0)
sum[k] = 0;
else if (sum[k] > 255)
sum[k] = 255;
}
if (src.channels() == 1)
{
dst.at<uchar>(i - hh, j - hw) = static_cast<uchar>(sum[0]);
}
else if (src.channels() == 3)
{
cv::Vec3b rgb = { static_cast<uchar>(sum[0]), static_cast<uchar>(sum[1]), static_cast<uchar>(sum[2]) };
dst.at<cv::Vec3b>(i - hh, j - hw) = rgb;
}
}
}
}
//DOG高斯差分
///
void DOG1(cv::Mat &src, cv::Mat &dst, cv::Size wsize, double sigma, double k = 1.6)
{
cv::Mat Mask1, Mask2, gaussian_dst1, gaussian_dst2;
generateGaussMask(Mask1, wsize, k*sigma);//获取二维高斯滤波模板1
generateGaussMask(Mask2, wsize, sigma);//获取二维高斯滤波模板2
//高斯滤波
GaussianFilter(src, gaussian_dst1, Mask1);
GaussianFilter(src, gaussian_dst2, Mask2);
dst = gaussian_dst1 - gaussian_dst2 - 1;
cv::threshold(dst, dst, 0, 255, cv::THRESH_BINARY);
}
//DOG高斯差分--使用opencv的GaussianBlur
void DOG2(cv::Mat &src, cv::Mat &dst, cv::Size wsize, double sigma, double k = 1.6)
{
cv::Mat gaussian_dst1, gaussian_dst2;
//高斯滤波
cv::GaussianBlur(src, gaussian_dst1, wsize, k*sigma);
cv::GaussianBlur(src, gaussian_dst2, wsize, sigma);
dst = gaussian_dst1 - gaussian_dst2;
cv::threshold(dst, dst, 0, 255, cv::THRESH_BINARY);
}
int main()
{
cv::Mat src = cv::imread("jo.png");
if (src.empty()) {
return -1;
}
if (src.channels() > 1) cv::cvtColor(src, src, CV_RGB2GRAY);
cv::Mat edge1, edge2;
DOG1(src, edge1, cv::Size(7, 7), 2);
DOG2(src, edge2, cv::Size(7, 7), 2);
cv::namedWindow("src", CV_WINDOW_NORMAL);
imshow("src", src);
cv::namedWindow("My_DOG", CV_WINDOW_NORMAL);
imshow("My_DOG", edge1);
cv::namedWindow("Opencv_DOG", CV_WINDOW_NORMAL);
imshow("Opencv_DOG", edge2);
cv::waitKey(0);
return 0;
}