【Opencv学习笔记】边缘检测算法,霍夫直线变换,像素重映射

一、Sobel边缘检测算子

  • 卷积应用 — 提取边缘
    图像在边缘处会发生像素值的跃迁,即像素值发生显著变化。对图像进行一阶求导,导数值变化越大,说明边缘信号越强。
    在这里插入图片描述在这里插入图片描述
    Sobel 算子是一个离散微分算子 (discrete differentiation operator)。 它结合了高斯平滑和微分求导。
    通过权重不同扩大水平(垂直)方向上的差异,求出图像灰度函数的近似梯度。
    水平梯度垂直梯度

  • API

Sobel(   src ,   dst,  int depth,  int dx,  int dy,  int ksize=3, double scale=1,double delta=0,intborderType=BORDER_DEFAULT )

各参数的意义如下:

src – 输入图像;
dst – 输出图像;
depth –输出图片深度;
dx – x方向导数运算参数;
dy – y方向导数运算参数;
ksize – Sobel内核的大小,可以是:1,3,5,7;
(以下三个参数一般不做改动)
scale – 可选的缩放导数的比例常数,默认值是1;
delta – 可选的增量常数被叠加到导数中,默认值是0;
borderType – 用于判断图像边界的模式,默认模式是BORDER_DEFAULT。

输入图像支持深度和输出图像支持深度的关系如下:
在这里插入图片描述
当 depth为-1时, 输出图像将和输入图像有相同的深度。输入8位图像则会截取顶端的导数。

  • 操作步骤
    1.高斯模糊
 GaussianBlur( src, src, Size(3,3), 0, 0 );  

2.转化为灰度图像

cvtColor( src, src_gray,COLOR_RGB2GRAY );  

3.x、y方向上梯度值计算

	Mat grad_x, grad_y; 
    //x方向梯度计算  
    Sobel( src_gray, grad_x, CV_16S, 1, 0, 3,); 
    //y方向梯度计算  
    Sobel( src_gray, grad_y, CV_16S, 0, 1, 3); 

4.得到最终梯度图像

计算近似梯度值公式:
在这里插入图片描述
方法一:用相关API

	Mat abs_grad_x, abs_grad_y;  
	convertScaleAbs( grad_x, abs_grad_x ); //取x方向像素绝对值
	convertScaleAbs( grad_y, abs_grad_y ); //取y方向像素绝对值  
	addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, zuizhong );  //加权和

方法二:用自定义算法对每一个像素进处理

	Mat grad_xy = Mat(grad_x.size(),grad_x.type());
	int width = grad_x.cols;
	int height =  grad_y.rows;
	for (int row = 0; row < height; row++) {
		for (int col = 0; col < width; col++) {
			int x =  grad_x.at<uchar>(row, col);
			int y =  grad_y.at<uchar>(row, col);
			int x = x + y;
			grad_xy.at<uchar>(row, col) = saturate_cast<uchar>(xy);
		}
	}

在这里插入图片描述

二、Laplance算子

在一阶导数的极值位置,二阶导数为0。所以我们也可以用这个特点来作为检测图像边缘的方法。
在这里插入图片描述

  • API
Laplacian( src, dst, int depth, int ksize=3, double scale=1,double delta=0,intborderType=BORDER_DEFAULT );

由于 Laplacian使用了图像梯度,它内部调用了 Sobel 算子,具体参数意义参考上面Sobel算子部分。

  • 处理步骤

1.高斯模糊 — 去噪声

GaussianBlur( src, src, Size(3,3), 0, 0 );

2.转换为灰度图像

cvtColor( src, src_gray, COLOR_RGB2GRAY );

3.拉普拉斯 — 二阶导数计算

Mat abs_dst;
Laplacian( src_gray, dst, CV_16S, 3 );

4.取绝对值

 convertScaleAbs( dst, abs_dst );

在这里插入图片描述

三、Canny边缘检测算法

  • API
Canny( src, dst, lowThreshold, lowThreshold*ratio, kernel_size );

输入参数:

