Imgproc_3_图像变换

本文详细介绍边缘检测算法,如Canny、Sobel等,并解释了几何变换的应用,包括重映射、仿射变换和旋转等,适用于图像处理和计算机视觉初学者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一边缘检测

边缘检测的一般步骤:
[1]滤波
边缘检测的算法主要基于图像强度的一阶和二阶导数,但导数通常对噪声很敏感。常见的滤波方法有高斯滤波。
[2]增强
增强边缘的基础是确定图像各点邻域强度的变化值。具体编程实现时,可通过计算梯度幅值来确定。
[3]检测
通过设定阈值,找到特定梯度的边缘点

1,Canny
1>canny边缘检测步骤
[1]消除噪声
一般使用高斯平滑滤波器卷积降噪
这里写图片描述

[2]计算梯度幅值和方向(调用Sobel())
这里写图片描述
这里写图片描述

Gx,Gy分别为作用于x,y方向卷积
梯度方向可能为0,45,90,135度

[3]非极大值抑制
排除非边缘像素,仅仅保留细线条

[4]滞后阈值
为高阈值和低阈值
若高于高阈值,则保留,低于低阈值,则舍弃,在两者中间,则仅仅连接到高于高阈值的像素时被保留
2>

void Canny(InputArray image, OutputArray edges, 
           double threshold1, double threshold2, 
           int apertureSize=3, bool L2gradient=false )
//image,单通道8-bit
//threshold1,threshold2分别为滞后过程的第一个阈值和第二个阈值,
//两者的比例一般为1:3或1:2
//apertureSize,Sobel()操作的孔径大小
//L2gradient,为true时,调用L2norm=sqrt((dI/dx)^2+(dI/dy)^2)计算
//(精度高),false时,调用L1norm=|dI/dx|+|dI/dy|(精度低)           

使用步骤:

//先进行滤波
cv::Size kel_size(3,3);
cv::blur(Image,result,kel_size);
//保证threshold1/threshold2=1/3,result为单通道8-bit
cv::Canny(result,result,25,25*3,3);

效果图:
这里写图片描述

2,Sobel
1>Sobel边缘检测步骤
[1]分别在x和y方向求导
水平变化:将图像I与奇数大小的核卷积,exp,
这里写图片描述
垂直变化:将图像I与奇数大小的核卷积,exp,
这里写图片描述

[2]在图像的每一点,结合以上两个结果求出近似梯度
这里写图片描述
也可近似为:
这里写图片描述

2>

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 )
/*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
 *dx,为x方向的差分阶数,dy,为y方向的差分阶数
 *ksize,卷积核大小,只能为1,3,5,7
 *scale,计算导数时可选的缩放因子,默认为1,表示没有缩放
 *delta,表示存入dst之前,可选的delta           
 */

当ksize=3时,由于取近似,Sobel的误差较大,而Scharr仅仅作用于ksize=3时,而且效果更加精确,更快,它的运算核为:
这里写图片描述

Sobel应用步骤:

//Image为灰度图
cv::Mat ImageX;
//求X方向梯度,目标图深度为CV_16S    cv::Sobel(Image,ImageX,CV_16S,1,0,3,1,1,cv::BORDER_DEFAULT);
//求ImageX每个元素的绝对值,并且转化为8-bit
cv::convertScaleAbs(ImageX,ImageX);

cv::Mat ImageY;
//求Y方向梯度,目标图深度为CV_16S
cv::Sobel(Image,ImageY,CV_16S,0,1,3,1,1,cv::BORDER_DEFAULT);
//求ImageY每个元素的绝对值,并且转化为8-bit
cv::convertScaleAbs(ImageY,ImageY);

//两梯度的近似绝对值相加
cv::addWeighted(ImageX,0.5,ImageY,0.5,0,result);

效果图:
这里写图片描述

3,Scharr
Scharr检测步骤,与Sobel相同,运算核子为(只有这一种情况):
这里写图片描述

