精华内容
下载资源
问答
  • 图像旋转原理与实现

    千次阅读 2019-06-17 12:00:28
    图像旋转原理与实现 图像旋转原理与实现 一般图像旋转是以图像的中心为原点,旋转一定的角度,也就是将图像上的所有像素都旋转一个相同的角度。旋转图像的的大小一般会改变,即可以把转出显示区域的图像截...

    图像旋转的原理与实现

    图像旋转的原理与实现

    一般图像的旋转是以图像的中心为原点,旋转一定的角度,也就是将图像上的所有像素都旋转一个相同的角度。旋转后图像的的大小一般会改变,即可以把转出显示区域的图像截去,或者扩大图像范围来显示所有的图像。图像的旋转变换也可以用矩阵变换来表示。设点clip_image002[14]逆时针旋转clip_image004[14]角后的对应点为clip_image006[14]。那么,旋转前后点clip_image002[15]clip_image006[15]的坐标分别是:

    clip_image009[10]                          (3-6)

    clip_image011[10]  (3-7)

     

     

    写成矩阵表达式为

    clip_image013[10]            (3-8)

     

     

     

     

    其逆运算为

    clip_image015[10]            (3-9)

     

     

    利用上述方法进行图像旋转时需要注意如下两点:

    (1)图像旋转之前,为了避免信息的丢失,一定要有坐标平移。

    (2)图像旋转之后,会出现许多空洞点。对这些空洞点必须进行填充处理,否则画面效果不好,一般也称这种操作为插值处理。

    以上所讨论的旋转是绕坐标轴原点(0,0)进行的。如果图像旋转是绕一个指定点(a,b)旋转,则先要将坐标系平移到该点,再进行旋转,然后将旋转后的图象平移回原来的坐标原点,这实际上是图像的复合变换。如将一幅图像绕点(a,b)逆时针旋转clip_image004[15]度,首先将原点平移到(a,b),即

    clip_image018[10]                        (3-10)

    然后旋转

    clip_image020[10]                 (3-11)

    然后再平移回来

    clip_image022[10]                        (3-12)

     

    综上所述,变换矩阵为clip_image024[10]

    代码:

    B=imread('image1.bmp');
    %读取原图像
    [m,n]=size(B); %获取原图尺寸w
    %参数设置
    theta = pi/4;  %旋转角度
    a = sin(theta);
    b = cos(theta);
    T = [cos(theta),sin(theta),;    %旋转矩阵
        -sin(theta),cos(theta)];
      
    %建立存储空间
    row=m+round((m)/2);
    col=n+round((n)/2);
    rotateima = zeros(row, col);  %存储旋转后图像的矩阵
      
    %图像旋转        
    for i=1:m                                      
        for j=1:n
            x=ceil(abs((i-round(m/2))*b-(j-round(n/2))*a+round(row/2)));  %坐标平移至中心
            y=ceil(abs((i-round(m/2))*a+(j-round(n/2))*b+round(col/2)));  %坐标平移至中心
            rotateima(x,y)=B(i,j);                   %未插值的图像
        end
    end
    nrotateima = uint8(rotateima);
    imshow(nrotateima);
    title('未插值的图像')
      
    %图像插值(近邻插值法)
    for i=1:row   
        for j=2:col-1      
                   
                if(rotateima(i,j) == 0 && rotateima(i,j-1) ~= 0 && rotateima(i,j+1) ~= 0 )      
                    rotateima(i,j) =rotateima(i,j-1) ;     
                
                end
        end
    end
      
    %图像显示
    figure(1);
    imshow(B)
    title('原始图像');
    % figure(2);
    % imshow(nrotateima);
    % title('未插值的图像');
    figure(3);
    imshow(rotateima/256);
    imwrite(rotateima/256, '旋转后图像.jpg', 'jpg');
    title('旋转图');

    原文链接:https://www.cnblogs.com/hustlx/p/5245226.html

    展开全文
  • 图像旋转算法原理

    千次阅读 2017-03-16 11:16:40
    图像旋转算法原理


    本文主要讲解图像处理中使用比较广泛的图像旋转算法原理,如下:

    原文地址:http://blog.163.com/yuyang_tech/blog/static/21605008320138455714221/?suggestedreading&wumii

    展开全文
  • 图像平移 旋转原理
  • 图像旋转原理,实现优化

    千次阅读 2016-09-11 15:19:40
    图像旋转原理,实现和优化


    图像旋转的原理

    图像旋转的原理其实很简单,为了简化公式的推导,这里我们假设绕原点 ( 0 , 0 ) (0,0) (0,0)旋转。

    这里写图片描述

    本文规定逆时针旋转为正,当然你也可以规定顺时针为正(此时,正角度就表示顺时针旋转)
    很容易得到三个方程:
    t a n ( θ + α ) = y ′ x ′ ( 1 ) tan(\theta + \alpha)={y' \over x'} \quad \quad \quad (1) tan(θ+α)=xy(1)
    t a n α = y x ( 2 ) tan\alpha={y \over x} \quad \quad \quad (2) tanα=xy(2)
    x 2 + y 2 = x ′ 2 + y ′ 2 ( 3 ) x^2+y^2={x'}^2+{y'}^2 \quad \quad \quad (3) x2+y2=x2+y2(3)
    由和角公式可知
    t a n ( θ + α ) = t a n θ + t a n α 1 − t a n θ t a n α tan(\theta + \alpha)={{tan\theta+tan\alpha} \over {1-tan\theta tan\alpha}} tan(θ+α)=1tanθtanαtanθ+tanα
    代入(1)和(2)可得
    y ′ = x ′ ( y + x t a n θ ) x − y t a n θ ( 4 ) y'={{x'(y+xtan\theta)} \over {x-ytan\theta}} \quad \quad \quad (4) y=xytanθx(y+xtanθ)(4)
    (4)代入(3)得到:
    x ′ 2 = ( x c o s θ − y s i n θ ) 2 ( 5 ) x'^2=(xcos\theta-ysin\theta)^2 \quad \quad \quad (5) x2=(xcosθysinθ)2(5)
    (5)代入(3)
    y ′ 2 = ( x s i n θ + y c o s θ ) 2 y'^2=(xsin\theta+ycos\theta)^2 y2=(xsinθ+ycosθ)2
    所以
    { x ′ = x c o s θ + y ( − s i n θ ) y ′ = x s i n θ + y c o s θ ( 6 ) \begin{cases} x '=xcos\theta +y(-sin\theta) \\ y'=xsin\theta + ycos \theta \end{cases} \quad \quad \quad (6) {x=xcosθ+y(sinθ)y=xsinθ+ycosθ(6)
    我们知道仿射变换矩阵可以表示为:
    A = [ c o s θ − s i n θ s i n θ c o s θ ] (7) A=\left[ \begin{matrix} cos\theta & -sin\theta \\ sin\theta & cos \theta \\ \end{matrix} \right] \tag{7} A=[cosθsinθsinθcosθ](7)
    公式7与公式6在形式上是一致的(这里没有加入平移和缩放)
    注意公式6中 x , y , x ′ , y ′ x,y,x',y' x,y,x,y实际表示 x − 0 , y − 0 , x ′ − 0 , y ′ − 0 x-0,y-0,x'-0,y'-0 x0,y0,x0,y0即与旋转中心横坐标以及纵坐标之间的距离,现在我们将旋转中心变为更具有一般性的 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),得旋转公式为:
    { x ′ = ( x − x 0 ) c o s θ + ( y − y 0 ) ( − s i n θ ) + x 0 y ′ = ( x − x 0 ) s i n θ + ( y − y 0 ) c o s θ + y 0 ( 8 ) \begin{cases} x '=(x-x_0)cos\theta +(y-y_0)(-sin\theta)+x_0 \\ y'=(x-x_0)sin\theta + (y-y_0)cos \theta+y_0 \end{cases} \quad \quad \quad (8) {x=(xx0)cosθ+(yy0)(sinθ)+x0y=(xx0)sinθ+(yy0)cosθ+y0(8)
    对于图像来说,规定顺时针旋转角度为正,则旋转公式与上述公式一致,由于任意角度的旋转后会出现像素坐标为负的情况,如果不将旋转后的图像坐标平移,会缺失部分图像,所以,对于旋转后的图像,我们通常会加入一个平移
    { x ′ = ( x − x 0 ) c o s θ + ( y − y 0 ) ( − s i n θ ) + x 0 + Δ x y ′ = ( x − x 0 ) s i n θ + ( y − y 0 ) c o s θ + y 0 + Δ y ( 9 ) \begin{cases} x '=(x-x_0)cos\theta +(y-y_0)(-sin\theta)+x_0+\Delta x \\ y'=(x-x_0)sin\theta + (y-y_0)cos \theta+y_0+\Delta y \end{cases} \quad \quad \quad (9) {x=(xx0)cosθ+(yy0)(sinθ)+x0+Δxy=(xx0)sinθ+(yy0)cosθ+y0+Δy(9)

    图像中使用后向映射,使用 x ′ , y ′ x',y' x,y表示 x , y x,y x,y,则上述公式变为
    { x = ( ( x ′ − x 0 − Δ x ) c o s θ + ( y ′ − y 0 − Δ y ) s i n θ ) / s c a l e + x 0 y = ( ( x ′ − x 0 − Δ x ) ( − s i n θ ) + ( y ′ − y 0 − Δ y ) c o s θ ) / s c a l e + y 0 ( 10 ) \begin{cases} x=((x'-x_0-\Delta x)cos\theta +(y'-y_0-\Delta y)sin\theta)/scale+x_0 \\ y=((x'-x_0-\Delta x)(-sin\theta) + (y'-y_0-\Delta y)cos \theta)/scale+y_0 \end{cases} \quad \quad \quad (10) {x=((xx0Δx)cosθ+(yy0Δy)sinθ)/scale+x0y=((xx0Δx)(sinθ)+(yy0Δy)cosθ)/scale+y0(10)
    这里加入了缩放,其中:scale>1表示原图放大 <1表示原图缩小
    有了上面的公式,下面就可以写出相应的代码了

    注:为什么公式推导的时候选用右手系?因为右手系与图像坐标系推导出来的公式在形式上是统一的,而右手系又是我们熟悉的坐标系。


    图像旋转的实现

    最近邻插值

    先给出一个结构最简洁的版本,采用最近邻插值

    // 宏定义
    #define DEGREE2RADIAN(x) (x*CV_PI/180)//角度转弧度
    #define RADIAN2DEGREE(x) (x*180/CV_PI)//弧度转角度
    #define  SHIFT  10
    #define  DESCALE(x,n)  (((x)+(1 << ((n)-1))) >> (n))
    /*	center:原图像的旋转中心
    	dstSize:旋转后图像的大小
    	theta:旋转角度,单位弧度,顺时针为正
    	scale:缩放,scale>1表示放大  <1表示缩小
    */ 
    void Rotate_Nearest(const Mat &srcImage, Mat &dstImage, Point center, Size dstSize, double theta, double scale)
    {
    	CV_Assert(srcImage.depth() == CV_8U);
    	dstImage.create(dstSize, srcImage.type());
    
    	int x0 = center.x;
    	int y0 = center.y;
    	theta = DEGREE2RADIAN(theta);
    
    	// dx,dy就是dst与src图像中心的距离 
    	int dx = dstImage.cols/2 - srcImage.cols/2;
    	int dy = dstImage.rows/2 - srcImage.rows/2;
    	int numberOfChannels = srcImage.channels();
    
    	int widthOfDst = dstImage.cols;
    	int heightOfDst = dstImage.rows;
    	
    	for (int y = 0; y <= heightOfDst - 1; ++y)
    	{
    		for (int x = 0; x <= widthOfDst - 1; ++x)
    		{
    			float srcX = ((x - x0 - dx)*cos(theta) + (y - y0 - dy)*sin(theta))/scale + x0;
    			float srcY = ((x0 + dx - x)*sin(theta) + (y - y0 - dy)*cos(theta))/scale + y0;
    
    			// get the nearest coordinate of src
    			int x1 = (int)srcX;
    			int y1 = (int)srcY;
    			if (numberOfChannels == 1)
    			{
    				if ((x1 >= 0 && x1 <= srcImage.cols - 1) && (y1 >= 0 && y1 <= srcImage.rows - 1))
    				{
    					dstImage.at<uchar>(y, x) = srcImage.at<uchar>(y1, x1);
    				}
    				else
    				{
    					// 越界赋值0
    					dstImage.at<uchar>(y, x) = 0;
    				}
    			}
    			else
    			{
    				if ((x1 >= 0 && x1 <= srcImage.cols - 1) && (y1 >= 0 && y1 <= srcImage.rows - 1))
    				{
    					dstImage.at<cv::Vec3b>(y, x) = srcImage.at<cv::Vec3b>(y1, x1);
    				}
    				else
    				{
    					dstImage.at<cv::Vec3b>(y, x) = cv::Vec3b(0,0,0);
    				}
    
    			}
    		}
    	}
    
    }
    

    测试使用的原始图像大小为1000 x 580 (三通道彩色图)
    测试代码:
    Rotate_Nearest(srcImage, dstImage, Point(srcImage.cols / 2, srcImage.rows / 2), Size(2500, 2500), 30.0, 2);
    效果:
    原图:
    这里写图片描述
    结果:
    这里写图片描述
    无缩放测试:
    Rotate_Nearest(srcImage, dstImage, Point(srcImage.cols / 2, srcImage.rows / 2), Size(2500, 2500), 30.0, 1);
    结果:
    这里写图片描述

    双线性插值

    下面我们对他进行优化,首先我们将最近邻插值改用更为通用的双线性插值,代码如下

    void Rotate_Bilinear(const Mat &srcImage, Mat &dstImage, Point center, Size dstSize, double theta, double scale)
    {
    	CV_Assert(srcImage.depth() == CV_8U);
    	dstImage.create(dstSize, srcImage.type());
    	dstImage.setTo(Scalar(0, 0, 0));
    
    	int x0 = center.x;
    	int y0 = center.y;
    	theta = DEGREE2RADIAN(theta);
    
    	// 
    	Mat extendedImage;
    	copyMakeBorder(srcImage, extendedImage, 1, 1, 1, 1, BORDER_CONSTANT,Scalar(0,0,0)); // 使用0填充边界
    
    	// dx,dy就是dst与src图像中心的距离 
    	int dx = dstImage.cols / 2 - srcImage.cols / 2;
    	int dy = dstImage.rows / 2 - srcImage.rows / 2;
    	int numberOfChannels = srcImage.channels();
    
    	int widthOfDst = dstImage.cols;
    	int heightOfDst = dstImage.rows;
    
    	for (int y = 0; y <= heightOfDst - 1; ++y)
    	{
    		for (int x = 0; x <= widthOfDst - 1; ++x)
    		{
    			// 按照原来的方式计算原图坐标
    			float srcX = ((x - x0 - dx)*cos(theta) + (y - y0 - dy)*sin(theta)) / scale + x0;
    			float srcY = ((x0 + dx - x)*sin(theta) + (y - y0 - dy)*cos(theta)) / scale + y0;
    			
    			// 加1,得到在extendedImage中的坐标
    			srcX++; 
    			srcY++;
    			 
    			// get the nearest coordinate of src
    			int x1 = (int)(srcX); 
    			int y1 = (int)(srcY);
    
    			// 浮点转化为整数
    			int dx1 = (srcX - x1)*(1<< SHIFT);
    			int dy1 = (srcY - y1)*(1<< SHIFT);
    
    			if (numberOfChannels == 1)
    			{
    				// !!!注意这里的范围,在extendedImage中,原图的范围就是1~cols - 2了
    				if ((x1 >= 1 && x1 <= extendedImage.cols - 2) && (y1 >= 1 && y1 <= extendedImage.rows - 2))
    				{	
    					//双线性插值
    					//周围4个点
    					//a就是最近邻像素
    					//a   b
    					//  p
    					//c   d
    					uchar a = extendedImage.at<uchar>(y1, x1);
    					uchar b = extendedImage.at<uchar>(y1, x1 + 1);
    					uchar c = extendedImage.at<uchar>(y1 + 1, x1);
    					uchar d = extendedImage.at<uchar>(y1 + 1, x1 + 1);
    
    					
    					//int p = (a*((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b*dx1*((1 << SHIFT) - dy1) + c*((1 << SHIFT) - dx1)*dy1 + d*dx1*dy1)/(1<<(2* SHIFT));
    					
    					int p = a*((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b*dx1*((1 << SHIFT) - dy1) + c*((1 << SHIFT) - dx1)*dy1 + d*dx1*dy1;
    					p = DESCALE(p, 2*SHIFT);
    
    					dstImage.at<uchar>(y, x) = p;
    				}
    				else
    				{
    					// 越界赋值0
    					dstImage.at<uchar>(y, x) = 0;
    				}
    			}
    			else
    			{
    				if ((x1 >= 1 && x1 <= extendedImage.cols - 2) && (y1 >= 1 && y1 <= extendedImage.rows - 2))
    				{
    					//双线性插值
    					//周围4个点
    					//a就是最近邻像素
    					//a   b
    					//  p
    					//c   d
    					Vec3b a = extendedImage.at<Vec3b>(y1, x1);
    					Vec3b b = extendedImage.at<Vec3b>(y1, x1 + 1);
    					Vec3b c = extendedImage.at<Vec3b>(y1 + 1, x1);
    					Vec3b d = extendedImage.at<Vec3b>(y1 + 1, x1 + 1);
    
    					
    					/*int p1 = (a[0] * ((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b[0] * dx1*((1 << SHIFT) - dy1) + c[0] * ((1 << SHIFT) - dx1)*dy1 + d[0] * dx1*dy1)/(1<<(2*SHIFT));
    					int p2 = (a[1] * ((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b[1] * dx1*((1 << SHIFT) - dy1) + c[1] * ((1 << SHIFT) - dx1)*dy1 + d[1] * dx1*dy1)/ (1 << (2 * SHIFT));
    					int p3 = (a[2] * ((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b[2] * dx1*((1 << SHIFT) - dy1) + c[2] * ((1 << SHIFT) - dx1)*dy1 + d[2] * dx1*dy1)/ (1 << (2 * SHIFT));*/
    
    					int p1 = a[0]*((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b[0]*dx1*((1 << SHIFT) - dy1) + c[0]*((1 << SHIFT) - dx1)*dy1 + d[0]*dx1*dy1;
    					p1 = DESCALE(p1, 2 * SHIFT);
    					int p2 = a[1]*((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b[1]*dx1*((1 << SHIFT) - dy1) + c[1]*((1 << SHIFT) - dx1)*dy1 + d[1] *dx1*dy1;
    					p2 = DESCALE(p2, 2 * SHIFT);
    					int p3 = a[2]*((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b[2]*dx1*((1 << SHIFT) - dy1) + c[2]*((1 << SHIFT) - dx1)*dy1 + d[2] *dx1*dy1;
    					p3 = DESCALE(p3, 2 * SHIFT);
    
    
    					dstImage.at<cv::Vec3b>(y, x) = Vec3b(p1,p2,p3);
    				}
    				else
    				{
    					dstImage.at<cv::Vec3b>(y, x) = cv::Vec3b(0, 0, 0);
    				}
    
    			}
    		}
    	}
    }
    

    测试方法与上述相同,测试代码如下:
    Rotate_Bilinear(srcImage, dstImage, Point(srcImage.cols / 2, srcImage.rows / 2), Size(2500, 2500),30.0, 2);
    基本旋转效果都是一样的,下面我们看局部放大图
    这里写图片描述
    这是最近邻的眼部区域
    这里写图片描述
    双线性在保持图像细节方面要好于最近邻,而且不容易产生锯齿

    双线性的优化

    上面的双线性插值速度比较慢,我们对其进行优化,主要优化有:
    1.将循环内部不变量提取出来
    由于sin和cos的计算是比较慢的函数,所以可以将他们提前计算好,而不是每次循环都要计算
    如:
    double sinTheta = sin(theta);
    double cosTheta = cos(theta);

    2.改变循环体内循环变量自增的方式
    采用加法自增的方式,而不是每次都要计算乘法,详见代码

    注:浮点数转化为整数的优化,上面已经涉及,这里不再说明

    优化后的代码如下:

    void Rotate_Bilinear2(const Mat &srcImage, Mat &dstImage, Point center, Size dstSize, double theta, double scale)
    {
    	CV_Assert(srcImage.depth() == CV_8U);
    	dstImage.create(dstSize, srcImage.type());
    	dstImage.setTo(Scalar(0, 0, 0));
    
    	int x0 = center.x;
    	int y0 = center.y;
    	theta = DEGREE2RADIAN(theta);
    
    	// 
    	Mat extendedImage;
    	copyMakeBorder(srcImage, extendedImage, 1, 1, 1, 1, BORDER_CONSTANT, Scalar(0, 0, 0)); // 使用0填充边界
    
    																						   // dx,dy就是dst与src图像中心的距离 
    	int dx = dstImage.cols / 2 - srcImage.cols / 2;
    	int dy = dstImage.rows / 2 - srcImage.rows / 2;
    	int numberOfChannels = srcImage.channels();
    
    	int widthOfDst = dstImage.cols;
    	int heightOfDst = dstImage.rows;
    
    	// 优化部分/
    	// 将循环内的不变量提取出来
    	double sinTheta = sin(theta);
    	double cosTheta = cos(theta);
    	scale = 1.0 / scale;
    
    	// 改变了循环内部增量的方式
    	double temp1= (0 - y0 - dy)*sinTheta;
    	double temp2 = (0 - y0 - dy)*cosTheta;
    	double dtemp1 = sinTheta;
    	double dtemp2 = cosTheta;
    
    	for (int y = 0; y <= heightOfDst - 1; ++y,temp1+=dtemp1,temp2+=dtemp2)
    	{
    		// 改变了循环内部增量的方式
    		double temp3= ((0 - x0 - dx)*cosTheta + temp1)*scale + x0;
    		double temp4= (-(0 - x0 - dx)*sinTheta + temp2)*scale + y0;
    		double dtemp3 = (cosTheta)*scale;
    		double dtemp4= (-sinTheta)*scale;
    		for (int x = 0; x <= widthOfDst - 1; ++x,temp3+=dtemp3,temp4+=dtemp4)
    		{
    			// 计算原图坐标
    			double srcX = temp3;
    			double srcY = temp4;
    
    			// 加1,得到在extendedImage中的坐标
    			srcX++;
    			srcY++;
    
    			// get the nearest coordinate of src
    			int x1 = (int)(srcX);
    			int y1 = (int)(srcY);
    
    			// 浮点转化为整数
    			int dx1 = (srcX - x1)*(1 << SHIFT);
    			int dy1 = (srcY - y1)*(1 << SHIFT);
    
    			if (numberOfChannels == 1)
    			{
    				// !!!注意这里的范围,在extendedImage中,原图的范围就是1~cols - 2了
    				if ((x1 >= 1 && x1 <= extendedImage.cols - 2) && (y1 >= 1 && y1 <= extendedImage.rows - 2))
    				{
    					//双线性插值
    					//周围4个点
    					//a就是最近邻像素
    					//a   b
    					//  p
    					//c   d
    					uchar a = extendedImage.at<uchar>(y1, x1);
    					uchar b = extendedImage.at<uchar>(y1, x1 + 1);
    					uchar c = extendedImage.at<uchar>(y1 + 1, x1);
    					uchar d = extendedImage.at<uchar>(y1 + 1, x1 + 1);
    
    					
    					//int p = (a*((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b*dx1*((1 << SHIFT) - dy1) + c*((1 << SHIFT) - dx1)*dy1 + d*dx1*dy1)/(1<<(2* SHIFT));
    
    					int p = a*((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b*dx1*((1 << SHIFT) - dy1) + c*((1 << SHIFT) - dx1)*dy1 + d*dx1*dy1;
    					p = DESCALE(p, 2 * SHIFT);
    
    					dstImage.at<uchar>(y, x) = p;
    				}
    				else
    				{
    					// 越界赋值0
    					dstImage.at<uchar>(y, x) = 0;
    				}
    			}
    			else
    			{
    				if ((x1 >= 1 && x1 <= extendedImage.cols - 2) && (y1 >= 1 && y1 <= extendedImage.rows - 2))
    				{
    					//双线性插值
    					//周围4个点
    					//a就是最近邻像素
    					//a   b
    					//  p
    					//c   d
    					Vec3b a = extendedImage.at<Vec3b>(y1, x1);
    					Vec3b b = extendedImage.at<Vec3b>(y1, x1 + 1);
    					Vec3b c = extendedImage.at<Vec3b>(y1 + 1, x1);
    					Vec3b d = extendedImage.at<Vec3b>(y1 + 1, x1 + 1);
    
    					
    					/*int p1 = (a[0] * ((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b[0] * dx1*((1 << SHIFT) - dy1) + c[0] * ((1 << SHIFT) - dx1)*dy1 + d[0] * dx1*dy1)/(1<<(2*SHIFT));
    					int p2 = (a[1] * ((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b[1] * dx1*((1 << SHIFT) - dy1) + c[1] * ((1 << SHIFT) - dx1)*dy1 + d[1] * dx1*dy1)/ (1 << (2 * SHIFT));
    					int p3 = (a[2] * ((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b[2] * dx1*((1 << SHIFT) - dy1) + c[2] * ((1 << SHIFT) - dx1)*dy1 + d[2] * dx1*dy1)/ (1 << (2 * SHIFT));*/
    
    					int p1 = a[0] * ((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b[0] * dx1*((1 << SHIFT) - dy1) + c[0] * ((1 << SHIFT) - dx1)*dy1 + d[0] * dx1*dy1;
    					p1 = DESCALE(p1, 2 * SHIFT);
    					int p2 = a[1] * ((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b[1] * dx1*((1 << SHIFT) - dy1) + c[1] * ((1 << SHIFT) - dx1)*dy1 + d[1] * dx1*dy1;
    					p2 = DESCALE(p2, 2 * SHIFT);
    					int p3 = a[2] * ((1 << SHIFT) - dx1)*((1 << SHIFT) - dy1) + b[2] * dx1*((1 << SHIFT) - dy1) + c[2] * ((1 << SHIFT) - dx1)*dy1 + d[2] * dx1*dy1;
    					p3 = DESCALE(p3, 2 * SHIFT);
    
    
    					dstImage.at<cv::Vec3b>(y, x) = Vec3b(p1, p2, p3);
    				}
    				else
    				{
    					dstImage.at<cv::Vec3b>(y, x) = cv::Vec3b(0, 0, 0);
    				}
    
    			}
    		}
    	}
    }
    

    下面我们测试一下他们的速度
    测试环境:Intel core i5-6200U,12G,放大2倍

    测试方法Rotate_NearestRotate_BilinearRotate_Bilinear2
    速度(单位ms)58.2144.978.7

    可以看出,优化后的代码速度提升还是很明显的。

    双线性代码还可以进一步的优化,有兴趣的朋友可以自己实现。

    完整工程见github项目:QQImageProcess_OpenCV
    其中图像旋转优化的实现在 Src/ImageProcess/GeometryTransformation.h中的Rotate_系列函数中。


    2016-9-11 15:12:24
    Last Updated:2017-3-11 23:22:16


    非常感谢您的阅读,如果您觉得这篇文章对您有帮助,欢迎扫码进行赞赏。
    这里写图片描述

    展开全文
  • 点击上方“小白学视觉”,选择加"星标"或“置顶”重磅干货,第一时间送达转自|OpenCV学堂01引言初学图像处理,很多人遇到的第一关就是图像旋转图像旋转图像几何变换中...

    点击上方“小白学视觉”,选择加"星标"或“置顶

    重磅干货,第一时间送达

    转自|OpenCV学堂

    01

    引言

    初学图像处理,很多人遇到的第一关就是图像旋转,图像旋转是图像几何变换中最具代表性的操作,包含了插值、背景处理、三角函数等相关知识,一个变换矩阵跟计算图像旋转之后的大小公式就让很多开发者最后直接调用函数了事,但是其实这个东西并没有这么难懂,可以说主要是之前别人写的公式太吓人,小编很久以前第一次接触的也是被吓晕了!所以决定从程序员可以接受的角度从新介绍一下图像旋转基本原理与OpenCV中图像旋转函数操作的基本技巧。

    图像旋转基本原理

    旋转涉及到两个问题,一个是图像旋转之后的大小会发生改变,会产生背景,通过背景填充方式都是填充黑色,此外旋转还是产生像素的位置迁移,新的位置像素需要通过插值计算获得,常见的插值方式有最近邻、线性插值、立方插值等。

    首先看旋转之后的图像宽高变化,如下图所示:

    这个是正常的平面坐标系中的旋转矩阵,可以简写为:

    是一个2x3的矩阵,但是在图像中左上角是原点,要实现围绕图像的中心位置旋转,M就要重新计算,所以OpenCV中的图像旋转矩阵为:

    其中scale是表示矩阵支持旋转+放缩,这里可以把Scale=1。第三列是图像旋转之后中心位置平移量。

    函数支持

    OpenCV中支持图像旋转的函数有两个,一个是直接支持旋转的函数,但是它支持的是90,180,270这样的特殊角度旋转。

    void cv::rotate   (
        InputArray    src,
        OutputArray dst,
        int rotateCode
    )
    

    其中rotateCode参数必须为:

    ROTATE_180,
    ROTATE_90_CLOCKWISE
    ROTATE_90_COUNTERCLOCKWISE 
    

    函数warpAffine支持任意角度的旋转,通过定义M矩阵实现

    void cv::warpAffine(
             InputArray      src, // 输入图像
             OutputArray dst, // 输出图像
             InputArray      M, // 旋转矩阵
             Size         dsize, // 输出图像大小
             int   flags = INTER_LINEAR, // 像素插值方式
             int   borderMode = BORDER_CONSTANT, // 背景填充默认为常量
             const Scalar &        borderValue = Scalar() // 填充颜色默认为黑色
    )
    

    但是M如何生成与获取,OpenCV中提供了一个函数根据输入的参数自动生成旋转矩阵M,该函数为:

    Mat cv::getRotationMatrix2D(
             Point2f   center,
             double    angle,
             double    scale
    )
    

    代码演示

    原图

    使用自定义的M矩阵实现图像旋转

    h, w, c = src.shape
    # 定义矩阵
    M = np.zeros((2, 3), dtype=np.float32)
    # 定义角度
    alpha = np.cos(np.pi / 4.0)
    beta = np.sin(np.pi / 4.0)
    print("alpha : ", alpha)
    # 初始化矩阵
    M[0, 0] = alpha
    M[1, 1] = alpha
    M[0, 1] = beta
    M[1, 0] = -beta
    cx = w / 2
    cy = h / 2
    tx = (1-alpha)*cx - beta*cy
    ty = beta*cx + (1-alpha)*cy
    M[0,2] = tx
    M[1,2] = ty
    # 执行旋转
    dst = cv.warpAffine(src, M, (w, h))
    cv.imshow("rotate-center-demo", dst)

    重新计算旋转之后的图像大小,实现无Crop版本的图像旋转

    h, w, c = src.shape
    M = np.zeros((2, 3), dtype=np.float32)
    alpha = np.cos(np.pi / 4.0)
    beta = np.sin(np.pi / 4.0)
    print("alpha : ", alpha)
    
    # 初始旋转矩阵
    M[0, 0] = alpha
    M[1, 1] = alpha
    M[0, 1] = beta
    M[1, 0] = -beta
    cx = w / 2
    cy = h / 2
    tx = (1-alpha)*cx - beta*cy
    ty = beta*cx + (1-alpha)*cy
    M[0,2] = tx
    M[1,2] = ty
    
    # change with full size
    bound_w = int(h * np.abs(beta) + w * np.abs(alpha))
    bound_h = int(h * np.abs(alpha) + w * np.abs(beta))
    
    # 添加中心位置迁移
    M[0, 2] += bound_w / 2 - cx
    M[1, 2] += bound_h / 2 - cy
    dst = cv.warpAffine(src, M, (bound_w, bound_h))
    cv.imshow("rotate without cropping", dst)

    背景随便变化+无Crop版本的图像旋转动态演示

    degree = 1.0
    d1 = np.pi / 180.0
    while True:
        alpha = np.cos(d1*degree)
        beta = np.sin(d1*degree)
        M[0, 0] = alpha
        M[1, 1] = alpha
        M[0, 1] = beta
        M[1, 0] = -beta
        cx = w / 2
        cy = h / 2
        tx = (1 - alpha) * cx - beta * cy
        ty = beta * cx + (1 - alpha) * cy
        M[0, 2] = tx
        M[1, 2] = ty
    
        # change with full size
        bound_w = int(h * np.abs(beta) + w * np.abs(alpha))
        bound_h = int(h * np.abs(alpha) + w * np.abs(beta))
        M[0, 2] += bound_w / 2 - cx
        M[1, 2] += bound_h / 2 - cy
        red = np.random.randint(0, 255)
        green = np.random.randint(0, 255)
        blue = np.random.randint(0, 255)
        dst = cv.warpAffine(src, M, (bound_w, bound_h), borderMode=cv.BORDER_CONSTANT, borderValue=(blue, green, red))
        cv.imshow("rotate+background", dst)
        c = cv.waitKey(1000)
        if c == 27:
            break
        degree += 1
        print("degree", degree)
        if degree > 360:
            degree = degree % 360

    下载1:OpenCV-Contrib扩展模块中文版教程

    在「小白学视觉」公众号后台回复:扩展模块中文教程即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。

    下载2:Python视觉实战项目31讲

    在「小白学视觉」公众号后台回复:Python视觉实战项目31讲即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。

    下载3:OpenCV实战项目20讲

    在「小白学视觉」公众号后台回复:OpenCV实战项目20讲即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。

    下载4:leetcode算法开源书

    在「小白学视觉」公众号后台回复:leetcode即可下载。每题都 runtime beats 100% 的开源好书,你值得拥有!

    
    

    交流群

    欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

    展开全文
  • 通过对通用的图像旋转矩阵作变换处理,给出了基于错切原理实现图像旋转的推导过程。基于错切原理图像旋转避免了通用旋转方法带来的缩放效应。
  • 详解 图像旋转变换 原理

    千次阅读 多人点赞 2019-10-17 17:26:36
    1、简介 旋转变换是计算机图像学中应用非常广泛的一种变换,为了后面解释的需要,我们也添加了平移变换、缩放变化等内容。文章重点介绍关于旋转的变换,包括二维旋转变换、三维旋转变换以及它的一些表达方式(旋转...
  • 图像旋转原理及实现

    万次阅读 多人点赞 2016-01-21 17:57:14
    在一次数字图像处理课中,我接触到了图片旋转的一些原理,当时没完全想明白,课后通过一段时间的学习,终于完成了图片旋转的matlab程序。开始觉得应该挺简单的,但终究是纸上谈兵,在实现的过程中遇到很多问题。
  • 在进行图像随机旋转的同时,相应的label值坐标也要进行相应的旋转。 如人脸关键点检测中人脸对应的关键点坐标,物体检测任务中的物体所在box坐标。 (1).原理-------坐标旋转变换公式 原理参照博客: ...
  • vc旋转原理,对于使用vc编写旋转程序而言是最好不过的选择!
  • 图像旋转算法原理 Python实现

    千次阅读 2019-05-14 18:22:23
    旋转原理 参考博客:https://blog.csdn.net/liyuan02/article/details/6750828 如下图,推导点(x0,y0)(x_0,y_0)(x0​,y0​)旋转θ\thetaθ到到点(x,y)(x,y)(x,y),半径为R 对于两点坐标可以这样表示: x0=R∗cos...
  • 文章目录四、matlb的imrotate函数一、图像旋转原理二、使用matlab实现1、思路2、实现代码3、优化1、思路2、代码实现3、使用双线性插值4、matlab自带的imrotate函数 四、matlb的imrotate函数 一、图像旋转原理  ...
  • 此文要说明图像旋转原理
  • 利用OpenCV实现旋转文本图像矫正的原理及OpenCV代码-附件资源
  • http://blog.csdn.net/liyuan02/article/details/6750828 图1     图2     图3      图4      图5      ...
  • 在做人脸或物体检测的任务中...在进行图像随机旋转的同时,相应的Label值坐标,如人脸关键点检测中人脸对应的关键点坐标,物体检测任务中的物体所在box坐标。 (1).原理-------坐标旋转变换公式 原理参照博客: ...
  • 分析了图像旋转的几何原理.并对图像旋转中用到的插值处理方法进行了比较和分析.
  • 该文章介绍了 采用最近邻方法实现函数旋转原理以及代码实现,旋转后保留图片内容,图片变大了一丢丢~
  • 图像旋转原理旋转公式

    千次阅读 2008-12-18 13:38:00
    如果果一个点(x1,y1)旋转到(x2,y2),对应的角度旋转从θ1到θ1+θ2sinθ1=y1/sqrt(x1*x1+ y1*y1) cosθ1=x1/sqrt(x1*x1+ y1*y1) sin(θ1+θ2)=sin(θ1)*cos(θ2)+cos(θ1)*sin(θ2)=y2/sqrt(x2*x2+y2*y2)cos(θ1...
  • 图像进行旋转矫正,关键是要获取旋转角度是多少!获取了旋转角度就可以用仿射变换对图像进行矫正,图像旋转的代码可以参考我的...文本图像的明显特征就是存在分行间隔,那么行文字之间这个灰度值变化就不如真正...
  • 本专栏主要介绍如果通过OpenCv-Python进行图像处理,通过原理理解OpenCv-Python的函数处理原型,在具体情况中,针对不同的图像进行不同等级的、不同方法的处理,以达到对图像进行去噪、锐化等一系列的操作。...
  • 本文介绍了图像旋转的基本原理及MATLAB实现,在不借助MATLAB自带函数的情况下,自己书写了实现图像旋转步骤的几个函数,使用的插值方法为双线性插值。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 55,100
精华内容 22,040
关键字:

旋转图像的方法与原理