detected_edges: 原灰度图像
detected_edges: 输出图像 (支持原地计算,可为输入图像)
lowThreshold: 用户通过 trackbar设定的值。
highThreshold: 设定为低阈值的2倍
kernel_size: 设定为 3 (Sobel内核大小,内部使用)

  • 处理步骤

1.创建与 src 同类型和大小的矩阵(dst)

dst.create( src.size(), src.type() );

2.转换为灰度图像

cvtColor( src, src_gray, CV_BGR2GRAY );

3.创建trackbar,来获取用户交互输入的低阈值

int threshold_value = 50;//定义阈值
int threshold_max = 255;
createTrackbar( "Min Threshold:", output,  &threshold_value, threshold_max, Canny_Demo );

4.首先, 使用 3x3的内核平滑图像

blur(src_gray, src_gray, Size(3, 3));

5.用Canny检测边缘

Canny(src_gray, output, threshold_value, threshold_value * 2, 3);

6.填充 dst 图像

dst = Scalar::all(0);

7.使用函数 copyTo 标识被检测到的边缘部分

src.copyTo(dst, output);

完整代码如下

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
Mat src, src_gray, dst;
int threshold_value = 50;
int threshold_max = 255;
const char* OUTPUT_TITLE = "Canny Result";
void Canny_Demo(int, void*);
int main(int argc, char** argv)
{
	src = imread("C:/Users/XWT11/Desktop/pp.png");
	if (!src.data) 
	{
		return -1;
	}
	namedWindow(OUTPUT_TITLE, WINDOW_NORMAL);
	dst.create(src.size(), src.type());
	cvtColor(src, src_gray, COLOR_BGR2GRAY);
	createTrackbar( "Min Threshold:", output,  &threshold_value, threshold_max, Canny_Demo );
	Canny_Demo(0, 0);
	waitKey(0);
	return 0;
	
}
	void Canny_Demo(int, void*)

	{
		Mat output;
		blur(src_gray, src_gray, Size(3, 3));
		Canny(src_gray, output, threshold_value, threshold_value * 2, 3, false);
		dst = Scalar::all(0);
		src.copyTo(dst, output);
		imshow(OUTPUT_TITLE,~ output);
	}
		
	}

在这里插入图片描述

四、霍夫直线变换

-原理(以下摘自《OpenCv3编程入门》)

对于霍夫变换, 我们将用 极坐标系 来表示直线.
在这里插入图片描述 化简得:
在这里插入图片描述 对于点 (x,
y), 我们可以将通过这个点的一族直线统一定义为:
在这里插入图片描述
这就意味着每一对 (r,theta) 代表一条通过点 (x, y) 的直线. 如果对于一个给定点 (x, y)
我们在极坐标对极径极角平面绘出所有通过它的直线, 将得到一条正弦曲线. 例如, 对于给定点 x = 8 and y= 6 我们可以绘出下图
在这里插入图片描述
我们可以对图像中所有的点进行上述操作. 如果两个不同点进行上述操作后得到的曲线在平面 \theta - r 相交,
这就意味着它们通过同一条直线.
在这里插入图片描述
一条直线能够通过在平面 theta - r 寻找交于一点的曲线数量来 检测. 越多曲线交于一点也就意味着这个交点表示的直线由更多的点组成.
一般来说我们可以通过设置直线上点的 阈值 来定义多少条曲线交于一点我们才认为 检测 到了一条直线.

这就是霍夫线变换要做的. 它追踪图像中每个点对应曲线间的交点. 如果交于一点的曲线的数量超过了 阈值, 那么可以认为这个交点所代表的参数对
(theta,r) 在原图像中为一条直线.

在执行霍夫线变换之前应先用Canny算子对图像进行边缘检测

	Canny(src, src_gray, 150, 200);

1.标准霍夫线变换
API
在这里插入图片描述

执行变换

	vector<Vec2f> lines;
	HoughLines(src_gray, lines, 1, CV_PI / 180, 150, 0, 0);

画出检测到的直线