void Scharr(InputArray src, OutputArray dst, int ddepth, 
            int dx, int dy, double scale=1, double delta=0, 
            int borderType=BORDER_DEFAULT )
//ddepth,见Sobel()
//dx,为x方向的差分阶数,dy,为y方向的差分阶数
//scale,计算导数时可选的缩放因子,默认为1,表示没有缩放
//delta,表示存入dst之前,可选的delta               

Scharr应用步骤:

//Image为灰度图
cv::Mat ImageX;
//ImageX深度为CV_16S
cv::Scharr(Image,ImageX,CV_16S,1,0);
//求ImageX每个元素的绝对值,并且转化为8-bit
cv::convertScaleAbs(ImageX,ImageX);

cv::Mat ImageY;
//ImageY深度为CV_16S
cv::Scharr(Image,ImageY,CV_16S,0,1);
//求ImageY每个元素的绝对值,并且转化为8-bit
cv::convertScaleAbs(ImageY,ImageY);

//两个方向梯度绝对值,近似相加
cv::addWeighted(ImageX,0.5,ImageY,0.5,0,result);

效果图:
这里写图片描述

4,Laplace
Laplace算子的定义:
这里写图片描述
内部代码调用Sobel

void Laplacian(InputArray src, OutputArray dst, int ddepth,
               int ksize=1, double scale=1, double delta=0,
               int borderType=BORDER_DEFAULT )
//ddepth目标图像的深度
//ksize,计算二阶导数的核大小,必须为正奇数
//scale,影响因子,默认为1表示无影响
//delta,表示存入目标图之前,可选的delta值               

当ksize=1时,Laplacian采用3*3的核
这里写图片描述

Laplace应用步骤:
[1]得到laplace图像

//转化成灰度图
cv::cvtColor(image,imageGray,CV_BGR2GRAY);    
//得到laplace,CV_32F深度
cv::Laplacian(imageGray,laplace,CV_32F,aperture);
//缩放因子
if(scale<0)
{
double lapmin,lapmax;
cv::minMaxLoc(laplace,&lapmin,&lapmax);
scale=127/std::max(-lapmin,lapmax);
}
//深度转换
cv::Mat laplaceImage;
laplace.convertTo(laplaceImage,CV_8U,scale,128);

[2]边缘显示
在[1]得到的laplace有的点为正,有的点为负,通过查找laplace过零点的曲线得到边缘

cv::Mat getZeroCrossings(cv::Mat laplace)
{
//阈值为0,负数为黑色,正数用白色
cv::Mat signImage;
cv::threshold(laplace,signImage,0,255,cv::THRESH_BINARY);

//深度转化
cv::Mat binary;
signImage.convertTo(binary,CV_8U);

//对图像进行膨胀
cv::Mat dilated;
cv::dilate(binary,dilated,cv::Mat());

//返回过零点的轮廓
return dilated-binary;
}

效果图:
这里写图片描述

5,高斯差分
高斯滤波器可以提取图像的低频部分,用两个不同带宽的高斯滤波器对一个图像做滤波,然后用这两个图像相减,得到较高频率构成的图像,这些频率被一个滤波器保留,被另一个滤波器丢弃,这种运算叫做高斯差分

cv::Mat gauss20;
cv::GaussianBlur(image,gauss20,cv::Size(),2.0);
cv::Mat gauss22;
cv::GaussianBlur(image,gauss22,cv::Size(),2.2);

//计算高斯差分
cv::Mat dog;
cv::subtract(gauss20,gauss22,dog,cv::Mat(),CV_32F);
//计算完的高斯差分图像像素值,有正有负,通过查找过零点曲线
//得到边缘
cv::Mat zero=getZeroCrossings(dog);

效果图:
这里写图片描述

二霍夫变换

1,霍夫线变换
1>原理
[1]一条直线可以有两种表达形式
a,在笛卡尔坐标系: 可由斜率和截距表示(m,b)
b,在极坐标系:可由极径和极角表示(r, θ)
这里写图片描述

