sobel算子原理与实现

一、原理:

首先介绍背景知识:

   1)边缘:灰度或结构等信息的突变处,边缘是一个区域的结束,也是另一个区域的开始,利用该特征可以分割图像。

   2)边缘点:图像中具有坐标[x,y],且处在强度显著变化的位置上的点。

   3)边缘段:对应于边缘点坐标[x,y]及其方位 ,边缘的方位可能是梯度角。

       索贝尔算子(Sobeloperator)主要用作边缘检测,在技术上,它是一离散性差分算子,用来运算图像亮度函数的灰度之近似值。在图像的任何一点使用此算子,将会产生对应的灰度矢量或是其法矢量。

为了节省时间,我就直接截图了,原理都这样。

套用公式就如下,

Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息,边缘定位精度不够高。当对精度要求不是很高时,是一种较为常用的边缘检测方法。

如果求整体x和y方向的So被浏览梯度,只需要将Gx和Gy想家即可。

二、C++代码实现

      我查过很多资料,都没有发现这个Sobel在C++上到底 是怎么实现的,以为就是没有代码,思索良久,在同学的提示下,终于写出来了,不过可能有不少瑕疵。


#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
	Mat m_img = imread("D://vvoo/lena.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]
				+ 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("原图", src);
	imshow("gradient", dstImage);
	imshow("Vertical gradient", grad_y);
	imshow("Horizontal gradient", grad_x);
 
	waitKey(0);
	return 0;
}

截图:

     为了作对比,再讲一下opencv的Sobel函数实现:

<pre name="code" class="cpp">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,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
  • 第三个参数,int类型的ddepth,输出图像的深度,支持如下src.depth()和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
  • 第四个参数,int类型dx,x 方向上的差分阶数。
  • 第五个参数,int类型dy,y方向上的差分阶数。
  • 第六个参数,int类型ksize,有默认值3,表示Sobel核的大小;必须取1,3,5或7。
  • 第七个参数,double类型的scale,计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。我们可以在文档中查阅getDerivKernels的相关介绍,来得到这个参数的更多信息。
  • 第八个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0。
  • 第九个参数, int类型的borderType,我们的老朋友了(万年是最后一个参数),边界模式,默认值为BORDER_DEFAULT。这个参数可以在官方文档中borderInterpolate处得到更详细的信息。

有点啰嗦了

 结果为:
#include<opencv2/opencv.hpp>
using namespace cv;
int main()
{
	Mat dstIamge, grad_x, grad_y, abs_grad_x, abs_grad_y;
	Mat srcIamge = imread("D:\\vvoo\\lena.jpg");
 
	imshow("原始图", srcIamge);
	cvtColor(srcIamge, srcIamge, COLOR_RGB2GRAY);
	//求 X方向梯度
	Sobel(srcIamge, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT);
	convertScaleAbs(grad_x, abs_grad_x);//使用线性变换转换输入数组元素成8位无符号整型
	imshow("X方向Sobel", abs_grad_x);
 
	//求Y方向梯度
	Sobel(srcIamge, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT);
	convertScaleAbs(grad_y, abs_grad_y);
	imshow("Y方向Sobel", abs_grad_y);
 
	//合并梯度(近似)
	addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0,dstIamge);
	imshow("整体方向Sobel", dstIamge);
 
	waitKey(0);
	return 0;
}


 

参考资料:

1.OpenCV入门教程

2.Sobel边缘检测算法

3.https://blog.csdn.net/qq_29540745/article/details/51918004

4.https://blog.csdn.net/zichen_ziqi/article/details/80858602

python实现

#/usr/bin/env python
# -*- coding: UTF-8 -*-
import numpy as np
import cv2


def directional_gradient(img, direction='x', thresh=[0, 255]):
    '''
    使用opencv Sobel算子来求方向梯度
    :param img: Grayscale
    :param direction: x or y axis
    :param thresh: apply threshold on pixel intensity of gradient image
    :return:  binary image
    '''
    if direction == 'x':
        sobel = cv2.Sobel(img, cv2.CV_64F, 1, 0)# 参数 1,0 为只在 x 方向求一阶导数,最大可以求 2 阶导数
    elif direction == 'y':
        sobel = cv2.Sobel(img, cv2.CV_64F, 0, 1)

    sobel_abs = np.absolute(sobel)#相当于|Gx|
    scaled_sobel = np.uint8(sobel_abs * 255 / np.max(sobel_abs))#归一化(归255)
    binary_output = np.zeros_like(sobel)
    binary_output[(scaled_sobel >= thresh[0]) & (scaled_sobel <= thresh[1])] = 1#二值化
    return binary_output

猜你喜欢

转载自blog.csdn.net/qq_37124237/article/details/82183177