for (size_t i = 0; i < lines.size(); i++)
 {
		float rho = lines[i][0]; // 极坐标中的r长度
		float theta = lines[i][1]; // 极坐标中的角度
		Point pt1, pt2;
		double a = cos(theta), b = sin(theta);
		double x0 = a * rho, y0 = b * rho;
		// 转换为平面坐标的四个点
		pt1.x = cvRound(x0 + 1000 * (-b));
		pt1.y = cvRound(y0 + 1000 * (a));
		pt2.x = cvRound(x0 - 1000 * (-b));
		pt2.y = cvRound(y0 - 1000 * (a));
		line(dst, pt1, pt2, Scalar(255, 255, 255), 1, LINE_AA);
	}

2.统计概率霍夫线变换
这是执行起来效率更高的霍夫线变换. 输出检测到的直线的两个端点

  • API
    在这里插入图片描述
    执行变换
	vector<Vec4f> plines;
	HoughLinesP(src_gray, plines, 1, CV_PI / 180.0, 10, 0, 10);

画出检测到的直线


	for (size_t i = 0; i < plines.size(); i++)
	{
		Vec4f hline = plines[i];
		line(dst, Point(hline[0], hline[1]), Point(hline[2], hline[3]),Scalar color = Scalar(255, 255, 255);, 3, LINE_AA);

在这里插入图片描述

五、像素重映射

  • 概念

把一个图像中一个位置的像素放置到另一个图片指定位置的过程.
为了完成映射过程, 有必要获得一些插值为非整数像素坐标,因为源图像与目标图像的像素坐标不是一一对应的.
我们通过重映射来表达每个像素的位置 (x,y) :
g(x,y) = f ( h(x,y) )
这里 g() 是目标图像, f() 是源图像, h(x,y) 是作用于 (x,y) 的映射方法函数.
在这里插入图片描述

  • API
remap( src, dst, map_x, map_y, CV_INTER_LINEAR, BORDER_CONSTANT, color)

参数说明:
src: 源图像
dst: 目标图像,与 src 相同大小
map_x: x方向的映射参数. 它相当于方法 h(i,j) 的第一个参数
map_y: y方向的映射参数. 注意 map_y 和 map_x 与 src 的大小一致。
CV_INTER_LINEAR: 非整数像素坐标插值标志. 这里给出的是默认值(双线性插值).
BORDER_CONSTANT: 默认

  • 处理步骤

1.创建目标图像和两个映射矩阵.( x 和 y )

dst.create( src.size(), src.type() );
map_x.create( src.size(), CV_32FC1 );
map_y.create( src.size(), CV_32FC1 );

2.更新重映射矩阵

//双层循环,历遍每一个像素点,改变map_x、map_y的值
for (int row = 0; row < src.rows; row++)
 {
	for (int col = 0; col < src.cols; col++)
 	{
 	//此处加入改变map_x、map_y的值的函数
	 }

四种不同映射:
a.图像宽高缩小一半,并显示在中间:

if (col > (src.cols * 0.25) && col <= (src.cols*0.75) && row > (src.rows*0.25) && row <= (src.rows*0.75))
 {
					map_x.at<float>(row, col) = 2 * (col - (src.cols*0.25));
					map_y.at<float>(row, col) = 2 * (row - (src.rows*0.25));
				}
					else {
					map_x.at<float>(row, col) = 0;
					map_y.at<float>(row, col) = 0;
						}

b.图像上下颠倒:

map_x.at<float>(row, col) = (src.cols - col - 1);
map_y.at<float>(row, col) = row;

c.图像左右颠倒:

map_x.at<float>(row, col) = col;
map_y.at<float>(row, col) = (src.rows - row - 1);

d.同时执行b和c的操作:

map_x.at<float>(row, col) = (src.cols - col - 1);
map_y.at<float>(row, col) = (src.rows - row - 1);

3.进行重映射操作

remap(src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 255, 255));

效果
a.
在这里插入图片描述
b.
在这里插入图片描述
c.
在这里插入图片描述
d.
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值