直线可以表示为:
这里写图片描述

化简为:
这里写图片描述

[2]对于点(x 0 , y 0 ),我们可以认为
这里写图片描述
每一对(r θ , θ)代表通过(x 0 , y 0 )的直线
[3]如果给定一点,在极坐标平面绘制出所有通过它的平面,得到正弦曲线
exp:x0=8,y0=6
这里写图片描述
[4]可以对图像中所有点进行上述操作,如果两个不同点进行上述操作后得到的曲线在极坐标平面内相交,则意味着它们通过同一条直线
这里写图片描述
表明这三点组成一条直线
[5]一条直线通过在极坐标平面交于一点的曲线数量来检测。我们可以设定阈值,来检测直线

2>标准霍夫变换

void HoughLines(InputArray image, OutputArray lines, 
                double rho, double theta, int threshold, 
                double srn=0, double stn=0 )
/*image,单通道8-bit,二值图像
 *lines,存储线的vector,存储了两元素vector, (r, θ),用vector<Vec2f>
 *rho直线搜索时,进步尺寸的单位半径
 *theta,以弧度为单位,直线搜索时,进步尺寸的单位角度
 *threshold,阈值,只有交点大于threshold时,才可以输出
 *srn,对于多尺度霍夫变换,srn是rho的除数距离,当src=0时,调用经典霍夫变
 *换,进步尺寸为rho,srn>0时,为rho/srn
 *stn,与srn相同,应用于theta
 *srn,stn应该同时为0,或者同时为正
 */              

使用步骤:

//Image为灰度图,进行Canny边缘检测,将result转化成BGR用于画线
cv::GaussianBlur(Image,Image,cv::Size(5,5),0);
cv::Canny(Image,Image,50,150,3);
cv::cvtColor(Image,result,CV_GRAY2BGR);

//进行霍夫线变换
std::vector<cv::Vec2f> lines;
cv::HoughLines(Image,lines,1.5,CV_PI/180,150);

//将得到的线进行标记
for(int i=0;i<lines.size();i++)
{
//将(r, θ),转化成直角坐标
float rho=lines[i][0],theta=lines[i][1];
cv::Point pt1,pt2;
double a=std::cos(theta),b=std::sin(theta);
double x=a*rho,y=b*rho;

pt1.x=cvRound(x+1000*(-b));
pt1.y=cvRound(y+1000*(a));
pt2.x=cvRound(x-1000*(-b));
pt2.y=cvRound(y-1000*(a));
cv::line(result,pt1,pt2,cv::Scalar(55,100,195),1,CV_AA);
}

效果图:
这里写图片描述

2>累计概率霍夫变换

 void HoughLinesP(InputArray image, OutputArray lines, 
                  double rho, double theta, int threshold, 
                  double minLineLength=0, double maxLineGap=0 )
 //image,单通道8-bit,二值图像
 //lines,线的输出向量,有四元素,(x0,y0,x1,y1),分别为线段的起始点和结束
 //点,用vector<Vec4i>
 //rho直线搜索时,进步尺寸的单位半径
 //theta,以弧度为单位,直线搜索时,进步尺寸的单位角度
 //threshold,阈值,只有交点大于threshold时,才可以输出
 //minLineLength,输出线段的最短距离
 //maxLineGap,允许同一行点与点连接起来的最大距离                  

使用步骤:

//Image为灰度图,进行Canny边缘检测,将result转化成BGR用于画线
cv::GaussianBlur(Image,Image,cv::Size(5,5),0);
cv::Canny(Image,Image,50,150,3); cv::cvtColor(Image,result,CV_GRAY2BGR);

//进行霍夫线变换
std::vector<cv::Vec4i> lines;
cv::HoughLinesP(Image,lines,1,CV_PI/180,80,50,10);
//画线
for(int i=0;i<lines.size();i++)
{
cv::Vec4i l=lines[i];
cv::line(result,cv::Point(l[0],l[1]),
cv::Point(l[2],l[3]),cv::Scalar(0,0,255),2,CV_AA);
}

