写在前面
Sobel算子同样是一种一阶微分算子,它的卷积算子和Prewitt算子非常类似,仅仅是系数不同,但Sobel算子对于像素位置的影响做了加权,与Prewitt算子、Roberts算子相比效果更好。
优点
对边缘定位较为准确,能较好地处理灰度渐变和噪声较多的图像,计算简单,可分别计算水平和垂直边缘,如EasyPR用其定位车牌。
原理
首先我们看Sobel算子:
我们发现:相比Prewitt算子,就是平滑系数不同,sobel进行了加权。而这个平滑系数其实就是窗口为3的非归一化的高斯平滑。
平滑算子:对于二项式展开式的系数可以作为非归一化的高斯平滑算子,具体原理推导可见《OpenCV算法精讲》(张平著)的第五章:图像平滑。二项式展开系数为:
窗口为w=3 ,平滑算子就为阶数n=w-1=2的展开式的系数,把2代入上式计算一下即可。
所以我们还可以自己构建任意窗口大小的平滑算子,可总结如下:
上表中的“平滑算子”一列其实就是杨辉三角(帕斯卡三角形)。
差分算子:窗口为w的差分算子就是在w-2阶的二项式展开系数两侧补0,然后后向差分得到。
举例: 构建窗口为5的平滑算子,则对应差分算子就为n=3阶展开式系数两侧补0,然后后向差分:
0 1 3 3 1 0
差分: 1-0 3-1 3-3 1-3 0-1
差分后: 1 2 0 -2 -1 (差分算子)
显然,Soble边缘检测是可分离的,可以分离实现提高效率。
代码实现
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
//阶乘
int factorial(int n){
int fac = 1;
//0的阶乘
if (n == 0)
return fac;
for (int i = 1; i <= n; ++i){
fac *= i;
}
return fac;
}
//获得Sobel平滑算子
cv::Mat getSobelSmoooth(int wsize){
int n = wsize - 1;
cv::Mat SobelSmooothoper=cv::Mat::zeros(cv::Size(wsize,1),CV_32FC1);
for (int k = 0; k <= n; k++){
float *pt = SobelSmooothoper.ptr<float>(0);
pt[k] = factorial(n) / (factorial(k)*factorial(n - k));
}
return SobelSmooothoper;
}
//获得Sobel差分算子
cv::Mat getSobeldiff(int wsize){
cv::Mat Sobeldiffoper = cv::Mat::zeros(cv::Size(wsize, 1), CV_32FC1);
cv::Mat SobelSmoooth = getSobelSmoooth(wsize - 1);
for (int k = 0; k < wsize; k++){
if (k == 0)
Sobeldiffoper.at<float>(0, k) = 1;
else if (k == wsize - 1)
Sobeldiffoper.at<float>(0, k) = -1;
else
Sobeldiffoper.at<float>(0, k) = SobelSmoooth.at<float>(0, k) - SobelSmoooth.at<float>(0, k - 1);
}
return Sobeldiffoper;
}
//卷积实现
void conv2D(cv::Mat& src, cv::Mat& dst, cv::Mat kernel, int ddepth, cv::Point anchor = cv::Point(-1, -1), int delta = 0, int borderType = cv::BORDER_DEFAULT){
cv::Mat kernelFlip;
cv::flip(kernel, kernelFlip, -1);
cv::filter2D(src, dst, ddepth, kernelFlip, anchor, delta, borderType);
}
//可分离卷积———先垂直方向卷积,后水平方向卷积
void sepConv2D_Y_X(cv::Mat& src, cv::Mat& dst, cv::Mat kernel_Y, cv::Mat kernel_X, int ddepth, cv::Point anchor = cv::Point(-1, -1), int delta = 0, int borderType = cv::BORDER_DEFAULT){
cv::Mat dst_kernel_Y;
conv2D(src, dst_kernel_Y, kernel_Y, ddepth, anchor, delta, borderType); //垂直方向卷积
conv2D(dst_kernel_Y, dst, kernel_X, ddepth, anchor, delta, borderType); //水平方向卷积
}
//可分离卷积———先水平方向卷积,后垂直方向卷积
void sepConv2D_X_Y(cv::Mat& src, cv::Mat& dst, cv::Mat kernel_X, cv::Mat kernel_Y, int ddepth, cv::Point anchor = cv::Point(-1, -1), int delta = 0, int borderType = cv::BORDER_DEFAULT){
cv::Mat dst_kernel_X;
conv2D(src, dst_kernel_X, kernel_X, ddepth, anchor, delta, borderType); //水平方向卷积
conv2D(dst_kernel_X, dst, kernel_Y, ddepth, anchor, delta, borderType); //垂直方向卷积
}
//Sobel算子边缘检测
//dst_X 垂直方向
//dst_Y 水平方向
void Sobel(cv::Mat& src, cv::Mat& dst_X, cv::Mat& dst_Y, cv::Mat& dst, int wsize, int ddepth, cv::Point anchor = cv::Point(-1, -1), int delta = 0, int borderType = cv::BORDER_DEFAULT){
cv::Mat SobelSmooothoper = getSobelSmoooth(wsize); //平滑系数
cv::Mat Sobeldiffoper = getSobeldiff(wsize); //差分系数
//可分离卷积———先垂直方向平滑,后水平方向差分——得到垂直边缘
sepConv2D_Y_X(src, dst_X, SobelSmooothoper.t(), Sobeldiffoper, ddepth);
//可分离卷积———先水平方向平滑,后垂直方向差分——得到水平边缘
sepConv2D_X_Y(src, dst_Y, SobelSmooothoper, Sobeldiffoper.t(), ddepth);
//边缘强度(近似)
dst = abs(dst_X) + abs(dst_Y);
cv::convertScaleAbs(dst, dst); //求绝对值并转为无符号8位图
cv::convertScaleAbs(dst_X, dst_X); //求绝对值并转为无符号8位图
cv::convertScaleAbs(dst_Y, dst_Y);
//cv::pow(dst_X, 2.0, dst_X);
//cv::pow(dst_Y, 2.0, dst_Y);
//cv::sqrt(dst_X + dst_Y, dst);
//dst.convertTo(dst, CV_8UC1);
}
int main(){
cv::Mat src = cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\Fig1025(a)(building_original).tif");
if (src.empty()){
return -1;
}
if (src.channels() > 1) cv::cvtColor(src, src, CV_RGB2GRAY);
int wsize = 3;
cv::Mat Sobeldiffoper, SobelSmooothoper;
SobelSmooothoper = getSobelSmoooth(wsize);
Sobeldiffoper = getSobeldiff(wsize);
//注意:要采用CV_32F,因为卷积后可能为负数,若用8位无符号,则会导致这些地方为0
cv::Mat dst, dst_X, dst_Y;
Sobel(src, dst_X, dst_Y, dst, wsize, CV_32FC1);
cv::namedWindow("src", CV_WINDOW_NORMAL);
imshow("src", src);
cv::namedWindow("水平边缘", CV_WINDOW_NORMAL);
imshow("水平边缘", dst_Y);
cv::namedWindow("垂直边缘", CV_WINDOW_NORMAL);
imshow("垂直边缘", dst_X);
//cv::namedWindow("边缘强度", CV_WINDOW_NORMAL);
imshow("边缘强度", dst);
cv::namedWindow("边缘强度取反-铅笔素描", CV_WINDOW_NORMAL);
imshow("边缘强度取反-铅笔素描",255-dst);
std::cout <<"SobelSmooothoper: "<< SobelSmooothoper << std::endl;
std::cout << "Sobeldiffoper: " << Sobeldiffoper << std::endl;
cv::waitKey(0);
return 0;
}
效果
划重点:有一个有趣的应用:生成铅笔画素描的效果,将边缘检测结果图取反即可!
参考:
《OpenCV算法精解》---张平
https://blog.csdn.net/weixin_40647819/article/list/2?
评论(0)
您还未登录,请登录后发表或查看评论