-
目录
第七章:图像变换
7.1、基于OpenCV的边缘检测
7.1.1、一般步骤
1、滤波
2、增强
3、检测
7.1.2、canny算子
1、canny算子简介
2、canny边缘检测的步骤
7.2、霍夫变换
7.2.2、OpenCV中的霍夫线变换
7.2.3、霍夫线变换原理
7.2.4、标准霍夫变换:HoughLines()函数
7.2.5、累计概率霍夫变换:HouhLinesP()函数
7.2.6、霍夫圆变换
7.2.7、霍夫梯度法的原理
7.2.8、霍夫梯度法的缺点
7.2.9、霍夫圆变换:HoughCircles()函数
7.2.10、综合示例
7.2、重映射
7.3.1、重映射的概念
7.3.2、实现重映射:remap()函数
7.4、仿射变换
7.4.1、认识仿射变换
7.4.2、仿射变换求法
7.4.4、计算二维旋转变换矩阵:getRotationMatrix2D
7.4.5、示例程序:仿射变换
7.5、直方图均衡化
7.5.1、直方图均衡化的概念和特点
7.5.2、实现直方图均衡化:equalizeHist()函数
7.5.3、示例程序:直方图均衡化
7.6、本章小结
第七章:图像变换
-
7.1、基于OpenCV的边缘检测
-
7.1.1、一般步骤
-
1、滤波
- 边缘检测的算法主要是基于图像强度的一阶和二阶导数,但导数通常对噪声很敏感,因此必须采用滤波器来改善与噪声有关的边缘检测器的性能。常见的是高斯滤波,即采用离散化的高斯函数产生一组归一化的高斯核,然后基于高斯核函数对图像灰度矩阵的每一点进行加权求和。
-
2、增强
- 增强边缘的基础是确定图像各点邻域强度的变化值。增强算法可以将图像灰 度点邻域强度值有显著变化的点凸显出来。 在具体编程实现时,可通过计算梯度 1!liÎJ值来确定。
-
3、检测
- 经过增强的图像, 往往邻域中有很多点的梯度值比较大, 而在特定的应用中, 这些点并不是要找的边缘点,所以应该采用某种方法来对这些点进行取舍。实际 工程中, 常用的方法是通过阔值化方法来检测
-
-
7.1.2、canny算子
-
1、canny算子简介
- canny的目标是找到一个最优的边缘检测算法,最优边缘检测的三个主要评价标准:1、低错误率:标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报;2、高定位性:标识出的边缘要与图像中的实际边缘尽可能接近;3、最小响应:图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘
- 为了满足这些要求,canny使用了变分法,这是一种寻找满足特定功能的函数的方法。最优检测用4给指数函数项的和标识,非常近似于一阶导数
-
2、canny边缘检测的步骤
- 1、消除噪声
- 使用高斯平滑滤波器卷积降噪
- 2、计算梯度幅值和方向
- 3、非极大值抑制
- 这一步排除非边缘像素,仅仅保留了一些细线条(候选边缘
- 4、滞后阈值
- 滞后阈值需要一个高阈值和一个低阈值
- 若某一像素位置的幅值超过高阈值,该像素被保留为边缘像素;某一像素位置的幅值小于低阈值,该像素被排除;某一像素位置的幅值在两个阈值之间,该像素仅仅在连接到一个高阈值的像素时被保留。
- 1、消除噪声
- 3、canny边缘检测:函数
-
1:输入图像;2:输出的边缘图;3:第一个阈值;4:第二个阈值;5:应用Sobel算子的孔径大小,默认值为3;6:计算图像梯度幅值的标识void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize=3, bool L2gradient=false)
- 需要注意的是,这个函数|阕值 l 和阔值2两者中较小的值用于边缘连接,而 较大的值用来控制强边缘的初始段,推荐的高低阔值比在2: 1 到3:1 之间。
-
- 4、代码示例
-
#include <opencv2/opencv.hpp> #include<opencv2/ highgui/highgui .hpp> #include<opencv2/imgproc/imgproc.hpp> using namespace cv; int main() { Mat src = imread("1.jpg"); Mat src1 = src.clone(); imshow("【原始图】",src); //简单用法:opencv3已弃用 //Canny(srcImage, srcImage,150, 100, 3); //高阶用法,转成灰度图,降噪,用canny,最后将得到的边缘作为掩码,拷贝原图到效果图上,得到彩色的边缘图 Mat dst, edge, gray; //创建于src同类型和大小的矩阵 dst.create(src1.size(),src1.type()); //将原图转换为灰度图 cvtColor(src1,gray,COLOR_BGR2GRAY); //先用3x3的内核降噪 blur(gray, edge, Size(3,3)); //运行canny算子 Canny(edge, edge, 3,9,3) //将g_dstImage 内所有元素置零 dst = Scalar::all(0); //使用canny算子输出边缘图g_cannyDetectedEdges作为掩码,来将原图g_srcImage拷到目标图g_dstImage中 src1.copyTo(dst,edge); imshow("【效果图】",dst); waitKey(0); return 0; }
-
-
- 7.1.3、sobel算子
- 这是一个用于边缘检测的离散微分算子。结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。在图像的任何一点使用此算子,都会产生对应的梯度矢量或其法向量
- 计算过程
- Sobel函数
-
void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3,double scale = 1, double delth=0, int borderType= BORDER_DEFAULT);
1:输入图像; 2:目标图像;3:输出图像的深度;4:x方向上的差分阶数;5:y方向上的差分阶数;6:sobel核的大小取1、3、5、7;7:计算导数值时可选的缩放因子,默认值1,标识没有应用缩放;8:在结果存入目标图之前可以选的delta值,有默认值0;9:边界模式
-
补充说明:
- 程序示例
-
#include <opencv2/opencv.hpp> #include<opencv2/ highgui/highgui .hpp> #include<opencv2/imgproc/imgproc.hpp> using namespace cv; int main() { //创建grad_x grad_y矩阵 Mat grad_x, grad_y; Mat abs_grad_x, abs_grad_y, dst; Mat src = imread("1.jpg"); imshow("【原始图】",src); //求x方向梯度 Sobel(src, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT); convertScaleAbs(grad_x, abs_grad_x); imshow("【效果图】x方向",abs_grad_x); //求y方向梯度 Sobel(src, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT); convertScaleAbs(grad_y, abs_grad_y); imshow("【效果图】y方向",abs_grad_y); //合并梯度 addWeighted(abs_grad_x,0.5,abs_grad_y,0.5,0,dst); imshow("【效果图】",dst); waitKey(0); return 0; }
-
- 7.1.4、Laplacian算子
- 1、简介
- Laplacian()函数
-
void Laplacian(InputArray src, OutputArray dst, int ddepth, int ksize=1,double scale=1, double delta = 0, intborderType = BORDER_DEFAULT);
1:输入图像;2:输出的边缘图;3:目标图像深度;4:用于计算二阶导数的滤波器的孔径大小;5:计算拉普拉斯值的时候可选的比例因子,默认值1;6:在结果存入目标图之前可以选的delta值,有默认值0;7:边界模式
-
它通过加上 sobe! 算子运 算出的图像x方向和y方向上的导数4 来得到载入图像的拉普拉斯变换结果。
-
-
示例程序
#include <opencv2/opencv.hpp> #include<opencv2/ highgui/highgui .hpp> #include<opencv2/imgproc/imgproc.hpp> using namespace cv; int main() { Mat src, src_gray, dst, abs_dst; src= imread("1.jpg"); imshow("【原始图】",src); //使用高斯滤波消除噪声 GaussianBlur(src, src, Size(3,3),0,0,BORDER_DEFAULT); //转换为灰度图 cvtColor(src,src_gray, COLOR_RGB2GRAY); //使用Laplace函数 Laplace(src_gray, dst, CV_16S, 3, 1, 0, BORDER_DEFAULT); //计算绝对值 convertScaleAbs(dst,abs_dst); imshow("【效果图】",abs_dst); waitKey(0); return 0; }
- 7.1.5、scharr滤波器
- 计算图像差分:Scharr()函数
- 使用 ScbalT 滤波器运算符计算x 或y方向的图像差分。 其实它的参数变量和 Sobel 基本上是一样的, 除了没有 ksize核的大小
-
void Scharr(InputArray src, OutputArray dst, int ddepth, int dx,int dy, double scale=1,double delta=0, intborderType = BORDER_DEFAULT);
1:输入图像;2:输出的边缘图;3:目标图像深度;4:x方向上的差分阶数;5:y方向上的差分阶数;6:计算导数值时可选的缩放因子,默认值1,标识没有应用缩放;7:在结果存入目标图之前可以选的delta值,有默认值0;8:边界模式
-
示例
#include <opencv2/opencv.hpp> #include<opencv2/ highgui/highgui .hpp> #include<opencv2/imgproc/imgproc.hpp> using namespace cv; int main() { Mat grad_x, grad_y; Mat abs_grad_x, abs_grad_y, dst; Mat src= imread("1.jpg"); imshow("【原始图】",src); //求x方向上的梯度 Scharr(src, grad_x, CV_16S, 1, 0, 1, 0, BORDER_DEFAULT); convertScaleAbs(grad_x, abs_grad_x); imshow("【效果图】x方向上Scharr",abs_grad_x); //求y方向上的梯度 Scharr(src, grad_y, CV_16S, 0, 1, 1, 0, BORDER_DEFAULT); convertScaleAbs(grad_y, abs_grad_y); imshow("【效果图】y方向上Scharr",abs_grad_y); //合并梯度 addWeighted(abs_grad_x,0.5, abs_grad_y,0.5,0,dst); imshow("【效果图】",dst); waitKey(0); return 0; }
- 计算图像差分:Scharr()函数
- 7.1.6、综合示例:边缘检测
#include <opencv2/opencv.hpp> #include<opencv2/ highgui/highgui .hpp> #include<opencv2/imgproc/imgproc.hpp> using namespace cv; Mat g_srcImage, g_srcGrayImage,g_dstImage; //canny边缘检测相关变量 Mat g_cannyDetectedEdges; int g_cannyLowThreshold=1; //sobel Mat g_sobelGradient_X, g_sobelGradient_Y; Mat g_sobelAbsGradient_X, g_sobelAbsGradient_Y; int g_sobelKernelSize=1; //Scharr Mat g_scharrGradient_X, g_scharrGradient_Y; Mat g_scharrAbsGradient_X,g_scharrAbsGradient_Y; static void on_Canny(int,void*);///Canny 边缘检测窗 口滚动条的回调函数 static void on_Sobel(int, void*); //Sobel 边缘检测窗 口滚动条的回调函数 void Scharr(); //封装了 Scharr 边缘检测相关代码的函数 int main(int argc, char **argv) { // 改变 console 字体颜色 system("color 2F"); g_srcImage = imread("1.jpg"); if( !g_srclmage.data ) ( printf( "读取 srclmage 错误- ! \n"); return false; ) namedWindow (" 【 原始图 】 ") ; imshow (" 【 原始图 】 ", g_srclmage) ; // 创建与 src 同类型和大小的矩阵(dst) g_dstlmage.create( g_srclmage.size() , g_srclmage. type() ); // 将原因像转换为灰皮图像 cvtColor( g_srclmage, g_srcGraylmage, COLOR_BGR2GRAY ); // 创建显示窗口 namedWindow ( " 【 效果图】 Canny 边缘检测", WINDOW AUTOSIZE ) ; namedWindow ( "【效采图 】 Sobel 边缘检测", WINDOW AUTOSIZE ); //创建trackbar createrTrackbar("参数值:","【效果图】canny边缘检测",&g_canyLowThreshold,120,on_Canny); createrTrackbar("参数值:","【效果图】sobel边缘检测",&g_sobelKernelSize,3,on_Sobel); on_Canny(0,0); on_Sobel(0,0); Scharr(); while ((char(waitKey(1)) != 'q')){} return 0; } void on_Canny(int, void*) { //使用 3x3 内核来降嗓 blur(g_srcGrayImage,g_cannyDetectedEdges,Size(3,3)); Canny(g_cannyDetectedEdges, g_cannyDetectedEdges,g_cannyLowThreshold,g_cannyLowThreshold*3,3); //允将 g_dstlmage 内的所有元素设立为 g_dstImage = Scalar::all(0); //使用 Canny 算子输出的边缘图 g_canηyDetectedEdges 作为掩码,来将原图g_srcImage拷到g_dstImage g_srcImage.copyTo(g_dstImage, g_cannyDetectedEdges); imshow("【效果图】canny边缘检测",g_dstImage); } void on_Sobel(int ,void*) { //求x方向梯度 Sobel(g_srcImage,g_sobelGradient_X,CV_16S,1,0,(2*g_sobelKernelSize+1),1,1,BORDER_DEFAULT); convertScaleAbs(g_sobelGradient_X,g_sobelAbsGradient_X);//计算绝对值, 并将给采转换成8位 //求y方向梯度 Sobel(g_srcImage,g_sobelGradient_Y,CV_16S,0,1,(2*g_sobelKernelSize+1),1,1,BORDER_DEFAULT); convertScaleAbs(g_sobelGradient_Y,g_sobelAbsGradient_Y);//计算绝对值, 并将给采转换成8位 //合并梯度 addWeighted(g_sobelAbsGradient_X,0.5,g_sobelAbsGradient_Y,0.5,0,g_dstImage); imshow("【效果图】sobel边缘检测",g_dstImage); } void Scharr() { //求x方向梯度 Scharr(g_srcImage,g_scharrAbsGradient_X,CV_16S,1,0,1,0,BORDER_DEFAULT); convertScaleAbs(g_scharrGradient_X,g_scharrAbsGradient_X);//计算绝对值, 并将给采转换成8位 //求x方向梯度 Scharr(g_srcImage,g_scharrAbsGradient_Y,CV_16S,0,1,1,0,BORDER_DEFAULT); convertScaleAbs(g_scharrGradient_Y,g_scharrAbsGradient_Y);//计算绝对值, 并将给采转换成8位 //合并梯度 addWeighted(g_scharrAbsGradient_X,0.5,g_scharrAbsGradient_Y,0.5,0,g_dstImage); imshow("【效果图】scharr边缘检测",g_dstImage); }
-
-
7.2、霍夫变换
-
用于识别图像的几何形状
-
7.2.2、OpenCV中的霍夫线变换
- 标准霍夫变换:HoughLines函数
- 多尺度霍夫变换:HoughLines函数
- 累计改了吧霍夫变换:HoughLinesP函数
-
7.2.3、霍夫线变换原理
-
霍夫变换使用极坐标表示直线:r=x+y; 对于一个点(,),可以将通过这个点的一族直线统一定义为=·+·,即每一对(,)代表一条通过点(,)的直线;对于一个给定点(,),在极坐标对极径极角平面绘出所有通过他的直线会得到一条正弦曲线;我们可以对图像上所有的点进行上述操作,如果两个不同点进行上述操作后得到的曲线在平面---r 相交,意味着它们通过同一条直线。如下图,这三条曲线在平面相较于(0.925,9.6),坐标表示的是参数对---r 平面的直线。
一般来说,一条直线能够通过在平面---r寻找交于一点的曲线数量来检测,越多曲线交于一点意味着这个交点表示的直线由更多的点组成。我们可以通过设置直线上的点的阈值来定义多少条曲线交于一点,认为是检测到了一条直线;这就是霍夫变换要做的,它追踪图像中每个点对应曲线间的交点。如果交于一点的曲线的数量超过了阈值,可以认为这个交点代表的参数对(,)在原图像中是一条直线
-
-
7.2.4、标准霍夫变换:HoughLines()函数
-
void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0,double stn=0)
1:输入图像;2:经过调用HoughLines()函数后存储了霍夫线变换检测到线条的输出矢量。每一条线由具有两个元素的矢量(,)表示,其中表示离坐标原点(左上角)的距离,是弧度线条旋转角度(0°表示垂直线,/2表示水平线);3:以像素为单位的距离精度。即直线搜索时的进步尺寸的单位半径;4:以弧度为单位的角度精度。即直线搜索时的进步尺寸的单位角度;5:累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。6:对于多尺度的霍夫变换,这是第三个参数尺寸的除数距离;7:第四个参数进步尺寸的单位角度的除数距离。
-
int main() { Mat srcImage = imread("1.jpg"); Mat midImage,dstImage; Canny(srcImage, midImage, 50, 200, 3); cvtColor(midImage, dstImage, CV_GRAY2BGR); vector<Vec2f> lines;//存放得到的线段矢量集合 HoughLines(midImage,lines,1,CV_PI/180,150,0,0); for(size_t i= 0;i<lines.size();i++)//依次画出每条线段 { float rho = lines[i][0],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(dstImage,pt1,pt2,Scalar(55,100,195),1,LINE_AA); } imshow("【原始图】",srcImage); imshow("【边缘检测后的图】",midImage); imshow("【效果图】",dstImage); waitKey(0); return 0; }
-
-
7.2.5、累计概率霍夫变换:HouhLinesP()函数
-
void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double midLineLength=0; double maxLineGap=0)
1:输入图像;2:经过调用HoughLinesP()函数后存储了霍夫线变换检测到线条的输出矢量。每一条线由具有4个元素的矢量(x_1.y_1,x_2,y_2)表示,其中(x_1,y_1),(x_2,y_2)是每个检测到的线段的结束点;3:以像素为单位的距离精度。即直线搜索时的进步尺寸的单位半径;4:以弧度为单位的角度精度。即直线搜索时的进步尺寸的单位角度;5:累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。6:最低线段的长度;7:默认值0,允许将同一行点与点之间连接起来的最大距离
-
int main() { Mat srcImage = imread("1.jpg"); Mat midImage,dstImage; Canny(srcImage, midImage, 50, 200, 3); cvtColor(midImage, dstImage, COLOR_GRAY2BGR); vector<Vec4i> lines;//存放得到的线段矢量集合 HoughLines(midImage,lines,1,CV_PI/180,80,50,10); for(size_t i= 0;i<lines.size();i++)//依次画出每条线段 { Vec4i l = lines[i] line(dstImage,Point(l[0],l[1]),Point(l[2],l[3]),Scalar(186,88,255),1,LINE_AA); } imshow("【原始图】",srcImage); imshow("【边缘检测后的图】",midImage); imshow("【效果图】",dstImage); waitKey(0); return 0; }
-
-
7.2.6、霍夫圆变换
- 点对应的二维极径极角空间被三维的圆心点x、y和半径r空间取代。对于圆使用3个参数表示 C:(,,r)。使用”霍夫梯度法“解决圆变换
-
7.2.7、霍夫梯度法的原理
-
1、对图像应用边缘检测
-
2、对边缘图像中的每一个非零点,考虑其局部梯度,用Sobel函数
-
3、利用得到的梯度,由斜率指定的直线上的每一个点都在累加器中被累加,这里的斜率是从一个指定的最小值到指定的最大值的距离
-
4、同时,标记边缘图像中每一个非0像素的位置
-
5、然后从二维累加器中这些点中选择候选的中心,这些中心都大于给定阈值并大于其所有近邻。这些候选的中心按照累加值降序排序,以便于最支持像素的中心首先出现
-
6、接下来对每一个中心,考虑所有的非0元素
-
7、这些像素按照其与中心的距离排序。从到最大半径的最小距离算起,选择非0像素最支持的一条半径
-
8、如果一个中心收到边缘图像非0像素最充分的支持,并且到前期被选择的中心有足够的距离,那么它就会被保留下来。
-
-
7.2.8、霍夫梯度法的缺点
-
1、使用Sobel计算梯度,它可以视作等同于一条局部切线,会在输出中产生一些噪声
-
2、在边缘图像中的整个非0像素集被看作每个中心的候选部分。如果把累加器的阈值减小,就会消耗很长时间;此外,每一个中心只选择一个圆,如果有同心圆就只能选择 一个
-
3、中心是按照其关联的累加器值的升序排列的,并且如果新的中心过于接近之前已经接受的中心的话,就不会被保留下来。当有很多同心圆或者是近似的同心圆时,霍夫梯度法倾向于保留最大的一个圆。
-
-
7.2.9、霍夫圆变换:HoughCircles()函数
-
void HoughCircles(InputArray image, OutputArray circles, int method,double dp, double minDist, double param1=100, double param2=100,int minRadius=0, int maxRadius=0)
1:输入图像;2:经过调用此函数后此参数存储了检测到的圆的输出矢量,用3个浮点表示;3:使用的检测方法,目前OpenCV中只有霍夫梯度法可以使用;4:检测圆心的累加器图像分辨率与输入图像之比的倒数,此参数允许创建一个比输入图像分辨率低的累加器。如果dp= 1 时, 累加器和输入图像具有相同的分辨率。 如果dp=2, 累加器便有输入图像一半那么大的宽度和高度。5:检测到的圆心之间的最小距离,算法能区分的两个不同圆之间的最小距离。参数若太小会使多个相邻的圆被检测成一个圆,太大会导致某些圆检测不到;6:传递给canny边缘检测算子的高阈值,低阈值为高阈值的一半;7:在检测阶段圆心的累加器阈值,越小就检测到更多不存在的圆,越大就更接近完美的圆形;8:圆半径的最小值;9:圆半径的最大值
-
int main() { Mat srcImage = imread("1.jpg"); Mat midImage,dstImage; imshow("【原始图】",srcImage); cvtColor(srcImage, midImage, COLOR_BGR2GRAY); //图像平滑 GaussianBlur(midImage,midImage,Size(9,9),2,2); //进行霍夫变换 vector(Vec3f> circles; HoughCircles(midImage,circles,HOUGH_GRADIENT,1.5,10,200,100,0,0); for(size_t i= 0;i<circles.size();i++)//依次画出每条线段 { //参数定义 Point center(cvRound(circles[i][0]),cvRound(circles[i][1])); int radius = cvRound(circles[i][2]); //绘制圆心 circle(srcImage,center,3,Scalar(0,255,0),-1,8,0); //绘制圆轮廓 circle(srcImage, center,radius,Scalar(155,50,255),3,8,0); } imshow("【效果图】",dstImage); waitKey(0); return 0; }
-
-
7.2.10、综合示例
-
#include <opencv2/opencv.hpp> #include<opencv2/ highgui/highgui .hpp> #include<opencv2/imgproc/imgproc.hpp> using namespace cv; Mat g_srcImage, g_midImage,g_dstImage; vector<Vec4i> g_lines;//存放得到的线段 int g_nthreshold=100; static void on_HoughLines(int, void*); int main() { system("color 3F"); Mat g_srcImage = imread("1.jpg"); imshow("【原始图】",g_srcImage); namedWindow("【效果i图】",1); createTrackbar("值","【效果图】",&g_nthreshold,200,on_HoughLines); Canny(g_srcImage,g_midImage,50,200,3); cvtColor(g_midImage,g_dstImage,COLOR_GRAY2BGR); on_HoughLines(g_nthreshold,0); HoughLinesP(g_midImage,g_lines,1,CV_PI/180,80,50,10); imshow("【效果图】",g_dstImage); waitKey(0); return 0; } static void on_HoughLines(int, void*) { Mat dstImage=g_dstImage.clone(); Mat midImage = g_midImage.clone(); vector<Vec4i> mylines; HoughLinesP(midImage,mylines,1,CV_PI/180,g_nthreshold+1,50,10); for(size_t i =0;i<mylines.size(),i++) { Vec4i l = mylines[i]; line(dstImage,Point(l[0],l[1]), Point(l[2],l[3]),Scalar(23,180,55),1,LINE_AA); } imshow("【效果图】",g_dstImage); }
-
-
-
7.2、重映射
-
7.3.1、重映射的概念
- 重映射就是把一副图像中某位置的像素放置到另一个图片指定位置的过程。为了完成映射过程,需要获得一些插值为非整数像素的坐标,因为源图像与目标图像的像素坐标不是一一对应的。通过重映射来表达每个像素的位置(x,y):g(x,y)=f(h(x,y));g是目标图像,f是源图像,h是映射函数。
-
7.3.2、实现重映射:remap()函数
- 基于公式:dst(x,y) = src((x,y), (x,y))
-
void remap(InputArray src, OutputArray dst, InputArray map1, InputArray map2, int interpolation, int borderMode=BORDER_CONSTANT, const Scalar &borderValue=Scalar())
1:输入图像;2:存放函数调用后的输出结果;3:表示点(x,y)的第一个映射或CV_16SC2、CV_32FC1 CV_32FC2类型的x值;4:同第三个参数,若map1表示点(x,y)时,这个参数不代表任何值;5:插值方式:INTRT_NEAREST------最近邻插值、INTER_LINEAR------双线性插值(默认值)、INTER_CUBIC------双三次样条插值、INTER_LANCOS4------Lanczos插值;6:边界模式,默认值CONSTANT,表示目标图像中的离群点的像素不会被此函数修改;7:当有常数边界时使用的值有默认值Scalar(),默认值为0
-
int main() { Mat srcImage, dstImage; Mat map_x, map_y; srcImage = imread("1.jpg"); imshow("【原始图】",srcImage); //创建和原始图一样的效果图,x重映射,y重映射 dstImage.create(srcImage.size(),srcImage.type()); map_x.create(srcImage.size(),CV_32FC1); map_y.create(srcImage.size(),CV_32FC1); //双重循环遍历每一个像素点,改变map_x,y的值 for(int j=0;j<srcImage.rows;j++) { for (int i=0;i<srcImage.cols;i++) { map_x.at<float>(j,i) = static_cast<float>(i); map_y.at<float>(j,i) = static_cast<float>(srcImage.rows-j); } } //进行重映射 remap(srcImage, dstImage, map_x,map_y,INTER_LINEAR,BORDER_CONSTANT,Scalar(0,0,0)); imshow("【程序窗口】",dstImage); waitKey(0); return 0; }
-
7.3.4、实现多种重映射
-
#include <opencv2/opencv.hpp> #include<opencv2/ highgui/highgui .hpp> #include<opencv2/imgproc/imgproc.hpp> using namespace cv; using namespace std; #define WINDOW_NAME "【程序窗口】" Mat g_srcImage,g_dstImage; Mat g_map_x,g_map_y; int update_map(int key); static void ShowHelpText(); int main(int argc, char **argv) { system("color 2F"); ShowHelpText(); g_srcImage= imread("1.jpg",1); //创建和原始图一样的效果图,x重映射,y重映射 g_dstImage.create(g_srcImage.size(),g_srcImage.type()); g_map_x.create(g_srcImage.size(),CV_32FC1); g_map_y.create(g_srcImage.size(),CV_32FC1); namedWindow(WINDOW_NAME,WINDOW_AUTOSIZE); imshow(WINDOW_NAME,g_srcImage); //更新x和y的值,进行重映射操作并显示效果图 while (1) { int key = waitKey(0); if((key &255)==27) { cout<<"程序退出。。。。。。。\n"; break; } //根据按下的键盘按键来更新map_x,y的值,调用remap进行重映射 remap(g_srcImage, g_dstImage, g_map_x,g_map_y, INTER_LINEAR,BORDER_CONSTANT,Scalar(0,0,0)); imshow(WINDOW_NAME,g_dstImage); } return 0; } int update_map(int key) { for(int j=0;j<g_srcImage.rows;j++) { for(int i=0; i<g_srcImage.cols;i++) { switch (key) { case '1'://1执行第一种重映射 if(i>g_srcImage.cols*0.25 && i<g_srcImage.cols*0.75 && j>g_srcImage.rows*0.25 && j<g_srcImage.rows*0.75) { g_map_x.at<float>(j,i) = static_cast<float>(2*(i-g_srcImage.cols*0.25)+0.5); g_map_y.at<float>(j,i)= static_cast<float>(2*(j-g_srcImage.rows*0.25)+0.5); } else{ g_map_x.at<float>(j,i)=0; g_map_y.at<float>(j,i)=0; } break; case '2': g_map_x.at<float>(j,i)=static_cast<float>(i); g_map_y.at<float>(j,i)=static_cast<float>(g_srcImage.rows-j); break; case '3': g_map_x.at<float>(j,i)=static_cast<float>(g_srcImage.cols-i); g_map_y.at<float>(j,i)=static_cast<float>(j); break; case '4': g_map_x.at<float>(j,i)=static_cast<float>(g_srcImage.cols-i); g_map_y.at<float>(j,i)=static_cast<float>(g_srcImage.rows-j); break; } } } return 1; } static void ShowHelpText() { printf("\n\n\n\t欢迎来到重映射示例程序···\n\n"); printf("\t当前opencv版本:"CV_VERSION); printf("\n\n\t按键操作说明:\n\n" "\t\t 【esc】---退出程序" "\t\t 【1】 ---第一种映射" "\t\t 【2】 ---第2种映射" "\t\t 【3】 ---第3种映射" "\t\t 【4】 ---第4种映射") }
-
-
7.4、仿射变换
-
7.4.1、认识仿射变换
- 是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间的过程,它保持了二维图形的“平直性”(直线经过变换之后依然是直线)和“平行性”(二维图形之间的相对位置关系保持不变,平行线依然是平行线,且直线上点的位置顺序不变)。
- 旋转,rotation(线性变换);平移, translation(向量加);缩放,scale(线性变换)
-
7.4.2、仿射变换求法
- 7.4.3、进行仿射变换:warpAffine()函数
- 公式:dst(x,y) = src(x +y+, x +y+)
-
void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, intborderMode = BORDER_CONSTANT,const Scalar& borderValue=Scalar())
1:输入图形;2:,函数调用后的运算结果存在这里;3:2x3的变换矩阵;4:输出图形尺寸;5:插值方式的标识符,默认为线性插值6:边界模式默认值为BORDER_CONSTANT;7:默认值为0,恒定边界情况下取的值
-
7.4.4、计算二维旋转变换矩阵:getRotationMatrix2D
- 此变换会将旋转中心映射到它自身
-
Mat getRotationMatrix2D(Point2f center, double angle, double scale)
1:源图像的旋转中心;2:旋转角度,为正表示逆时针旋转;3:缩放系数
-
7.4.5、示例程序:仿射变换
-
#include <opencv2/opencv.hpp> #include<opencv2/ highgui/highgui .hpp> #include<opencv2/imgproc/imgproc.hpp> using namespace cv; using namespace std; #define WINDOW_NAME1 "【原始图窗口】" #define WINDOW_NAME2 "【warp后窗口】" #define WINDOW_NAME1 "【warp和rotate后窗口】" static void ShowHelpText(); int main() { system("color 1A"); ShowHelpText(); //【1】参数准备 //定义两组点,表示两个三角形 Point2f srcTriangle[3]; Point2f dstTriangle[3]; //定义Mat变量 Mat rotMat(2,3,CV_32FC1); Mat warpMat(2,3,CV_32FC1); Mat srcImage,dstImage_warp,dstImage_warp_rotate; //【2】加载源图像并作一些初始化 srcImage = imread("1.jpg"); dstImage_warp = Mat::zeros(srcImage.rows,srcImage.cols, srcImage.type()); //【3】设置源图像和目标图形上的三组点以计算仿射变换 srcTriangle[0] = Point2f(0,0); srcTriangle[1] = Point2f(static_cast<float>(srcImage.cols-1),0); srcTriangle[2] = Point2f(0,static_cast<float>(srcImage.rows-1)); dstTriangle[0] = Point2f(static_cast<float>(srcImage.cols*0.0), static_cast<float>(srcImage.rows*0.33)); dstTriangle[1] = Point2f(static_cast<float>(srcImage.cols*0.65), static_cast<float>(srcImage.rows*0.35)); dstTriangle[0] = Point2f(static_cast<float>(srcImage.cols*0.15), static_cast<float>(srcImage.rows*0.6)); //【4】求得仿射变换 warpMat = getAffineTransform(srcTriangle,dstTriangle); //【5】对源图像应用刚刚求得的仿射变换 warpAffine(srcImage,dstImage_warp,warpMat,dstImage_warp.size()); //【6】对图形进行缩放后再旋转 //计算绕图像中点顺时针旋转50°缩放因子为0.6的旋转 矩阵 Point center = Point(dstImage_warp.cols/2,dstImage_warp/2); double angle = -30.0; double scale=0.8; //通过上边的旋转细节信息求得旋转矩阵 rotMat = getRotationMatrix2D(center, angle, scale); //旋转已缩放后的图像 warpAffine(dstImage_warp,dstImage_warp_rotate,rotMat,dstImage_warp.size()); imshow(WINDOW_NAME1,srcImage); imshow(WINDOW_NAME2,dstImage_warp); imshow(WINDOW_NAME3,dstImage_warp_rotate); waitKey(0); return 0; } static void ShowHelpText() { printf("\n\n\n\t 仿射变换示例程序\n\n"); printf("\t当前版本"CV_VERSION); }
-
-
7.5、直方图均衡化
-
7.5.1、直方图均衡化的概念和特点
- 直方图均衡化是灰度变换的一个重要应用,它高效且易于实现,广泛应用于 图像增强处理中。图像的像素灰度变化是随机的, 直方图的图形高低不齐, 直方 图均衡化就是用一定的算法使直方图大致平和的方法。
- 直方图均衡化是通过拉伸像素强度分布范围来增强图像对比度的一种方法。均衡化处理后的图像只能是近似均匀分布。 均衡化图像的动态范围扩大了,但其本质是扩大了量化间隔,而量化级别反而减少了,因此, 原来灰度不同的象素经处理后可能变的相同, 形成了一片相同灰度的区域, 各区域之间有明显的边 界,从而出现了伪轮廓。
- 在原始图像对比度本来就很高的情况下,如果再均衡化则灰度调和, 对比度 会降低。 在泛白缓和的图像中,均衡化会合并-些象素灰皮, 从而增大对比度。 均衡化后的图片如果再对其均衡化, 则图像不会有任何变化。
-
7.5.2、实现直方图均衡化:equalizeHist()函数
-
void equalizeHist(InputArray src, OutputArray dst)
1:输入图像;2:输出图像
-
均衡化步骤:
-
计算输入图像的直方图H
-
进行直方图归一化,直方图的组距的和为255
-
计算直方图积分:(i) = H(j)
-
以作为查询表进行图像变换:dst(x,y) = (src(x,y))文章来源:https://www.toymoban.com/news/detail-775447.html
-
此函数就是把直方图的每个灰度级进行归一化处理,求每种灰度的累积分布,得到一个映射的灰度映射表,然后根据相应的灰度值来修正原图中的每个像素文章来源地址https://www.toymoban.com/news/detail-775447.html
-
-
7.5.3、示例程序:直方图均衡化
-
int main() { Mat srcImage, dstImage; srcImage = imread("1.jpg"); cvtColor(srcImage, srcImage, COLOR_BGR2GRAY); imshow("【原始图】",srcImage); equalizeHist(srcImage,dstImage); imshow("【经过直方图均衡化后的图】",dstImage); waitKey(0); return 0; }
-
-
-
7.6、本章小结
-
到了这里,关于OpenCV:第七章、图像变换的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!