效果图:
这里写图片描述

2,霍夫圆变换
原理:
[1]先对图像应用边缘检测
[2]对图像上每一个非0点考虑局部梯度,利用Sobel(),计算x和y方向的Sobel一阶导数得到梯度
[3]利用得到的梯度,由斜率指定直线上的每一个点都在累加器中累加,斜率为从一个指定最小值到指定的最大值的距离
[4]同时,标记边缘图像中每一个非0像素的位置
[5]从二维累加器中选择候选点的中心,这些中心都大于给定阈值并且大于其所有近邻,这些候选中心按照累加值降序排列
[6]对每一中心考虑所有非零像素
[7]像素按照,与中心的距离排序,从最大半径的最小距离算起,选择非0像素最支持的一条半径

缺点:
[1]Sobel计算局部梯度,可能会产生噪声
[2]如果有同心圆,只选择一个,如果阈值偏低,则算法执行的时间较长
[3]有同心圆,偏向于保存更大的圆

void HoughCircles(InputArray image, OutputArray circles, 
                  int method, double dp, double minDist, 
                  double param1=100, double param2=100, 
                  int minRadius=0, int maxRadius=0 )
/*image为单通道8位灰度图
 *circles,圆的输出矢量,包含(x,y,radius),用vector<Vec3f>
 *method,只有CV_HOUGH_GRADIENT
 *dp,用来检测圆心的累加器图像分辨率和输入图像之比的导数,允许创建
 *一个比输入图像分辨率低的累加器,exp:dp=1,则尺寸相同,dp=2,则
 *只有输入图像的一半的高度和宽度
 *minDist两个圆之间的最小距离,太小,则多个相邻的圆被当成一个,太
 *大,则很多圆检测不到
 *param1,在CV_HOUGH_GRADIENT中代表传给Canny的高阈值,而低阈
 *值为高阈值的一半
 *param2,在CV_HOUGH_GRADIENT中代表圆心累加器的阈值,越大,则检
 *测的圆越完美
 *minRadius,maxRadius分别为圆的最小,最大半径                  

使用步骤:

//将result转化成BGR便于画圆
cv::cvtColor(Image,result,CV_GRAY2BGR);
cv::GaussianBlur(Image,Image,cv::Size(3,3),0);

//霍夫圆变换
std::vector<cv::Vec3f> circles;
cv::HoughCircles(Image,circles,CV_HOUGH_GRADIENT,1.5,10,                 200,155);

//画圆
for(int i=0;i<circles.size();i++)
{
cv::Point center(cvRound(circles[i[0]),cvRound(circles[i][1]));
int radius=cvRound(circles[i][2]);

//先画圆心,再画半径        cv::circle(result,center,3,cv::Scalar(0,0,255),-1,8,0);
cv::circle(result,center,radius,cv::Scalar(0,0,255),3,8,0);
}

效果图:
这里写图片描述

二几何变换

1,重映射
把一幅图像中相应位置的像素放置到另一张图片指定位置的过程
(x,y)表示每个像素的位置:
g(x, y) = f(h(x, y))
g()是目标图像,f()是源图像,h(x,y)是作用于(x,y)的映射方法函数

void remap(InputArray src, OutputArray dst, InputArray map1, 
           InputArray map2, int interpolation, 
           int borderMode=BORDER_CONSTANT, 
           const Scalar& borderValue=Scalar())
//此函数不支持就地操作
//map1(两种可能的表达形式):
//表示点(x,y)的第一个形式
//CV_16SC2,CV_32FC1,CV_32FC2类型的X
//map2:
//当map1表示(x,y)时,不代表任何值
//CV_16UC1 , CV_32FC1类型的Y
//interpolation,resize中插值方法,在此不支持,INTER_AREA

使用方法:

//定义map1,map2
cv::Mat map_x(image.size(),CV_32FC1);
cv::Mat map_y(image.size(),CV_32FC1);

result.create(image.size(),image.type());
//确定remap的方法,为图像向中心聚拢
for(int j=0;j<image.rows;j++)
for(int i=0;i<image.cols;i++)
{
map_x.at<float>(j,i) = 2*( i - image.cols*0.25f ) + 0.5f;
map_y.at<float>(j,i) = 2*( j - image.rows*0.25f ) + 0.5f;
}
//重映射
cv::remap(image,result,map_x,map_y,CV_INTER_LINEAR);

效果图:
这里写图片描述

2,仿射变换
几何中,一个向量空间进行一次线性变换并接上一个平移,变换成另一个向量空间的过程

可以表示为:
乘以一个矩阵(线性变换)再加上一个向量(平移)的形式

可以有三种变换形式:
旋转,平移,缩放
使用2*3矩阵表示仿射变换
这里写图片描述
我们使用矩阵A和B对二维向量这里写图片描述做变换
也可以表示为:
这里写图片描述或者这里写图片描述
这里写图片描述

仿射变换的求法:
[1]已知X和T,而且已知它们是有联系,接下来求M
[2]已知M和X,用T=M*X

矩阵M联系着两张图片:
这里写图片描述
我们可以通过两组三点求出变换矩阵M,之后就可以应用
1>实现变换

void warpAffine(InputArray src, OutputArray dst, 
                InputArray M, Size dsize, 
                int flags=INTER_LINEAR, 
                int borderMode=BORDER_CONSTANT, 
                const Scalar& borderValue=Scalar())
//M变换矩阵,2*3的size,旋转和缩放操作通过getRotationMatrix2D()得
//到,平移和缩放通过getAffineTransform()得到
//dsize,输出图像的尺寸
//flags,插值方法的标识符(见resize()),又增加了
//WARP_INVERSE_MAP,代表反变换(dst到src)                

通过以下方式进行变换
这里写图片描述

2>计算转换矩阵

Mat getAffineTransform(InputArray src, InputArray dst)
//通过三组点得到,转换矩阵

这里写图片描述
这里写图片描述

应用:

//定义两组三对点
Point2f srcTri[3];
Point2f dstTri[3];
//确定三对点之间的转换关系
srcTri[0] = Point2f( 0,0 );
srcTri[1] = Point2f( src.cols - 1.f, 0 );
srcTri[2] = Point2f( 0, src.rows - 1.f );

dstTri[0] = Point2f( src.cols*0.0f, src.rows*0.33f );
dstTri[1] = Point2f( src.cols*0.85f, src.rows*0.25f );
dstTri[2] = Point2f( src.cols*0.15f, src.rows*0.7f );

//得到转换矩阵M
Mat warp_dst;
warp_mat = getAffineTransform( srcTri, dstTri );
//进行转换
warp_dst = Mat::zeros( src.rows, src.cols, src.type() );
warpAffine( src, warp_dst, warp_mat, warp_dst.size() );

效果图:
这里写图片描述

3>计算二维旋转矩阵

Mat getRotationMatrix2D(Point2f center, double angle, 
                        double scale)
//center源图像的旋转中心,变换会将旋转中心映射到它自身
//angle,旋转角度,角度为正表示逆时针旋转(坐标原点为左上角)
//scale,缩放系数

通过以下公式计算:
这里写图片描述
这里写图片描述

应用:


  Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
  double angle = -50.0;
  double scale = 0.6;
  //得到旋转矩阵
  rot_mat = getRotationMatrix2D( center, angle, scale );
  //变换
  warpAffine( warp_dst, warp_rotate_dst, rot_mat,      
             warp_dst.size() );

效果图:
这里写图片描述

4>invertAffineTransform
通过M,可以得到反向转换矩阵iM,这样通过dst,iM,就可以得到源图像

void invertAffineTransform(InputArray M, OutputArray iM)
//iM也为2*3的矩阵
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值