简要描述
sobel算子主要用于获得数字图像的一阶梯度,常见的应用是边缘检测。
原理
算子使用两个3*3的矩阵(图1)算子使用两个3*3的矩阵(图1)去和原始图片作卷积,分别得到横向G(x)和纵向G(y)的梯度值,如果梯度值大于某一个阈值,则认为该点为边缘点
图1:卷积矩阵
图2:卷积运算
图像直接卷积实现Sobel:
//图像直接卷积实现Sobel
bool sobelEdge(const cv::Mat image, cv::Mat &result, uchar threshold)
{
CV_Assert(image.channels() == 1);
//初始化水平核因子
cv::Mat sobelx = (cv::Mat_<float>(3, 3) << 1, 0, -1, 2, 0, -2, 1, 0, -1);
//初始化垂直核因子
cv::Mat sobely = (cv::Mat_<float>(3, 3) << 1, 2, 1, 0, 0, 0, -1, -2, -1);
result = cv::Mat::zeros(image.rows - 2, image.cols - 2, image.type());
double graMag = 0;
for (int i = 1; i < image.rows - 1; i++)
{
for (int j = 1; j < image.cols - 1; j++)
{
float edgex = 0, edgey = 0;
//遍历计算水平与垂直梯度
for (int k = -1; k < 2; k++)
{
for (int p = -1; p < 2; p++)
{
edgex += (float)image.at<uchar>(k + i, p + j) * sobelx.at<float>(1 + k, 1 + p);
edgey += (float)image.at<uchar>(k + i, p + j) * sobely.at<float>(1 + k, 1 + p);
}
}
//计算梯度模长
graMag = sqrt(pow(edgex, 2) + pow(edgey, 2));
//二值化
result.at<uchar>(i - 1, j - 1) = ((graMag > threshold) ? 255 : 0);
}
}
return true;
}
运行结果:
非极大值抑制Sobel边缘实现:
//非极大值抑制实现Sobel竖直细化边缘
bool SobelVerEdge(cv::Mat image, cv::Mat &result)
{
CV_Assert(image.channels() == 1);
image.convertTo(image, CV_32FC1);
//水平方向的Sobel算子
cv::Mat sobelx = (cv::Mat_<float>(3, 3) << -0.125, 0, 0.125, -0.25, 0, 0.25, -0.125, 0, 0.125);
//卷积操作
cv::Mat ConResMat;
cv::filter2D(image, ConResMat, image.type(), sobelx);
//计算梯度的幅度
cv::Mat graMagMat;
cv::multiply(ConResMat, ConResMat, graMagMat);
//根据梯度幅度及参数设置阈值(mean获取各个通道的均值)
int scaleVal = 4;
double thresh = scaleVal * cv::mean(graMagMat).val[0];
cv::Mat resulttempMat = cv::Mat::zeros(graMagMat.size(), graMagMat.type());
float *pDataMag = (float*)graMagMat.data;
float *pDataRes = (float*)resulttempMat.data;
const int rows = ConResMat.rows, cols = ConResMat.cols;
for (int i = 1; i != rows - 1; i++)
{
for (int j = 1; j != cols - 1; j++)
{
//计算该点梯度与水平或垂直梯度值的大小并比较结果
bool b1 = (pDataMag[i * cols + j] > pDataMag[i * cols + j - 1]);
bool b2 = (pDataMag[i * cols + j] > pDataMag[i * cols + j + 1]);
bool b3 = (pDataMag[i * cols + j] > pDataMag[(i - 1) * cols + j]);
bool b4 = (pDataMag[i * cols + j] > pDataMag[(i - 1) * cols + j]);
//判断邻域梯度是否满足大于水平或垂直的条件
//并根据自适应阈值参数进行二值化
pDataRes[i * cols + j] = 255 * ((pDataMag[i * cols + j] > thresh) && ((b1 && b2) || (b3 && b4)));
}
}
resulttempMat.convertTo(resulttempMat, CV_8UC1);
result = resulttempMat.clone();
return true;
}
运行结果:
在OpenCV中,sobel算子在C++中的函数原型如下:
void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT )
InputArray src:输入的原图像,Mat类型
OutputArray dst:输出的边缘检测结果图像,Mat型,大小与原图像相同。
int ddepth:输出图像的深度,针对不同的输入图像,输出目标图像有不同的深度,具体组合如下:
- 若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F
- 若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F
- 若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F
- 若src.depth() = CV_64F, 取ddepth = -1/CV_64F
注:ddepth =-1时,代表输出图像与输入图像相同的深度。
int dx:int类型dx,x 方向上的差分阶数,1或0
int dy:int类型dy,y 方向上的差分阶数,1或0
其中,dx=1,dy=0,表示计算X方向的导数,检测出的是垂直方向上的边缘;dx=0,dy=1,表示计算Y方向的导数,检测出的是水平方向上的边缘。
int ksize:为进行边缘检测时的模板大小为ksize*ksize,取值为1、3、5和7,其中默认值为3。
代码实现:
int main()
{
cv::Mat image = cv::imread("1.jpg", 0);
if (image.empty()) return -1;
cv::imshow("image", image);
cv::Mat edgeMat, edgexMat, edgeyMat;
//求x方向Sobel边缘
cv::Sobel(image, edgexMat, CV_16S, 1, 0, 3, 1, 0, cv::BORDER_DEFAULT);
//求y方向Sobel边缘
cv::Sobel(image, edgeyMat, CV_16S, 0, 1, 3, 1, 0, cv::BORDER_DEFAULT);
//线性变换,转换输入数组元素为8位无符号整形
cv::convertScaleAbs(edgexMat, edgexMat);
cv::convertScaleAbs(edgeyMat, edgeyMat);
//x与y方向边缘叠加
cv::addWeighted(edgexMat, 0.5, edgeyMat, 0.5, 0, edgeMat);
cv::imshow("edgeMat", edgeMat);
cv::waitKey(0);
return 0;
}
运行结果: