图像处理中什么是缩放_数字图像处理中图像缩放原理 - CSDN
  • 实现了一个Avalon-ST总线的简单图像缩放模块,将640*480的图像输入,变成320*240大小的图像输出。主要思想是,得到图像的行列计数值,根据要缩放的比例,在相应的行列值使能valid信号,比如我要将640*480图像缩小成...
    
    实现了一个Avalon-ST总线的简单图像缩放模块,将640*480的图像输入,变成320*240大小的图像输出。主要思想是,得到图像的行列计数值,根据要缩放的比例,在相应的行列值使能valid信号,比如我要将640*480图像缩小成320*240,那么只需控制将其隔行、隔列使能valid就行了。其实就是一个降采样。代码直接贴出
    1. </pre><pre name="code" class="cpp">module ST_RESIZE(  
    2.     // global clock & reset  
    3.         input           clk,  
    4.         input               reset_n,  
    5.   
    6.     // stream sink  
    7.         input   [23:0]      sink_data,  
    8.         input               sink_valid,  
    9.         output          sink_ready,  
    10.         input           sink_sop,  
    11.         input           sink_eop,  
    12.       
    13.     // stream source  
    14.         output [23:0]     source_data,  
    15.        output            source_valid,  
    16.        input             source_ready,  
    17.        output            source_sop,  
    18.        output            source_eop  
    19.   
    20. );  
    21.   
    22. localparam CTRL_PKT_NUM     = 3;  
    23.   
    24. assign sink_ready   =  source_ready;    
    25. assign source_sop   =  sink_sop;  
    26. assign source_eop   =  sink_eop;  
    27. //assign source_valid =  sink_valid;  
    28. //assign source_data  =  sink_data;  
    29.   
    30. parameter WIDTH = 640;  
    31. parameter HEIGHT = 480;  
    32. parameter w_LOG_WIDTH = 320;  
    33. parameter w_LOG_HEIGHT = 240;  
    34. reg state;  
    35. localparam STATE_CTRL = 0;  
    36. localparam STATE_DATA = 1;  
    37. always@(posedge clk)begin  
    38. if(~reset_n)  
    39.     state <= STATE_CTRL;  
    40. else if( process_en && sink_sop&& sink_data == 24'd15)  
    41.     state <= STATE_CTRL;  
    42. else if( process_en && sink_sop&& sink_data == 24'd0)  
    43.     state <= STATE_DATA;  
    44. end  
    45.   
    46. reg [10:0]  h_count;  
    47. reg [10:0]  v_count;  
    48. wire process_en;  
    49. assign process_en = source_ready & sink_valid;    
    50. always@(posedge clk)begin  
    51. if(~reset_n)  
    52.     h_count <= 11'b0;  
    53. else if( process_en && sink_sop && sink_data == 24'd0)  
    54.     h_count <= 11'b0;  
    55. else if( process_en && h_count< WIDTH-1 && state == STATE_DATA)  
    56.     h_count <= h_count + 1'b1;  
    57. else if( process_en &&  h_count== WIDTH-1 && state == STATE_DATA)  
    58.     h_count <= 11'b0;  
    59. end  
    60.   
    61. always@(posedge clk)begin  
    62. if(~reset_n)  
    63.     v_count <= 11'b0;  
    64. else if( process_en && sink_sop&& sink_data == 24'd0)  
    65.     v_count <= 11'b0;  
    66. else if( process_en && h_count == WIDTH-1 && state == STATE_DATA)  
    67.     v_count <= v_count + 1'b1;  
    68. end  
    69.   
    70.   
    71.   
    72. assign source_valid =(sink_valid & sink_sop & sink_data == 24'd15) |   
    73.                     (sink_valid & state == STATE_CTRL )|  
    74.                     (sink_valid & sink_sop & sink_data == 24'd00) |   
    75.                     ( sink_valid & state == STATE_DATA & h_count[0]==0 & v_count[0]==0) |  
    76.                     ( sink_valid & sink_eop )  
    77.                     ;  
    78. //assign source_data =  sink_data;  
    79.   
    80. always @(*) begin  
    81.   case (state )  
    82.     STATE_CTRL :   
    83.     if(source_ready & sink_valid)begin  
    84.         if(dot_cnt==0)source_data = { 4'b0,  w_LOG_WIDTH[ 7: 4], 4'b0,  w_LOG_WIDTH[11: 8], 4'b0,  w_LOG_WIDTH[15:12] };  
    85.         if(dot_cnt==1)source_data = { 4'b0,  w_LOG_HEIGHT[11: 8], 4'b0, w_LOG_HEIGHT[15:12], 4'b0,  w_LOG_WIDTH[ 3: 0] };  
    86.         if(dot_cnt==2)source_data = { 4'b0,            4'b0, 4'b0, w_LOG_HEIGHT[ 3: 0], 4'b0, w_LOG_HEIGHT[ 7: 4] };  
    87.     end  
    88.     STATE_DATA : source_data = sink_data;  
    89.     default : source_data = sink_data;  
    90.   endcase  
    91. end  
    92.   
    93. reg [11:0] dot_cnt;  
    94. always @(posedge clk) begin  
    95.   if (!reset_n) begin  
    96.      dot_cnt <= 0;  
    97.   end  
    98.   else begin  
    99.     if (dout_ready & source_ready)begin  
    100.         if ((pkt_state == STATE_CTRL) )begin // control packet  
    101.             if ( dot_cnt < (CTRL_PKT_NUM-1) )  
    102.                dot_cnt <= dot_cnt + 11'd1;  
    103.             else  
    104.                dot_cnt <= 0;  
    105.       end  
    106.   end  
    107. end  
    108. endmodule  
    </pre><pre name="code" class="cpp">module ST_RESIZE(
    	// global clock & reset
    		input          	clk,
    		input	            reset_n,
    
    	// stream sink
    		input	[23:0]		sink_data,
    		input	      		sink_valid,
    		output         	sink_ready,
    		input          	sink_sop,
    		input          	sink_eop,
    	
    	// stream source
    		output [23:0]     source_data,
    	   output            source_valid,
    	   input             source_ready,
    	   output            source_sop,
    	   output            source_eop
    
    );
    
    localparam CTRL_PKT_NUM     = 3;
    
    assign sink_ready   =  source_ready;  
    assign source_sop   =  sink_sop;
    assign source_eop   =  sink_eop;
    //assign source_valid =  sink_valid;
    //assign source_data  =  sink_data;
    
    parameter WIDTH = 640;
    parameter HEIGHT = 480;
    parameter w_LOG_WIDTH = 320;
    parameter w_LOG_HEIGHT = 240;
    reg state;
    localparam STATE_CTRL = 0;
    localparam STATE_DATA = 1;
    always@(posedge clk)begin
    if(~reset_n)
        state <= STATE_CTRL;
    else if( process_en && sink_sop&& sink_data == 24'd15)
        state <= STATE_CTRL;
    else if( process_en && sink_sop&& sink_data == 24'd0)
        state <= STATE_DATA;
    end
    
    reg [10:0]  h_count;
    reg [10:0]  v_count;
    wire process_en;
    assign process_en = source_ready & sink_valid;  
    always@(posedge clk)begin
    if(~reset_n)
        h_count <= 11'b0;
    else if( process_en && sink_sop && sink_data == 24'd0)
        h_count <= 11'b0;
    else if( process_en && h_count< WIDTH-1 && state == STATE_DATA)
        h_count <= h_count + 1'b1;
    else if( process_en &&  h_count== WIDTH-1 && state == STATE_DATA)
        h_count <= 11'b0;
    end
    
    always@(posedge clk)begin
    if(~reset_n)
        v_count <= 11'b0;
    else if( process_en && sink_sop&& sink_data == 24'd0)
        v_count <= 11'b0;
    else if( process_en && h_count == WIDTH-1 && state == STATE_DATA)
        v_count <= v_count + 1'b1;
    end
    
    
    
    assign source_valid =(sink_valid & sink_sop & sink_data == 24'd15) | 
    					(sink_valid & state == STATE_CTRL )|
    					(sink_valid & sink_sop & sink_data == 24'd00) | 
    					( sink_valid & state == STATE_DATA & h_count[0]==0 & v_count[0]==0) |
    					( sink_valid & sink_eop )
    					;
    //assign source_data =  sink_data;
    
    always @(*) begin
      case (state )
        STATE_CTRL : 
    	if(source_ready & sink_valid)begin
    		if(dot_cnt==0)source_data = { 4'b0,  w_LOG_WIDTH[ 7: 4], 4'b0,  w_LOG_WIDTH[11: 8], 4'b0,  w_LOG_WIDTH[15:12] };
    		if(dot_cnt==1)source_data = { 4'b0,  w_LOG_HEIGHT[11: 8], 4'b0, w_LOG_HEIGHT[15:12], 4'b0,  w_LOG_WIDTH[ 3: 0] };
    		if(dot_cnt==2)source_data = { 4'b0,            4'b0, 4'b0, w_LOG_HEIGHT[ 3: 0], 4'b0, w_LOG_HEIGHT[ 7: 4] };
    	end
        STATE_DATA : source_data = sink_data;
        default : source_data = sink_data;
      endcase
    end
    
    reg [11:0] dot_cnt;
    always @(posedge clk) begin
      if (!reset_n) begin
         dot_cnt <= 0;
      end
      else begin
        if (dout_ready & source_ready)begin
    		if ((pkt_state == STATE_CTRL) )begin // control packet
    			if ( dot_cnt < (CTRL_PKT_NUM-1) )
    			   dot_cnt <= dot_cnt + 11'd1;
    			else
    			   dot_cnt <= 0;
          end
      end
    end
    endmodule


    另外,假如对于一幅图像(1920*1080),我想把它缩小到任意的大小,比如320*240大小:

    对于水平的320像素,因为1920/320=6,因此只需设置一个列循环计数值,范围0-5,在等于5的时候,输出像素valid为1;

    对于竖直方向的240,因为1080除以240不是整数,而是9/2。也就是说,需要每隔4.5行,就应该输出一个valid。为了使其整数化,可以等同为每隔9行输出两个valid。因此可以设置一个行循环计数值,范围0-8,在等于3或者等于8的时候,输出valid为1。代码如下:

    1. module ST_RESIZE(  
    2.     // global clock & reset  
    3.         input           clk,  
    4.         input               reset_n,  
    5.   
    6.     // stream sink  
    7.         input   [15:0]      sink_data,  
    8.         input               sink_valid,  
    9.         output          sink_ready,  
    10.         input           sink_sop,  
    11.         input           sink_eop,  
    12.       
    13.     // stream source  
    14.         output [15:0]     source_data,  
    15.        output            source_valid,  
    16.        input             source_ready,  
    17.        output            source_sop,  
    18.        output            source_eop  
    19.   
    20. );  
    21.   
    22. assign sink_ready   =  source_ready;    
    23. assign source_sop   =  sink_sop;  
    24. assign source_eop   =  sink_eop;  
    25.   
    26. parameter WIDTH = 1920;  
    27. parameter HEIGHT = 1080;  
    28.   
    29. reg state;  
    30. localparam STATE_CTRL = 0;  
    31. localparam STATE_DATA = 1;  
    32. always@(posedge clk)begin  
    33. if(~reset_n)  
    34.     state <= STATE_CTRL;  
    35. else if( process_en && sink_sop&& sink_data == 16'd15)  
    36.     state <= STATE_CTRL;  
    37. else if( process_en && sink_sop&& sink_data == 16'd0)  
    38.     state <= STATE_DATA;  
    39. end  
    40.   
    41. reg [10:0]  h_count;  
    42. reg [10:0]  v_count;  
    43. wire process_en;  
    44. assign process_en = source_ready & sink_valid;    
    45.   
    46. reg [3:0] h_step_count;  
    47. reg [3:0] v_step_count;  
    48. always@(posedge clk)begin  
    49. if(~reset_n)begin  
    50.     h_count <= 11'b0;  
    51.     h_step_count <= 4'b0;  
    52.     end  
    53. else if( process_en && sink_sop && sink_data == 16'd0)begin  
    54.     h_count <= 11'b0;  
    55.     h_step_count <= 4'b0;  
    56.     end  
    57. else if( process_en && h_count< WIDTH-1 && state == STATE_DATA)begin  
    58.     h_count <= h_count + 1'b1;  
    59.     if(h_step_count >= 4'd5)  
    60.         h_step_count <= 4'b0;  
    61.     else  
    62.         h_step_count <= h_step_count+1;  
    63.     end  
    64. else if( process_en &&  h_count== WIDTH-1 && state == STATE_DATA)begin  
    65.     h_count <= 11'b0;  
    66.     h_step_count <= 4'b0;  
    67.     end  
    68. end  
    69.   
    70. always@(posedge clk)begin  
    71. if(~reset_n)begin  
    72.     v_count <= 11'b0;  
    73.     v_step_count <= 4'b0;  
    74.     end  
    75. else if( process_en && sink_sop&& sink_data == 16'd0)begin  
    76.     v_count <= 11'b0;  
    77.     v_step_count <= 4'b0;      
    78.     end  
    79. else if( process_en && h_count == WIDTH-1 && state == STATE_DATA)begin  
    80.     v_count <= v_count + 1'b1;  
    81.     if(v_step_count >= 4'd8)  
    82.         v_step_count <= 4'b0;  
    83.     else  
    84.         v_step_count <= v_step_count+1;  
    85.     end  
    86. end  
    87.   
    88.   
    89. assign source_valid =(sink_valid & sink_sop & sink_data == 16'd15) | //  
    90.                     (sink_valid & state == STATE_CTRL )|  
    91.                     (sink_valid & sink_sop & sink_data == 16'd00) |   
    92.                     ( sink_valid & state == STATE_DATA & h_step_count==4'd5 &( v_step_count==4'd3 | v_step_count==4'd8)) |  
    93.                     ( sink_valid & sink_eop )  
    94.                     ;                     
    95.                       
    96.                       
    97. assign source_data =  sink_data;  
    98. endmodule 
    展开全文
  • 在日常工作,我们经常需要对图像进行缩放(放大、缩小),旋转、平移等各种操作,这类操作统称为图像的几何变换。相对于前面提到的灰度变换,几何变换是改变了原图像像素点在新图像中的空间位置。 我们首先来看看...

    在日常工作中,我们经常需要对图像进行缩放(放大、缩小),旋转平移等各种操作,这类操作统称为图像的几何变换。相对于前面提到的灰度变换,几何变换是改变了原图像像素点在新图像中的空间位置。

    我们首先来看看图像缩放操作。假设一幅图像是100×100像素大小,放大一倍后是200×200大小。图像中的每一个像素点位置可以看作是一个点,也可以看作是二维平面上的一个矢量。图像缩放,本质上就是将每个像素点的矢量进行缩放,也就是将矢量x方向和y方向的坐标值缩放。也就是[x,y][x, y]变成了[kxx,kyy][k_x \cdot x,k_y\cdot y],一般情况下kx=kyk_x=k_y,但是很多时候也不相同,例如将100×100的图像变成400×300的图像。学过线性代数的同学很快就能知道,这可以表示成矩阵乘法的形式:


    [uv]=[kx00ky][xy] \begin{aligned} {\left[ \begin{array}{ccc} u\\ v\\ \end{array} \right ]}={ \left[ \begin{array}{ccc} k_x &amp; 0 \\ 0 &amp; k_y\\ \end{array} \right ]}{ \left[ \begin{array}{ccc} x\\ y\\ \end{array} \right ]} \end{aligned}

    通过上述矩阵乘法的形式,我们就把原图像上的每一个像素点映射到新图像上相应的像素点了,这称为前向映射

    但是,我们很快注意到,原始图像有100×100=10000个像素点,而变换后的图像是200×200=40000个像素点。纵使把原始图像的10000个像素点全部映射到新图像上的对应点上,新图像上仍然有40000-10000=30000个点没有与原图像进行对应,那这30000个像素点的灰度值从何而来呢?

    我们把上面的矩阵表达式稍微转换下,两边乘以放大矩阵的逆矩阵:

    [kx00ky]1[uv]=[xy] \begin{aligned} { \left[ \begin{array}{ccc} k_x &amp; 0 \\ 0 &amp; k_y\\ \end{array} \right ] }^{-1}{\left[ \begin{array}{ccc} u\\ v\\ \end{array} \right ]}={ \left[ \begin{array}{ccc} x \\ y\\ \end{array} \right ]} \end{aligned}

    通过上面的式子,我们可以将新图像中的每一个像素点[u,v][u, v]与原图像中的一个像素点[x,y][x, y]对应起来了。这称为后向映射。显然后向映射比前向映射更有效。

    import cv2
    import numpy as np
    
    lenna100 = cv2.imread("lenna100.png", 0)
    row, col = lenna100.shape
    kx, ky = 2, 2
    A = np.mat([[kx, 0], [0, ky]])
    lenna200 = np.zeros((kx * row, ky * col))
    
    for r in range(kx * row):
        for l in range(ky * col):
            v = np.dot(A.I, np.array([r, l]).T)
            lenna200[r, l] = lenna100[int(v[0, 0]), int(v[0, 1])]
    
    cv2.imshow("lenna100", lenna100)
    cv2.imshow("lenna200", lenna200.astype("uint8"))
    cv2.waitKey()
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里写图片描述

    转载自:https://blog.csdn.net/saltriver/article/details/79680067

    展开全文
  • 目录 1 图像缩放-resize() 2 图像旋转-getRotationMatrix2D(), ...图像几何变换有图像缩放图像旋转、图像翻转和图像平移等。 1 图像缩放-resize() 图像缩放主要调用 resize() 函数实现,具体如下: resul...

    目录

    1 图像缩放- resize()

    2 图像旋转- getRotationMatrix2D(), warpAffine()

    3 图像翻转- flip()

    4 图像平移- warpAffine()

    参考资料


    图像几何变换有图像缩放、图像旋转、图像翻转和图像平移等。

     

    1 图像缩放- resize()

    图像缩放主要调用 resize() 函数实现,具体如下:

    result = cv2.resize(src, dsize[, result[. fx[, fy[, interpolation]]]])

    其中,参数

    src 表示原始图像;

    dsize 表示缩放大小;

    fx和fy 也可以表示缩放大小倍数,他们两个(dsize或fx/fy)设置一个即可实现图像缩放。例如:

    (1)result = cv2.resize(src, (160,160))

    (2)result = cv2.resize(src, None, fx=0.5, fy=0.5)

    图像缩放:设({x_0},{y_0})是缩放后的坐标,(x,y)是缩放前的坐标,{s_x} 和 {s_y} 为缩放因子,则公式如下:

                                                                           [{x_0}\text{ }{\text{ }}{y_0}\text{ }{\text{ }}1] = [x\text{ }{\text{ }}y\text{ }{\text{ }}1]{\text{ }}\left[ \begin{gathered} {s_x}\text{ }{\text{ }}0\text{ }\text{ }{\text{ 0}} \hfill \\ 0{\text{ }}\text{ }\text{ }{s_y}\text{ }{\text{ 0}} \hfill \\ {\text{0 }}\text{ }\text{ }0\text{ }\text{ }{\text{ 1}} \hfill \\ \end{gathered} \right]

     

    (1) cv2.resize(src, (200,100)) 设置的dsize是列数为200,行数为100

    result = cv2.resize(src, (200,100))

    代码如下:

    # encoding:utf-8
    import cv2
    import numpy as np
    
    # 读取图片
    src = cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
    
    # 图像缩放
    result = cv2.resize(src, (200,100))
    print (result.shape)
    
    # 显示图像
    cv2.imshow("src", src)
    cv2.imshow("result", result)
    
    # 等待显示
    cv2.waitKey(0)
    cv2.destroyAllWindows()

     

    运行结果如下图所示:

     

    (2)可以获取 原始图像像素\times乘以缩放系数 进行图像变换;

    result = cv2.resize(src, (int(cols * 0.6), int(rows * 1.2)))

     

    代码如下所示:

    # encoding:utf-8
    import cv2
    import numpy as np
    
    # 读取图片
    src = cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
    rows, cols = src.shape[:2]
    print
    rows, cols
    
    # 图像缩放 dsize(列,行)
    result = cv2.resize(src, (int(cols * 0.6), int(rows * 1.2)))
    
    # 显示图像
    cv2.imshow("src", src)
    cv2.imshow("result", result)
    
    # 等待显示
    cv2.waitKey(0)
    cv2.destroyAllWindows()

     

    运行结果如下图所示:

     

    (3)(fx,fy) 缩放倍数的方法对图像进行放大或缩小。

    result = cv2.resize(src, None, fx=0.3, fy=0.3)

     

    代码如下所示:

    # encoding:utf-8
    import cv2
    import numpy as np
    
    # 读取图片
    src =cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
    rows, cols = src.shape[:2]
    print
    rows, cols
    
    # 图像缩放
    result = cv2.resize(src, None, fx=0.3, fy=0.3)
    
    # 显示图像
    cv2.imshow("src", src)
    cv2.imshow("result", result)
    
    # 等待显示
    cv2.waitKey(0)
    cv2.destroyAllWindows()

     

    运行结果如下图所示:(按例比 0.3 \times 0.3 缩小)


     

    2 图像旋转- getRotationMatrix2D(), warpAffine()

    图像旋转:设 ({x_0},{y_0}) 是旋转后的坐标,({x},{y}) 是旋转前的坐标,({ m},{n}) 是旋转中心,a 是旋转的角度,({ left},{top}) 是旋转后图像的左上角坐标,则公式如下:

                                         [{x_0}\text{ }{\text{ }}{y_0}\text{ }{\text{ }}1] = [x\text{ }{\text{ }}y\text{ }{\text{ }}1]{\text{ }}\left[ \begin{gathered} \text{ }\text{ } 1\text{ }\text{ }\text{ }{\text{ }}0\text{ }\text{ }{\text{ 0}} \hfill \\ \text{ }\text{ }0{\text{ }} - 1\text{ }{\text{ 0}} \hfill \\ {\text{ - }}m\text{ }{\text{ }}n\text{ }{\text{ 1}} \hfill \\ \end{gathered} \right]{\text{ }}\left[ \begin{gathered} \cos a{\text{ }} - \sin a{\text{ }}0 \hfill \\ \sin a\text{ }\text{ }{\text{ }}\cos a\text{ }{\text{ }}0 \hfill \\ \text{ }\text{ }{\text{ }}0{\text{ }}\text{ }\text{ }\text{ }\text{ }\text{ }\text{ }0\text{ }\text{ }\text{ }{\text{ }}1 \hfill \\ \end{gathered} \right]{\text{ }}\left[ \begin{gathered} \text{ }\text{ }1\text{ }\text{ }\text{ }\text{ }{\text{ }}0\text{ }\text{ }{\text{ }}0 \hfill \\ \text{ }\text{ }0{\text{ }} \text{ }- 1\text{ }{\text{ }}0 \hfill \\ left\text{ }{\text{ }}top{\text{ }}\text{ }1 \hfill \\ \end{gathered} \right]

     

    图像旋转主要调用getRotationMatrix2D() 函数和 warpAffine() 函数实现,绕图像的中心旋转,具体如下:

    M = cv2.getRotationMatrix2D((cols/2, rows/2), 30, 1)

    其中,参数分别为:旋转中心、旋转度数、scale

    rotated = cv2.warpAffine(src, M, (cols, rows))

    其中,参数分别为:原始图像、旋转参数 和 原始图像宽高

     

    (1)旋转30度

    代码如下:

    # encoding:utf-8
    import cv2
    import numpy as np
    
    # 读取图片
    src =  cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
    
    # 原图的高、宽 以及通道数
    rows, cols, channel = src.shape
    
    # 绕图像的中心旋转
    # 参数:旋转中心 旋转度数 scale
    M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 30, 1)
    # 参数:原始图像 旋转参数 元素图像宽高
    rotated = cv2.warpAffine(src, M, (cols, rows))
    
    # 显示图像
    cv2.imshow("src", src)
    cv2.imshow("rotated", rotated)

     

    运行结果如下图所示:

     

    (2)旋转90度

    代码如下:

    # encoding:utf-8
    import cv2
    import numpy as np
    
    # 读取图片
    src =  cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
    
    # 原图的高、宽 以及通道数
    rows, cols, channel = src.shape
    
    # 绕图像的中心旋转
    # 参数:旋转中心 旋转度数 scale
    M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 90, 1)
    # 参数:原始图像 旋转参数 元素图像宽高
    rotated = cv2.warpAffine(src, M, (cols, rows))
    
    # 显示图像
    cv2.imshow("src", src)
    cv2.imshow("rotated", rotated)
    

     

    运行结果如下图所示:

     

    (3)旋转180度

    代码如下:

    # encoding:utf-8
    import cv2
    import numpy as np
    
    # 读取图片
    src =  cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
    
    # 原图的高、宽 以及通道数
    rows, cols, channel = src.shape
    
    # 绕图像的中心旋转
    # 参数:旋转中心 旋转度数 scale
    M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 180, 1)
    # 参数:原始图像 旋转参数 元素图像宽高
    rotated = cv2.warpAffine(src, M, (cols, rows))
    
    # 显示图像
    cv2.imshow("src", src)
    cv2.imshow("rotated", rotated)
    

     

    运行结果如下图所示:


     

    3 图像翻转- flip()

    图像翻转在OpenCV中调用函数 flip() 实现,函数用法如下:

    dst = cv2.flip(src, flipCode)

    其中,参数:

    src 表示原始图像;

    flipCode 表示翻转方向,如果flipCode为0,则以X轴为对称轴翻转,如果fliipCode>0则以Y轴为对称轴翻转,如果flipCode<0则在X轴、Y轴方向同时翻转。

     

    代码如下:(注意一个窗口多张图像的用法

    # encoding:utf-8
    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    
    # 读取图片
    img = cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
    src = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
    # 图像翻转
    # 0以X轴为对称轴翻转 >0以Y轴为对称轴翻转 <0X轴Y轴翻转
    img1 = cv2.flip(src, 0)
    img2 = cv2.flip(src, 1)
    img3 = cv2.flip(src, -1)
    
    # 显示图形 (注意一个窗口多张图像的用法)
    titles = ['Source', 'Image1', 'Image2', 'Image3']
    images = [src, img1, img2, img3]
    for i in range(4):
        plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
        plt.title(titles[i])
        plt.xticks([]), plt.yticks([])
    plt.show()
    
    # 等待显示
    cv2.waitKey(0)
    cv2.destroyAllWindows()

     

    运行结果如下图所示:(注意一个窗口多张图像的用法


     

    4 图像平移- warpAffine()

    图像平移:设 ({x_0},{y_0}) 是缩放后的坐标,({x},{y}) 是缩放前的坐标,{d_x},{d_y} 为偏移量,则公式如下:

                                                                 [{x_0}\text{ }{\text{ }}{y_0}\text{ }{\text{ }}1] = [x\text{ }{\text{ }}\text{ }y{\text{ }}\text{ }1]{\text{ }}\left[ \begin{gathered} \text{ }1{\text{ }}\text{ }\text{ }0\text{ }\text{ }{\text{ }}0 \hfill \\ \text{ }0\text{ }\text{ }{\text{ }}1\text{ }\text{ }{\text{ }}0 \hfill \\ {d_x}{\text{ }}\text{ }{d_y}{\text{ }}\text{ }1 \hfill \\ \end{gathered} \right]

     

    图像平移首先定义平移矩阵M,再调用 warpAffine() 函数实现平移,函数用法如下:

    M = np.float32([[1, 0, x], [0, 1, y]])

    shifted = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

     

    代码如下:

    # encoding:utf-8
    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    
    # 读取图片
    img = cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
    image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
    # 图像平移 下、上、右、左平移
    M = np.float32([[1, 0, 0], [0, 1, 100]])
    img1 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
    
    M = np.float32([[1, 0, 0], [0, 1, -100]])
    img2 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
    
    M = np.float32([[1, 0, 100], [0, 1, 0]])
    img3 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
    
    M = np.float32([[1, 0, -100], [0, 1, 0]])
    img4 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
    
    # 显示图形
    titles = ['Image1', 'Image2', 'Image3', 'Image4']
    images = [img1, img2, img3, img4]
    for i in range(4):
        plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
        plt.title(titles[i])
        plt.xticks([]), plt.yticks([])
    plt.show()
    

     

    运行结果如下图所示:


     

    参考资料

    [1] https://blog.csdn.net/Eastmount/article/details/82454335

    [2] Python+OpenCV图像处理

    展开全文
  • 图像基本变换是图像处理的基本内容,是学习以后复杂的仿射变换、透视变换以及更高级的MLS网格变形等内容的基础,意义重大。本篇将从平移、缩放和旋转三个方面来讲解如何单纯使用C语言来轻松实现这三个算法。

            本篇作为新年到来前的最后一篇,提前祝大家新年快乐!

            图像几何变换又叫做图像基本变换,主要包括图像平移、图像缩放和图像旋转几个部分,当然还有图像镜像等简单的内容。图像基本变换是图像处理的基本内容,是学习以后复杂的仿射变换、透视变换以及更高级的MLS网格变形等内容的基础,意义重大。本篇将从平移、缩放和旋转三个方面来讲解如何单纯使用C语言来轻松实现这三个算法。

    图像平移变换

    [定义与算法]

            图像平移变换可以表示为水平方向和垂直方向的位移,如果把图像坐标系的原点(0,0)点平移到(x0,y0),则图像内任意一点(x,y)平移后坐标(x’,y’)用公式表示如下:

     

            我们对测试图进行水平和垂直正方向平移100像素,效果图如图Fig.1所示。

            注意,黑色区域是我们默认填充的颜色,平移变换会出现图像跑到原图画布外面的情况,此时,原来的区域可以填充任意颜色,图像平移变换就这么简单。

    [绘制与代码]

            我们用C语言来实现图像平移变换算法,定义f_Transform.h文件,在文件中定义如下接口:

    /*********************************************************
    *Function:图像平移变换
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          xoffset:水平方向平移量
    *          yoffset:垂直方向平移量
    *Return:  0-成功,其他失败
    *********************************************************/
    int f_XYOfffset(unsigned char* srcData, int width, int height, int stride, int xoffset, int yofffset);

            在这个接口中,定义了两个参数xoffset和yoffset分别用来表示水平和垂直的偏移向量,注意,如果xoffset和yoffset都为正数,则表示的是向水平和垂直的正方向偏移了xoffset和yoffset个像素距离,表现在结果图中,即图像向左偏移,反之,图像向右边偏移。

            完整接口代码如下:

    /*********************************************************
    *Function:图像平移变换
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          xoffset:水平方向平移向量
    *          yoffset:垂直方向平移向量
    *Return:  0-成功,其他失败
    *********************************************************/
    int f_XYOfffset(unsigned char* srcData, int width, int height, int stride, int xoffset, int yoffset)
    {
    	int ret = 0;
    	unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * height * stride);
    	memcpy(tempData, srcData, sizeof(unsigned char) * height * stride);
    	unsigned char* pSrc = srcData;
    	for(int j = 0; j < height; j++)
    	{
    		for(int i = 0; i < width; i++)
    		{
    			int cx = i + xoffset;
    			int cy = j + yoffset;
    			if(cx >= 0 && cx < width && cy >= 0 && cy < height)
    			{
    				int pos = cx * 4 + cy * stride;
    			    pSrc[0] = tempData[pos];
    				pSrc[1] = tempData[pos + 1];
    				pSrc[2] = tempData[pos + 2];
    			}
    			else
    			{
    				pSrc[0] = 0;
    				pSrc[1] = 0;
    				pSrc[2] = 0;
    			}
    			pSrc += 4;			
    		}
    	}
    	free(tempData);
    	return ret;
    };

            接口到这里就写完了,仅仅30行左右,下面我们来写个测试代码:

    #include "stdafx.h"
    #include"imgRW\f_SF_ImgBase_RW.h"
    #include"f_Transform.h"
    int _tmain(int argc, _TCHAR* argv[])
    {
    	//定义输入图像路径
    	char* inputImgPath = "Test.png";
    	//定义输出图像路径
    	char* outputImgPath = "res_offset.jpg";
    	//定义图像宽高信息
    	int width = 0, height = 0, component = 0, stride = 0;
    	//图像读取(得到32位bgra格式图像数据)
    	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
    	stride = width * 4;
    	int ret = 0;
    	//其他图像处理操作(这里以32位彩色图像灰度化为例)
    	//////////////////////////IMAGE PROCESS/////////////////////////////
    	//图像平移
    	int xoffset = -100;//图像向右平移
    	int yoffset = -100;//图像向左平移
         //调用图像平移变换接口
    	ret = f_XYOfffset(bgraData, width, height, stride,xoffset, yoffset);
         //保存平移变换结果图
    	ret = Trent_ImgBase_ImageSave(outputImgPath, width, height, bgraData, JPG);
    	printf("f_XYOffset is finished!");
    ////////////////////////////////////////////////////////////////////
    	free(bgraData);
    }
    	return 0;

            可以看到,在这段代码中,我们使用的都是C语言标准库,对于初学者而言,非常方便,通俗易懂。

    图像缩放变换

    [定义与算法]

            图像缩放即图像缩小与放大,是图像处理中最常用的操作,可以说图像处理离不开图像缩放,好的图像缩放算法可以高清还原图像信息,对于各种复杂的图像应用而言意义重大。

            假设图像中任意一点(x,y),按照水平方向缩放比例a和垂直方向缩放比例b进行缩放,则缩放后点坐标(x’,y’)的计算如下:

                                                                                                x{}'=ax

                                                                                                y{}'=by

            当a和b小于1时,表现为缩小,大于1时表现为图像放大,等于1时不缩放;既然有了放大与缩小,就存在图像信息的删除与填充,如何进行精确计算缩放后的坐标位置?这里引入一个必需要讲解的内容---图像插值算法。

            图像插值算法有很多,从最邻近插值,到二次插值、三次插值、卷积插值等等以及超分辨率算法等高级插值,可以说,图像插值是一门学问,单独成书的也很多很多。这里,我们讲解两种最常用也是最基础的插值算法:最邻近插值和双线性二次插值。

            最邻近插值不明思义就是用距离它最近的点来代替它。如下图Fig.2所示,有A和B两个像素点,A的值为100,B的值为20,要计算AB之间的C点像素值,其中,C点距离A的距离为0.4,距离B的距离为0.6,那么,C点距离A点像素最近,则C=100,这就是最邻近插值。

            最邻近插值计算量最小,但效果较差,往往会出现锯齿问题,即缩放后的图像边缘会出现锯齿毛刺,如图Fig.3所示,左边为原图,右边为最邻近插值放大两倍的结果,可以看到字母的边缘出现了锯齿状,非常不平滑。

     

            双线性二次插值也称为一阶插值,这个算法说复杂也复杂,说简单也简单,如果你要从数学上讲明白,那要从拉格朗日插值多项式说起。

            拉格朗日插值:

            对于给定的n+1个节点,x0,x1,..xn,如果能够找到n+1个多项式l1(x),l2(x),...ln(x),满足如下条件:,

            那么,拉格朗日插值多项式P(x)表示如下:

            其中,l(x)被称作插值基函数。

            上述内容可以参考张铁所著《数值分析》136页部分,上述内容与二次插值有什么关系呢?这里我们一道例题来说明,该例题也来自张铁《数值分析》。

            对于x0和x1,它的一阶拉格朗日插值多项式L(x)求取如上所示,其中l0(x)和l1(x)分别表示对应权重,如果要计算x0到x1之间的任意一点x,那么计算L1(x)即可,看到这里我们明白,如果给定两个点,计算两点之间的任意一点插值,那么,我们只要可以使用上述1阶拉格朗日插值多项式即可,而这个方法就是二次插值的基础。

            对于双线性二次插值,我们假设要计算的插值点(x,y)的值为f(x,y),在它的附近有有f(i,j),f(i+1,j),f(i,j+1)和f(i+1,j+1)四个点,如下图Fig.4所示,f(x,y)的计算方法如下:

            ①我们使用一阶拉格朗日插值计算点(i,j)和(i+1,j)之间的点(x,j)处的插值f(x,j);

            ②我们使用一阶拉格朗日插值计算点(i,j+1)和(i+1,j+1)之间的点(x,j+1)处的插值f(x,j+1);

            ③我们使用一阶拉格朗日插值计算(x,j)和(x,j+1)之间点(x,y)处的插值f(x,y);

            这个公式就是双线性插值公式。我们用这个公式,对比最邻近插值效果,如下图Fig.5所示。

    [绘制与代码]

            有了上述算法的解析,下面我们通过C语言来实现,首先定义接口如下:

    /*********************************************************
    *Function:图像缩放变换
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          scaleX:水平方向缩放比例,[0,]
    *          scaleY:垂直方向缩放比例,[0,]
    *          outW:缩放结果图宽度
    *          outH:缩放结果图高度
    *          outStride:缩放结果图Stride
    *          interpolation:插值方式,0-最邻近插值,1-双线性插值
    *Return:  缩放图像bgra数据指针
    *********************************************************/
    unsigned char* f_Zoom(unsigned char* srcData, int width, int height, int stride, float scaleX, float scaleY, int* outW, int* outH, int * outStride, int interpolation);

            我们定义了f_Zoom的接口,这个接口中,由于缩放会改变图像大小,因此,我们返回一个缩放后的图像数据指针,同时,返回缩放后的图像宽高信息outW、outH和outStride;由于缩放包含水平和垂直方向的缩放因子,所以,添加水平缩放参数scaleX和垂直缩放参数scaleY,当scaleX小于1时表示水平缩小,等于1表示水平不缩放,大于1表示水平放大,垂直方向亦如此;最后,由于我们可以使用最邻近插值和双线性插值两种方式进行缩放,因此,添加了interpolation插值参数;

            有了接口,我们给出完整的接口实现代码如下:

    /*********************************************************
    *Function:图像缩放变换
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          scaleX:水平方向缩放比例,[0,]
    *          scaleY:垂直方向缩放比例,[0,]
    *          outW:缩放结果图宽度
    *          outH:缩放结果图高度
    *          outStride:缩放结果图Stride
    *          interpolation:插值方式,0-最邻近插值,1-双线性插值
    *Return:  缩放图像bgra数据指针
    *********************************************************/
    unsigned char* f_Zoom(unsigned char* srcData, int width, int height, int stride, float scaleX, float scaleY, int* outW, int* outH, int * outStride, int interpolation)
    {
    	int w = width * scaleX;
    	int h = height * scaleY;
    	int s = w * 4;
    	unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * s * h);
    	memset(tempData, 255, sizeof(unsigned char) * s * h);
    	unsigned char* pTemp = tempData;
    	//最邻近插值
    	if(interpolation == 0)
    	{
    	    for(int j = 0; j < h; j++)
    	    {
    	    	for(int i = 0; i < w; i++)
    	    	{
    	    		int cx = CLIP3(i * width / w, 0, width - 1);
    	    		int cy = CLIP3(j * height / h, 0, height - 1);
    	    		int pos = cx * 4 + cy * stride;
    	    		pTemp[0] = srcData[pos];
    	    		pTemp[1] = srcData[pos + 1];
    	    		pTemp[2] = srcData[pos + 2];
    	    		pTemp[3] = srcData[pos + 3];
    	    		pTemp += 4;			
    	    	}
    	    }
    	}
    	else//双线性插值
    	{
    		for(int j = 0; j < h; j++)
    	    {
    	    	for(int i = 0; i < w; i++)
    	    	{
    	    		float cx = CLIP3((float)i * width / w, 1, width - 2);
    	    		float cy = CLIP3((float)j * height / h, 1, height - 2);
    				int tx = (int)cx;
    				int ty = (int)cy;
    				float p = abs(cx - tx);
    				float q = abs(cy - ty);
    	    		int pos = tx * 4 + ty * stride;
    				int p1 = pos;
    				int p2 = pos + stride;
    				int p3 = pos + 4;
    				int p4 = pos + 4 + stride;
    				float a = (1.0f - p) * (1.0f - q);
    				float b = (1.0f - p) * q;
    				float c = p * (1.0f - q);
    				float d = p * q;
    	    		pTemp[0] = CLIP3((a * srcData[p1 + 0] + b * srcData[p2 + 0] + c * srcData[p3 + 0] + d * srcData[p4 + 0]), 0, 255);
    	    		pTemp[1] = CLIP3((a * srcData[p1 + 1] + b * srcData[p2 + 1] + c * srcData[p3 + 1] + d * srcData[p4 + 1]), 0, 255);
    	    		pTemp[2] = CLIP3((a * srcData[p1 + 2] + b * srcData[p2 + 2] + c * srcData[p3 + 2] + d * srcData[p4 + 2]), 0, 255);
    	    		pTemp[3] = CLIP3((a * srcData[p1 + 3] + b * srcData[p2 + 3] + c * srcData[p3 + 3] + d * srcData[p4 + 3]), 0, 255);
    	    		pTemp += 4;			
    	    	}
    	    }
    	}
    	*outW = w;
    	*outH = h;
    	*outStride = s;
    	return tempData;
    };

            代码不足60行,通俗易懂,最后给出接口的调用代码:

    #include "stdafx.h"
    #include"imgRW\f_SF_ImgBase_RW.h"
    #include"f_Transform.h"
    int _tmain(int argc, _TCHAR* argv[])
    {
    	//定义输入图像路径
    	char* inputImgPath = "t150.png";
    	//定义图像宽高信息
    	int width = 0, height = 0, component = 0, stride = 0;
    	//图像读取(得到32位bgra格式图像数据)
    	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
    	stride = width * 4;
    	int ret = 0;
    	//其他图像处理操作(这里以32位彩色图像为例)
    	//////////////////////////IMAGE PROCESS/////////////////////////////
    	//图像缩放
    	int outW, outH, outS;
    	float scaleX = 5;
    	float scaleY = 5;
    	int interpolation = INTERPOLATE_BILINEAR;//INTERPOLATE_NEAREST;
    	char* outZoomImgPath = "res_zoom.png";
    	unsigned char* pResZoom = f_Zoom(bgraData, width, height, stride, scaleX, scaleY, &outW, &outH, &outS, interpolation);
    	ret = Trent_ImgBase_ImageSave(outZoomImgPath, outW, outH, pResZoom, PNG);
    	free(pResZoom);
    	printf("f_zoom is finished!");
    	////////////////////////////////////////////////////////////////////
    	free(bgraData);
    	return 0;
    }

            效果测试如下图Fig.6所示:

    图像旋转变换

    [定义与算法]

            图像旋转即将图像按照某个原点顺时针或者逆时针旋转某个角度。

            如果平面上所有点(x,y)绕原点O旋转\Theta角度,旋转后的点为(x’,y’),则两者正向和逆向计算公式如下:

            公式的推导我们以正向变换为例,在极坐标系中,假设(x,y)到原点O距离为r,注意,这里O点表示上文平移后的图像中心点,(x,y)和原点的连线与x轴方向的夹角为b,旋转角度为a,旋转后坐标为(x’,y’),如下图Fig.7所示,则按照极坐标公式有:

            上述便是正向推导过程,即由(x,y)到旋转后的点(x’,y’)。

            在实际中,图像平面中的原点一般为左上角,也就是左上角为(0,0)点,垂直方向向下为正方向,与正常的笛卡尔坐标系不同。我们想要的往往是图像围绕图像中心点进行角度旋转,这个时候,我们需要把点(x,y)先转换为以图像中心为原点的坐标,也就是进行一定的坐标平移,然后再进行旋转变换。同时,为例避免孔洞现象,我们一般在计算的过程中,是按照逆向变换,根据目标图像素位置(x’,y’)计算它在原图中的位置(x,y),一次完成旋转变换的。

            假设旋转后图像的宽为W’,高为H’,点(x’,y’)映射到原图中的坐标为(x,y),则计算过程如下:

            ①按照平移逆变换将(x’,y’)进行平移:

            ②按照旋转逆变换公式将(x,y)进行变换:

            ③将坐标原点由图像中心平移至左上角:

            ④根据(x,y)位置选择插值算法插值得到最终旋转后的像素值;

            上面的过程就是完整的图像旋转变换,下面我们将动手实践一下。

    [绘制与代码]

            我们首先定义一个图像旋转的接口,如下:

    /*********************************************************
    *Function:图像旋转变换
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          angle:图像旋转角度
    *          outW:旋转结果图宽度
    *          outH:旋转结果图高度
    *          outStride:旋转结果图Stride
    *          interpolation:插值方式,0-最邻近插值,1-双线性插值
    *Return:  旋转图像bgra数据指针
    *********************************************************/
    unsigned char* f_Rotate(unsigned char* srcData, int width, int height, int stride, int angle, int* outW, int* outH, int* outStride, int interpolation);

            图像旋转变换中,图像的大小发生了变化,因此这里我们的接口返回一个变换后的图像数据指针,与缩放接口类似,添加新图像宽高输出参数outW,outH和outStride;由于旋转变换需要角度信息,因此这里添加了角度输入参数angle,范围为0到360度,同时,设置插值算法参数interpolation;

            按照前文的旋转变换算法公式,我们给出C代码如下:

    /*********************************************************
    *Function:图像旋转变换
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          angle:图像旋转角度
    *          outW:旋转结果图宽度
    *          outH:旋转结果图高度
    *          outStride:旋转结果图Stride
    *          interpolation:插值方式,0-最邻近插值,1-双线性插值
    *Return:  旋转图像bgra数据指针
    *********************************************************/
    unsigned char* f_Rotate(unsigned char* srcData, int width, int height, int stride, int angle, int* outW, int* outH, int* outStride, int interpolation)
    {
    	float degree = angle * PI / 180.0f;
    	float cx = 0, cy = 0, Cos = 0, Sin = 0;
    	Cos = cos(degree);
    	Sin = sin(degree);
    	//计算新图像的宽高
        int w = width * Cos + height * Sin;
    	int h = height * Cos + width * Sin;
    	int s = w * 4;
    	*outW = w;
    	*outH = h;
    	*outStride = s;
    	//常量计算,用来优化速度
    	cx = -w / 2.0f * Cos - h / 2.0f * Sin + width / 2.0f;
    	cy = w / 2.0f * Sin - h / 2.0f * Cos + height / 2.0f;
    	unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * s * h);
    	memset(tempData, 255, sizeof(unsigned char) * s * h);
    	unsigned char* pTemp = tempData;
    	//最邻近插值
    	if(interpolation == 0)
    	{
    	    for(int j = 0; j < h; j++)
    	    {
    	    	for(int i = 0; i < w; i++)
    	    	{
    				//这里实际上就是按照公式计算,进行了优化,把一些常量计算放到了外面的cx和cy中
    	    		int tx = i * Cos + j * Sin + cx;
    	    		int ty = j * Cos - i * Sin + cy;
    				if(tx >= 0 && tx < width && ty >= 0 && ty < height)
    				{
    	    		    int pos = tx * 4 + ty * stride;
    	    		    pTemp[0] = srcData[pos];
    	    		    pTemp[1] = srcData[pos + 1];
    	    		    pTemp[2] = srcData[pos + 2];
    	    		    pTemp[3] = srcData[pos + 3];
    				}
    				else
    				{
    					pTemp[0] = 0;
    	    		    pTemp[1] = 0;
    	    		    pTemp[2] = 0;
    	    		    pTemp[3] = 255;
    				}
    	    		pTemp += 4;			
    	    	}
    	    }
    	}
    	else//双线性插值
    	{
    		for(int j = 0; j < h; j++)
    	    {
    	    	for(int i = 0; i < w; i++)
    	    	{
    				//这里实际上就是按照公式计算,进行了优化,把一些常量计算放到了外面的cx和cy中
                    float mx = i * Cos + j * Sin + cx;
    	    		float my = j * Cos - i * Sin + cy;
    				if(mx >= 0 && mx < width && my >= 0 && my < height)
    				{
    				    int tx = (int)mx;
    				    int ty = (int)my;
    				    float p = abs(mx - tx);
    				    float q = abs(my - ty);
    	    		    int pos = tx * 4 + ty * stride;
    				    int p1 = pos;
    				    int p2 = pos + stride;
    				    int p3 = pos + 4;
    				    int p4 = pos + 4 + stride;
    				    float a = (1.0f - p) * (1.0f - q);
    				    float b = (1.0f - p) * q;
    				    float c = p * (1.0f - q);
    				    float d = p * q;
    	    		    pTemp[0] = CLIP3((a * srcData[p1 + 0] + b * srcData[p2 + 0] + c * srcData[p3 + 0] + d * srcData[p4 + 0]), 0, 255);
    	    		    pTemp[1] = CLIP3((a * srcData[p1 + 1] + b * srcData[p2 + 1] + c * srcData[p3 + 1] + d * srcData[p4 + 1]), 0, 255);
    	    		    pTemp[2] = CLIP3((a * srcData[p1 + 2] + b * srcData[p2 + 2] + c * srcData[p3 + 2] + d * srcData[p4 + 2]), 0, 255);
    	    		    pTemp[3] = CLIP3((a * srcData[p1 + 3] + b * srcData[p2 + 3] + c * srcData[p3 + 3] + d * srcData[p4 + 3]), 0, 255);
    				}
    				else
    				{
    					pTemp[0] = 0;
    	    		    pTemp[1] = 0;
    	    		    pTemp[2] = 0;
    	    		    pTemp[3] = 255;
    				}
    				pTemp += 4;
    	    	}
    	    }
    	}
    	return tempData;
    };

            本文的代码可以看到,基本都是将插值的interpolation条件判断放到了循环外面,导致代码段较长,实际上这样做是为了优化速度,增强代码可读性,大家可以体会一下。

            我们对上面接口进行调用测试如下:

    #include "stdafx.h"
    #include"imgRW\f_SF_ImgBase_RW.h"
    #include"f_Transform.h"
    int _tmain(int argc, _TCHAR* argv[])
    {
    	//定义输入图像路径
    	char* inputImgPath = "Test.png";
    	//定义图像宽高信息
    	int width = 0, height = 0, component = 0, stride = 0;
    	//图像读取(得到32位bgra格式图像数据)
    	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
    	stride = width * 4;
    	int ret = 0;
    	//其他图像处理操作(这里以32位彩色图像为例)
    	//////////////////////////IMAGE PROCESS/////////////////////////////
    	//图像旋转
    	int outW, outH, outS;
    	int angle = 80;
    	int interpolation = INTERPOLATE_NEAREST;//INTERPOLATE_BILINEAR;//INTERPOLATE_NEAREST;
    	char* outRotateImgPath = "res_rotate_nearest.jpg";
    	unsigned char* pResRotate = f_Rotate(bgraData, width, height, stride, angle, &outW, &outH, &outS, interpolation);
    	ret = Trent_ImgBase_ImageSave(outRotateImgPath, outW, outH, pResRotate, JPG);
    	free(pResRotate);
    	printf("f_Rotate is finished!");
    	////////////////////////////////////////////////////////////////////
    	free(bgraData);
    	return 0;
    }

            大家可以看到,调用非常方便,甚至比opencv更加通俗易懂。最后我们给出对应的测试效果,如图Fig.8所示。

            本节完整的代码工程关注本人公众号“SF图像算法”有相关下载链接即可免费下载。

    [知识扩展]

            在本文中,我们详细了解了图像平移、图像缩放和图像旋转三种图像几何变换,也是最常用的图像基本变换。在这个过程中我们是单个一一讲解的,为的是让新手同学们能够各个击破,单独理解。而实际中,这三个变换可以通过一个仿射变换矩阵来统一表达,放这边还矩阵如下:

            其中,s表示缩放比例,tx和ty表示平移量,theta表示角度;

            按照这个公式,我们可以一次计算出三种变换后对应的结果(x’,y’),然后再进行插值计算即可,大家可以自己尝试一下;

            对于图像变形和插值算法,多少年来,有无数相关的论文研究,近几年来,随着深度学习的飞速发展,基于卷积神经网络的超分辨率,图像复原以及图像变形的算法也是层出不穷,可见其意义之大!

            最后,学海无涯,共勉!

     

     

    展开全文
  • 图像缩放的定义为:将图像中的某点(x,y)经缩放后其位置变为(x’,y’),则两者之间的关系为:  X’= ax y’ = by a、b分别是x、y方向上的缩放比例。当a、b大于1时图像放大,小于1时,图像缩小。当a = -1,b = 1时会...
  • 图像缩放,其实关键点在于图像的像素点之间的对应关系该怎么处理。如下图: 左边是原图像,右边是要放大后的图像,那么这两个图形之间的像素点该怎么对应。 方法1: 就是 对小图的每个像素,计算其在大图...
  • 使用插值算法实现图像缩放是数字图像处理算法经常遇到的问题。我们经常会将某种尺寸的图像转换为其他尺寸的图像,如放大或者缩小图像。由于在缩放的过程会遇到浮点数,如何在FPGA正确的处理浮点数运算是在FPGA...
  • 图像的几何变换,如缩放、旋转和翻转等,在图像处理中扮演着重要的角色,python的Image类分别提供了这些操作的接口函数
  • 所以在程序我们可以使用一个2*3的数组结构来存储变换矩阵: 以最简单的平移变换为例,平移(b1,b2)坐标可以表示为: 因此,平移变换的变换矩阵及逆矩阵记为: 2、缩放变换: 将图像横坐标放大(或缩小)sx倍,...
  • 主要结合自己大三所学课程《数字图像处理》及课件进行讲解,主要通过MFC单文档视图实现显示BMP图片空间几何变换,包括图像平移、图形旋转、图像反转倒置镜像和图像缩放的知识。同时文章比较详细基础,没有采用GDI+...
  • 该资源主要参考我的博客【数字图像处理】六.MFC空间几何变换之图像平移、镜像、旋转、缩放详解,博客地址http://blog.csdn.net/eastmount/article/details/46345299 主要讲述基于VC++6.0 MFC图像处理的应用知识,要...
  • 1.4数字图像处理的基本步骤 数字图像处理的内容划分为以下两个主要类别:一类是其输入输出都是图像。一类是其输入可能是图像,但输出是从这些图像提取的属性。 图像获取是数字图像处理的第一步处理。图像获取与...
  • 以灰度图像circuit.tif为例,利用Matlab图像处理工具箱的imresize函数对图像进行比例缩放变换。要求:创建4个figure窗口(不可以用subplot,显示不出来放大效果),分别用于显示原始图像、等比例放大1.5倍后的图像...
  • 图像处理中是一种很基础的几何变换,但是具有很重要的作用,比如:当输入图片尺寸过大时,处理速度会很慢,适当的缩小图像可以在不影响处理效果的同时有效提高代码执行速度。 opencv提供了resize函数实现图片缩放...
  • 1、最近邻插值(最最基础的方法了) 举个栗子: 假设我们有一张原始图(源图,source),大小为3*3。 225 46 77 56 88 77 45 125 35 想要放大为4*4的目标图(destination)。... ...
  • 该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门、OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子、图像增强技术、图像分割等,后期结合深度学习研究图像识别、图像分类应用。...
  • 本简易图像处理GUI实现 加载图片、灰度处理图片、0.5倍缩放图片、保存处理后的图片功能;版本为R2018b 中文版本。
  • 用了CImg库和opencv实现图片的缩放 题目: Zooming and Shrinking Images by Pixel Replication (a) Write a computer program capable of zooming and shrinking an image by pixel replication. Assume that the...
1 2 3 4 5 ... 20
收藏数 80,705
精华内容 32,282
关键字:

图像处理中什么是缩放