一、作用:
sobel算子是图像边缘检测的最重要的算子之一。
Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息,边缘定位精度不够高。当对精度要求不是很高时,是一种较为常用的边缘检测方法。
二、原理:
Sobel算子输入的图像是灰度图,所以正常图片进行sobel算子提取边缘前,先要把图片转成灰度图。
边缘是指在图像上像素灰度变化最显著的地 方,边缘检测算子则利用图像边缘灰度的突变来检 测边缘。Sobel算子包含两组3×3的滤波器,分别对水平及垂直方向上的边缘敏感。
水平方向和垂直方向的Sobel算子分别如下:
具体与图像的运算如下:
然后分别用这两个算子对输入图像进行卷积操作,得到x,y方向上各点的卷积结果,即垂直梯度Gx和水平梯度Gy。
然后作一个平方和运算:
或者:
如果某点梯度 G 大于某一阈值,则认为该点(x,y)为边缘点。
代码:
python:
# coding=utf-8
import cv2
img = cv2.imread("D:/fangzi.jpg", 0)
cv2.imshow('origin',img)
#Sobel函数求完导数后会有负值,还有会大于255的值。而原图像是uint8,即8位无符号数,
#所以Sobel建立的图像位数不够,会有截断。因此要使用16位有符号的数据类型,即cv2.CV_16S。
x = cv2.Sobel(img, cv2.CV_16S, 1, 0)#垂直方向梯度
y = cv2.Sobel(img, cv2.CV_16S, 0, 1)#水平方向梯度
absX = cv2.convertScaleAbs(x) #转回原来的uint8格式,否则图像无法显示。
absY = cv2.convertScaleAbs(y) #转回原来的uint8格式,否则图像无法显示。
#两个方向的梯度加起来形成最终的梯度
dst = cv2.addWeighted(absX, 1, absY, 1, 0)
cv2.imshow("absX", absX)
cv2.imshow("absY", absY)
cv2.imshow("Result", dst)
cv2.waitKey(0)
效果:
输入:
垂直方向:
水平方向:
最终结果:
这张图可以太简单了,我们来看看复杂的:
垂直方向:
水平方向:
最终结果:
c++:
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
Mat m_img = imread("D:\\fangzi.jpg");
Mat src(m_img.rows, m_img.cols, CV_8UC1, Scalar(0));
cvtColor(m_img, src, CV_RGB2GRAY); //转成灰度图
Mat dstImage(src.rows, src.cols, CV_8UC1, Scalar(0));
for (int i = 1; i < src.rows - 1; i++)
{
for (int j = 1; j < src.cols - 1; j++)
{
dstImage.data[i*dstImage.step + j] = sqrt((src.data[(i - 1)*src.step + j + 1] //step 为图象像素行的实际宽度
+ 2 * src.data[i*src.step + j + 1]
+ src.data[(i + 1)*src.step + j + 1]
- src.data[(i - 1)*src.step + j - 1] - 2 * src.data[i*src.step + j - 1]
- src.data[(i + 1)*src.step + j - 1])*(src.data[(i - 1)*src.step + j + 1]
+ 2 * src.data[i*src.step + j + 1] + src.data[(i + 1)*src.step + j + 1]
- src.data[(i - 1)*src.step + j - 1] - 2 * src.data[i*src.step + j - 1]
- src.data[(i + 1)*src.step + j - 1]) + (src.data[(i - 1)*src.step + j - 1] + 2 * src.data[(i - 1)*src.step + j]
+ src.data[(i - 1)*src.step + j + 1] - src.data[(i + 1)*src.step + j - 1]
- 2 * src.data[(i + 1)*src.step + j]
- src.data[(i + 1)*src.step + j + 1])* (src.data[(i - 1)*src.step + j - 1] + 2 * src.data[(i - 1)*src.step + j]
+ src.data[(i - 1)*src.step + j + 1] - src.data[(i + 1)*src.step + j - 1]
- 2 * src.data[(i + 1)*src.step + j]
- src.data[(i + 1)*src.step + j + 1]));
}
}
Mat grad_y(src.rows, src.cols, CV_8UC1, Scalar(0));
{
for (int i = 1; i < src.rows - 1; i++)
{
for (int j = 1; j < src.cols - 1; j++)
{
grad_y.data[i*grad_y.step + j] = abs((src.data[(i - 1)*src.step + j + 1]
+ 2 * src.data[i*src.step + j + 1]
+ src.data[(i + 1)*src.step + j + 1]
- src.data[(i - 1)*src.step + j - 1] - 2 * src.data[i*src.step + j - 1]
- src.data[(i + 1)*src.step + j - 1]));
}
}
}
Mat grad_x(src.rows, src.cols, CV_8UC1, Scalar(0));
{
for (int i = 1; i < src.rows - 1; i++)
{
for (int j = 1; j < src.cols - 1; j++)
{
grad_x.data[i*grad_x.step + j] = sqrt((src.data[(i - 1)*src.step + j - 1] + 2 * src.data[(i - 1)*src.step + j]
+ src.data[(i - 1)*src.step + j + 1] - src.data[(i + 1)*src.step + j - 1]
- 2 * src.data[(i + 1)*src.step + j]
- src.data[(i + 1)*src.step + j + 1])* (src.data[(i - 1)*src.step + j - 1] + 2 * src.data[(i - 1)*src.step + j]
+ src.data[(i - 1)*src.step + j + 1] - src.data[(i + 1)*src.step + j - 1]
- 2 * src.data[(i + 1)*src.step + j]
- src.data[(i + 1)*src.step + j + 1]));
}
}
}
imshow("origin", src);
imshow("gradient", dstImage);
imshow("Vertical gradient", grad_y);
imshow("Horizontal gradient", grad_x);
waitKey(0);
return 0;
}
效果:
输入:
水平梯度:
垂直梯度:
最终结果:
优缺点:
优点:计算简单,速度很快;
缺点:计算方向单一,对复杂纹理的情况显得乏力;
直接用阈值来判断边缘点欠合理解释,会造成较多的噪声点误判。