[傅里叶变换]手绘板-傅里叶绘图Matlab小程序

1、一点叨叨

傅里叶原理表明:任何连续测量的时序或信号,都可以表示为不同频率的正弦波信号的无限叠加。在信号分析中,我们可以通过傅里叶变换把时域信号变换到频域,让我们转换视角观察信号,同样我们还能够在频域选择我们所需要的频率的信号,而这在时域是完全做不到的。

除了时域频域的视角,傅里叶绘图的玩法使我们可以通过几何方面来理解傅里叶变换。


傅里叶级数的几何表示

如上图所示,傅里叶级数的几何描述便是一组首尾相连的向量,每个向量代表着傅里叶级数中的一项,不同的向量以不同角速度旋转,向量末端的点在一个方向上的投影便是原信号,即级数各项之和。而如果把向量末端看作一个坐标点的话,此坐标在相互正交的方向上投影得到的两个信号便是此点在正交方向上投影长度变化的描述,反过来,这两个信号组合便可以在平面上绘制此坐标点绘制的曲线。

通过以上对傅里叶变换的理解,我们便可以对一组使用复坐标描述的在二维平面上的连续点(间隔很小)做离散傅里叶变换,再通过离散傅里叶逆变换得到原复坐标的一种新的表述形式,而这种形式就如上面的傅里叶级数一样,使用一组有限个以不同角速度旋转的首尾相连的向量来描述这些连续点的,基于此原理,我们可以通过Matlab编写程序,来实现通过傅里叶方法绘制曲线的功能。

主要要实现的就是复坐标向量转傅里叶绘制矩阵,通过对复坐标向量进行离散傅里叶变换和离散傅里叶逆变换得到有限个以不同角速度旋转的首尾相连的向量来描述复坐标向量,并将此绘制矩阵供给绘制函数使用


对于N个复数序列x_n,我们对其进行离散傅里叶变换:
X_k=\sum_{k=0}^{N-1}x_n\cdot e^{-i2\pi kn/N}
N个复数序列X_k又可以进行离散傅里叶逆变换得到:
x_n=\frac{1}{N}\sum_{k=0}^{N-1}X_k\cdot e^{i2\pi kn/N}
上式中,n=0时:
x_0=\frac{1}{N}(X_0\cdot e^0+X_1\cdot e^0+X_2\cdot e^0+\cdots +X_{N-2}\cdot e^0 +X_{N-1}\cdot e^0)
n=1时:
x_1=\frac{1}{N}(X_0\cdot e^0+X_1\cdot e^{\frac{i2\pi}{N}}+X_2\cdot e^{\frac{i4\pi}{N}}+\cdots +X_{N-2}\cdot e^{\frac{i2\pi (N-2)}{N}} +X_{N-1}\cdot e^{\frac{i2\pi (N-1)}{N}})
不难看出,随着n的增大,这种描述便是有限个以不同角速度旋转的首尾相连的向量,X_k便是那一个个向量,e^{i2\pi kn/N}便是随着n增大时不同X_k的不同旋转量,由于N一般取值较大,那么随着n的增大旋转变化比较小,可以达到一种旋转向量连续绘制线条的动态效果。以上代码便是通过这一原理对初始的复坐标向量进行转换,得到进行动态绘制的参数矩阵。


2、代码

其实是我大学MATLAB大作业写的,贴出来凑点Cyber junk吧。不需要.fig文件,窗口都写代码里的,一个文件运行就行。

%%                                 ----****手绘图形转傅里叶绘制小程序****----
%==========================================================================================================================
%    【基本功能】:“跟随模式”记录手绘板绘制的线条(记录鼠标绘制速度),使用傅里叶方法绘制线条;
%                  “边缘模式”提取手绘板绘制图案的边缘,使用傅里叶方法匀速的绘制图案边缘。
%
%    【使用方法】:选择绘制模式在手绘板上随意绘制图案,点击‘傅里叶绘制’打开窗口进行动态绘制,点击‘清除’清除手绘板上已
%                  绘制的图形。
%                 
%    【注    意】:点击‘傅里叶绘制’后在绘制过程中无法操作手绘板窗口,等待绘制完成后再次进行手绘板操作,傅里叶绘制如需停止
%                  请按<Ctrl>+<C>。
%
%==========================================================================================================================


%% 主程序(窗口设置)
function Fourier_Draw()                          %主程序,创建小程序窗口
%--------------------------------------------------------------------------------------------------------------------------
close all
global GUI;                                      %全局GUI变量,方便控件交互
global Fdraw_able;                               %可以进行傅里叶绘制的判断变量
Fdraw_able = 0;                                  %刚打开程序,手绘板上无图像,设置不能进行傅里叶绘制
global axes_x axes_y axes_l;                     %主窗口可绘图区域参数
global h_l;                                      %方形主窗口边长

h_l = 400;                                               %设置方形主窗口边长
GUI.h=figure('unit','pixels',...                         %窗口显示单位为像素       
    'position',[100, 100, h_l, h_l,],...                 %窗口位置
    'Name','手绘板',...                                  %窗口标题
    'Color',[0.15 0.15 0.15],...                         %窗口颜色
    'menubar','none',...                                 %窗口菜单栏不显示
    'numbertitle','off',...                              %窗口数字标记不显示
    'resize','off',...                                   %窗口尺寸不可调整
    'WindowButtonDownFcn',@WindowButtonDownFcn,...       %鼠标点击窗口时调用WindowButtonDownFcn函数
    'WindowButtonMotionFcn',@WindowButtonMotionFcn,...   %鼠标点击并移动时调用WindowButtonMotionFcn函数
    'WindowButtonUpFcn',@WindowButtonUpFcn);             %鼠标松开时调用WindowButtonUpFcn函数

GUI.button1 = uicontrol('Parent',GUI.h,...               %属于GUI.h的控件
    'Style','pushbutton',...                             %控件类型为按键
    'String','傅里叶绘制',...                             %按键文字
    'Position',[h_l/8, h_l/16, h_l/4, h_l/8],...         %按键位置和尺寸
    'visible','on',...                                   %按键可视
    'FontName','黑体',...                                %文字字体
    'FontSize',10,...                                    %文字字号
    'FontWeight','bold',...                              %文字粗体
    'callback',@StartFourier_Draw);                      %点击按键时调用StartFourier_Draw函数

GUI.button2 = uicontrol('Parent',GUI.h,...
    'Style','pushbutton',...
    'String','清除',...
    'Position',[5*h_l/8, h_l/16, h_l/4, h_l/8],...
    'visible','on',...
    'FontName','黑体',...
    'FontSize',10,...
    'FontWeight','bold',...
    'callback',@Clean_Screen);                                %点击按键时调用Clean_Screen函数

GUI.popupmenu1 = uicontrol('Parent',GUI.h,...
    'Style','popupmenu',...                                   %控件类型为弹出栏
    'String',['跟随模式';'边缘模式'],...                       %弹出栏的可选项
    'Position',[33*h_l/80, 9*h_l/80 7*h_l/40 3*h_l/80],...
    'visible','on',...
    'FontName','黑体',...
    'FontSize',8,...
    'callback',@Clean_Screen);                                %切换选项时调用Clean_Screen函数

axes_x = 0.125;                                         %window1坐标系原点x所在位置(窗口比例值)
axes_y = 0.2;                                           %window1坐标系原点y所在位置(窗口比例值)
axes_l = 0.75;                                          %window1坐标系长和宽(窗口比例值)
GUI.window1 = axes('Parent',GUI.h,...                   %属于GUI.h的坐标系
    'Position',[axes_x axes_y axes_l axes_l],...
    'xtick',[],...                                      %坐标轴无刻度
    'ytick',[],...
    'xcolor','w',...                                    %坐标轴颜色为白色,即不可见
    'ycolor','w');
%--------------------------------------------------------------------------------------------------------------------------
end

%% 鼠标点击窗口反馈函数
function WindowButtonDownFcn(~,~)
%--------------------------------------------------------------------------------------------------------------------------
global GUI
global axes_l h_l;                              %window1坐标系长和宽(窗口比例值);方形主窗口边长             
set(GUI.window1,'XLim',[0,1],'YLim',[0,1])      %固定window1窗口坐标范围
global mode;                                    %绘制模式参数
global draw_enable;                             %手绘板可绘制标志
global x y;                                     %存取鼠标位置坐标
global width;                                   %边缘模式手绘板画笔粗细
global D_Path;                                  %跟随模式连续点复坐标向量存取                            
global add_point;                               %跟随模式线条插入点
global Rsl;                                     %傅里叶绘制分辨率,用于减少边缘模式绘制出现的锯齿

Rsl = 1.67;                                     %设置分辨率,建议在0~3之内
D_Path = [];                                    %清空D_Path
mode = get(GUI.popupmenu1,'Value');             %读取弹出栏选项,跟随模式为1,边缘模式为2
add_point = 5;                                  %设置插入点,越大存取连续点越多,跟随模式绘制越慢
width = 0;                                      %初始笔触粗细为0
    position=get(gca,'currentpoint');           %获取最近一次鼠标位置,gca代表当前坐标系
    x(1)=position(1);                           %点击位置横坐标
    y(1)=position(3);                           %点击位置纵坐标
    if  x(1)>0 && x(1)<1 && y(1)>0 && y(1)<1    %判断点击位置是否在window1内
        draw_enable=1;                          %打开绘制
        if mode == 1                            %跟随模式存储起点复坐标
            D_Path(1) = Rsl*x(1)*axes_l*h_l + Rsl*y(1)*axes_l*h_l*1i;
        end
    end
%--------------------------------------------------------------------------------------------------------------------------    
end

%% 鼠标点击移动反馈函数
function WindowButtonMotionFcn(~,~)
%--------------------------------------------------------------------------------------------------------------------------
global axes_l h_l;             %window1坐标系长和宽(窗口比例值);方形主窗口边长             
global mode;                   %绘制模式参数
global draw_enable;            %手绘板可绘制标志
global Fdraw_able;             %傅里叶绘制可绘制标志
global x y;                    %存取鼠标位置坐标
global width;                  %边缘模式手绘板画笔粗细
global D_Path;                 %跟随模式连续点复坐标向量临时存储 
global add_point;              %跟随模式线条插入点
global Rsl;                    %傅里叶绘制分辨率

if draw_enable                            %判断是否可以绘制
Fdraw_able = 1;                           %鼠标点击并移动手绘板上就一定有图像,设置傅里叶绘制可绘制

    position=get(gca,'currentpoint');           %获取最近一次鼠标位置,gca代表当前坐标系
    x(end)=position(1);                         %存储短线条终点
    y(end)=position(3);
    if mode == 1                                                %如果是跟随模式
        if  x(end)>0 && x(end)<1  &&  y(end)>0 && y(end)<1      %如果鼠标在window1内
            x = linspace(x(1),x(end),add_point);                %细分线条并插入点
            y = linspace(y(1),y(end),add_point);
            line(x,y,'LineWidth',2,'color','k');                %绘制短线条,起点为前一次调用WindowButtonMotionFcn的鼠标坐标,
                                                                %终点为本次调用WindowButtonMotionFcn的鼠标坐标
            for add = 2:add_point                               
                D_Path(end+1) =Rsl*x(add)*axes_l*h_l + Rsl*y(add)*axes_l*h_l*1i;   %跟随模式存储后续复坐标
            end
        end
    end
    if mode == 2                                                %如果是边缘模式
        x = linspace(x(1),x(end),10);                           %细分线条并插入8点
        y = linspace(y(1),y(end),10);
        if width < 4                                            %边缘模式手绘板起始伪笔触,笔触慢慢变粗
            width = width + 0.5;
        end
        if  width >2 && sqrt((x(end)-x(1))*(x(end)-x(1)) + (y(end)-y(1))*(y(end)-y(1))) > 0.1
            width = width -0.7;                                 %边缘模式手绘板鼠标快速绘制伪笔触,绘制过快线条会变细
        end
        line(x,y,'LineWidth',width,'color','k','Marker','.');   %绘制短线条,起点为前一次调用WindowButtonMotionFcn的鼠标坐标,
                                                                %终点为本次调用WindowButtonMotionFcn的鼠标坐标
    end
    x(1)=x(end);                                                %短线条起始点变换为本次调用WindowButtonMotionFcn
    y(1)=y(end);
end
%--------------------------------------------------------------------------------------------------------------------------
end

%% 鼠标松开反馈函数
function WindowButtonUpFcn(~,~)
%--------------------------------------------------------------------------------------------------------------------------
global mode;                    %绘制模式参数
global draw_enable;             %手绘板可绘制标志
global Follow_Lines;            %跟随模式连续点复坐标向量存取结构体 
global D_Path;                  %跟随模式连续点复坐标向量临时存储 

if draw_enable                              %判断是否可以绘制  
    if mode == 1                            %如果是跟随模式
        Follow_Lines(end+1).path = D_Path;  %存储这一笔绘制的线条
    end
end
draw_enable = 0;                            %关闭绘制
%--------------------------------------------------------------------------------------------------------------------------
end

%% 清除手写板函数
function Clean_Screen(~,~)
%--------------------------------------------------------------------------------------------------------------------------
global GUI;
global Fdraw_able;                       %傅里叶绘制可绘制标志
global Follow_Lines;                     %跟随模式连续点复坐标向量存取结构体 
     cla(GUI.window1);                   %清除window1上的图像
     Fdraw_able = 0;                     %设置傅里叶绘制不可绘制
     Follow_Lines = struct('path', {});  %清空跟随模式存储连续点复坐标向量的结构体 
%--------------------------------------------------------------------------------------------------------------------------
end

%% 傅里叶绘制按键反馈函数
function StartFourier_Draw(~,~)
%--------------------------------------------------------------------------------------------------------------------------
global GUI;
global axes_l h_l;               %window1坐标系长和宽(窗口比例值);方形主窗口边长 
global Fdraw_able;               %傅里叶绘制可绘制标志
global mode;                     %绘制模式参数
global Follow_Lines;             %跟随模式连续点复坐标向量存取结构体 

if Fdraw_able == 1               %如果可以傅里叶绘制,打开子窗口sh

GUI.sh=figure('unit','pixels',...                %窗口显示单位为像素
   'position',[100+h_l 100 600 600],...          %窗口位置
   'Name','傅里叶绘图',...                        %窗口标题
   'Color',[0.15 0.15 0.15],...                  %窗口颜色
   'menubar','none',...                          %窗口菜单栏不显示
   'numbertitle','off',...                       %窗口数字标记不显示
   'resize','on',...                             %窗口尺寸可调整
   'visible','on',...                            %窗口不可视
   'DeleteFcn',@Clean_Screen);                   %窗口关闭时调用Clean_Screen函数
    
pause(0.01)                                  %等待子窗口打开
set(GUI.sh,'windowstyle','modal')            %设置子窗口打开时主窗口不可操作
pause(0.01)                                  %等待设置完成
    
    if mode == 1                             %如果是跟随模式
        F1 = DFT_IDFT(Follow_Lines);         %求解Follow_Line的绘制矩阵F1
        FFT_Draw(F1);                        %由绘制矩阵F1进行动态傅里叶绘制
    end
        
    if mode == 2                                                       %如果是边缘模式
        cut=getframe(GUI.window1,[2, 2, axes_l*h_l-2, axes_l*h_l-2]);  %截取window1的图片
        Graph_Lines = Pick_Point(cut.cdata);                           %提取转换window1图片的边缘连续点为
                                                                       %连续点复坐标向量结构体Graph_Line
        F2 = DFT_IDFT(Graph_Lines);                                    %求解Follow_Line的绘制矩阵F2
        FFT_Draw(F2);                                                  %由绘制矩阵F2进行动态傅里叶绘制
    end
end
Follow_Lines = struct('path', {});                                     %清空跟随模式存储连续点复坐标向量的结构体 
%--------------------------------------------------------------------------------------------------------------------------
end

%% =========================================我是明显的分割线=====================================================
%     分界线上是有关GUI设置以及控件交互的代码,包含了少部分跟随模式存储线条的代码,下面的代码是数据处理与绘图的代码,它们是程序的
% 核心部分,程序想要展示的部分都由它们来实现。


%% 连续点提取函数(边缘绘制模式)
function Line_Points = Pick_Point(img)
%--------------------------------------------------------------------------------------------------------------------------
global G Path;                         %预处理图像矩阵;边缘模式连续点复坐标向量临时存储 
global FindG;                          %寻找初始绘制点矩阵
global axes_l h_l;                     %window1坐标系长和宽(窗口比例值);方形主窗口边长      
global msizex msizey;                  %图像矩阵行数和列数
global sx sy;                          %初始绘制点坐标
global Rsl;                            %傅里叶绘制分辨率
msizex = Rsl*floor(axes_l*h_l);            
msizey = Rsl*floor(axes_l*h_l);

resize = imresize(img, [msizex, msizey]);         %对图像矩阵进行缩放
grey = rgb2gray(resize);                          %真彩图转换为灰度图
B_eg = edge(grey,'Canny');                        %转二值化图像,边缘为1,其他地方为0
    
Line_Points = struct('path', {});                 %边缘模式连续点复坐标向量存取结构体,函数输出
G = rot90(B_eg, 3);                               %eg矩阵逆时针旋转90*3度,即顺时针旋转90度,因为矩阵下标与坐标系坐标存在一个
FindG = rot90(B_eg, 3);                           %顺时针旋转90度的关系,存入G和FindG中
Path = [];                                        %Path清空
sx = 0;sy =0;
for x = 1:msizex                                  %1值点扫描
     for y = 1:msizey
        if G(x,y) ~= 0                            %找到1值点
            Find_StartPoint(x,y);                 %寻找此线条的初始绘制点
            Zchange(sx,sy);                       %将连续1值点转化为连续点复坐标向量        
            Line_Points(end + 1).path = Path;     %存储此连续线条
            Path = [];                            %Path清空
        end
    end
end

% 寻找初始绘制点函数 (*深度优先遍历算法*)
function [] = Find_StartPoint(x, y)
    dx = [0, 0,1,-1,1, 1,-1,-1,0, 0,2,-2,1,2, 2, 1,-1,-2,-2,-1,0,3, 0,-3,2, 2,-2,-2];  %搜寻向量
    dy = [1,-1,0, 0,1,-1,-1, 1,2,-2,0, 0,2,1,-1,-2,-2,-1, 1, 2,3,0,-3, 0,2,-2,-2, 2];
    FindG(x, y) = 0;                                       %当前1值点清零,避免无限递归
    sx = x; sy = y;                                        %记录当前坐标
    for scan = 1:length(dx)                                %周边点扫描  
        if (x+dx(scan)>0)&&(y+dy(scan)>0)&&(x+dx(scan)<msizex)&&(y+dy(scan)<msizey)    %限制搜寻范围在矩阵内
            if FindG(x+dx(scan),y+dy(scan)) ~= 0           %找到临近1值点
                Find_StartPoint(x+dx(scan),y+dy(scan));    %递归调用直到搜寻到此线条的终点            
                break                                      %回溯到初始点后跳出循环,不改变已保存的线条终点坐标sx,sy           
            end                                            %终点坐标sx,sy便可作为绘制的起点坐标
        end
    end
end

% 连续点转换连续点复坐标向量函数 (*深度优先遍历算法*)
function [] = Zchange(x, y)
    dx = [0, 0,1,-1,1, 1,-1,-1,0, 0,2,-2,1,2, 2, 1,-1,-2,-2,-1,0,3, 0,-3,2, 2,-2,-2];
    dy = [1,-1,0, 0,1,-1,-1, 1,2,-2,0, 0,2,1,-1,-2,-2,-1, 1, 2,3,0,-3, 0,2,-2,-2, 2];
    G(x, y) = 0;                                       %当前1值点清零,避免无限递归
    Path(end + 1) = x + y * 1i;                        %轮廓点的坐标用复数表示
    for scan = 1:length(dx)                            %周边点扫描
        if (x+dx(scan)>0)&&(y+dy(scan)>0)&&(x+dx(scan)<msizex)&&(y+dy(scan)<msizey)    %限制搜寻范围在矩阵内
            if G(x+dx(scan),y+dy(scan)) ~= 0           %找到临近1值点
                Zchange(x+dx(scan),y+dy(scan));        %递归调用转换周边的连续1值点  
                break                                  %回溯到初始点后跳出循环,防止产生跳跃线
            end       
        end
    end
end
%--------------------------------------------------------------------------------------------------------------------------
end

%% 连续线转傅里叶矩阵函数(**核心原理代码**)
function XnSet = DFT_IDFT(line_points)
%--------------------------------------------------------------------------------------------------------------------------
XnSet = struct('Xn',{},'XnSum', {});                           %用于动态傅里叶绘制的矩阵结构体
for serial_L = 1:length(line_points)                           %serial_L代表第serial_L条连续线
    N = length(line_points(serial_L).path);
    if N < 1000
        Xk = dft(line_points(serial_L).path,N);                %对连续点复向量做傅里叶变换  
    else
        Xk = fft(line_points(serial_L).path,N);
    end
    
    Wnk = exp(2i*pi*([0:length(Xk)-1]'*[0:length(Xk)-1])./ length(Xk))./ length(Xk);  %傅里叶逆变换的(W_N^-nk)/N矩阵
    
    for serial_P = 1:length(Wnk(:, 1))                         %serial_P代表第serial_P个连续点                  
        Wnk(serial_P,:) = Xk.* Wnk(serial_P,:);                %求傅里叶逆变换矩阵Xk*(W_N^-nk)/N
    end
                                                 %[X0*(W_N^0)/N, X1*(W_N^0)/N  , ... , Xk*(W_N^0)/N  , ... ]
    XnSet(end + 1).Xn = Wnk;            %Xn保存  %[X0*(W_N^0)/N, X1*(W_N^-1)/N , ... , Xk*(W_N^-k)/N , ... ] 矩阵
                                                 %[     ...    ,      ...      , ... ,        ...    , ... ]
                                                 %[X0*(W_N^0)/N, X1*(W_N^-n)/N , ... , Xk*(W_N^-nk)/N, ... ]
                                                     
    XnSet(end).XnSum = cumsum(Wnk, 2);  %Xn按行求累积和得到动态绘制指向边缘点的组合向量所需的矩阵
end

% 离散傅里叶变换函数 ( -提高运算速度可以直接使用fft函数,这里只为把代码原理表述的更详尽- )
function Xk=dft(xn,N)
    xn=[xn,zeros(1,N-length(xn))];   %xn(1*N)
    n=0:N-1;
    k=0:N-1;
    WN=exp(-1j*2*pi/N);
    nk=n'*k;
    WNnk=WN.^nk;                     %W_N^nk矩阵(N*N)
    Xk=xn*WNnk;                      %Xk(1*N)
end
%--------------------------------------------------------------------------------------------------------------------------
end

%% 傅里叶动态绘图函数
function [] = FFT_Draw(XnSet)
%--------------------------------------------------------------------------------------------------------------------------
global axes_l h_l;                                     %window1坐标系长和宽(窗口比例值);方形主窗口边长   
global serial_color;                                   %子圆变色绘制参数;
global Rsl;                                            %傅里叶绘制分辨率

for serial_L = 1:length(XnSet)                                   %第serial_L条连续线
    for serial_P = 1:ceil(Rsl):length(XnSet(serial_L).XnSum)     %第serial_P个连续点
        
        serial_color = 6;                              %每画一帧子圆变色绘制参数回归初始值,达到每个子圆的颜色一直保持不变
        clf                                            %清除serial_P-1时绘制的图案达到动态效果
        hold on                                        %开启保留绘图痕迹
            
        if serial_L > 1                                %第serial_L条连续线绘制时保持前几条边缘线不消失
             for k = 1:serial_L - 1
                 if length(XnSet(k).XnSum(1,:)) >1
                    lastpoint = 2*XnSet(k).XnSum(end,end) - XnSet(k).XnSum(end-1,end);  %添加最后短线条绘制使线闭合
                    plot([XnSet(k).XnSum(:,end);lastpoint],'-k','LineWidth',2);         %最后一列即为原来的边缘点复数坐标值
                 end
            end
        end
       
        plot(XnSet(serial_L).XnSum(1:serial_P,end),'-k','LineWidth',2);                      %绘制serial_P之前的所有边缘点
         
        if  serial_L~= length(XnSet) || serial_P~= length(XnSet(length(XnSet)).XnSum) -...
            mod(length(XnSet(length(XnSet)).XnSum)-1,ceil(Rsl))                             %如果不是最后的线条的最后一点
            plot(XnSet(serial_L).XnSum(serial_P,end),'k.','MarkerSize',10);                 %绘制组合向量笔头点
            for d = 2:length(XnSet(serial_L).XnSum(serial_P,:))-1
                rd = length(XnSet(serial_L).XnSum(serial_P,:))+1-d;                         %由末端开始绘圆使中心圆覆盖边缘圆
                if abs(XnSet(serial_L).Xn(serial_P,rd+1)) > 2                                       %如果Xkn向量长度大于2
                    Zcircle(XnSet(serial_L).XnSum(serial_P,rd),XnSet(serial_L).Xn(serial_P,rd+1));  %绘制Xkn向量旋转子圆
                end
            end
            if length(XnSet(serial_L).Xn(serial_P,:)) > 1                                    %如果线条不止一个点
                Center_circle(XnSet(serial_L).XnSum(serial_P,1),XnSet(serial_L).Xn(serial_P,2));    %绘制组合向量X0/N中心圆
            end 
            plot(XnSet(serial_L).XnSum(serial_P, :),'Color',[150,50,0]/255,'LineWidth',1.5); %绘制最终指向边缘点的组合向量
            plot(XnSet(serial_L).XnSum(serial_P,1),'r.','MarkerSize',20);                    %绘制组合向量中心点          
            plot(XnSet(serial_L).XnSum(serial_P,1),'ro','MarkerSize',8);                     %绘制组合向量中心小圆
        end
        
        hold off                                                %关闭不保留绘图痕迹                                 
 
        xlim([2,Rsl*axes_l*h_l-2]);ylim([2,Rsl*axes_l*h_l-2])   %限制画框大小不变
        set(gca,'xtick',[])                                     %坐标轴无刻度
        set(gca,'ytick',[])
        set(gca,'xcolor','w')                                   %坐标轴颜色为白色,即不可见
        set(gca,'ycolor','w')
        pause(0.01/(10^Rsl))                                    %控制绘图速度    
    end
    
    hold on                                                     %开启保留绘图痕迹       
    if length(XnSet(end).XnSum(1,:)) > 1                        %如果最后一条线不是单个点
        end_lastpoint = 2*XnSet(end).XnSum(end,end) - XnSet(end).XnSum(end-1,end);  %添加最后一条线的最后短线条
        plot([XnSet(end).XnSum(:,end);end_lastpoint],'-k','LineWidth',2);           %绘制最后一条线
    end
    hold off                                                    %关闭保留绘图痕迹
    
    xlim([2,Rsl*axes_l*h_l-2]);ylim([2,Rsl*axes_l*h_l-2])       %限制画框大小不变
    set(gca,'xtick',[])                                         %坐标轴无刻度
    set(gca,'ytick',[])
    set(gca,'xcolor','w')                                       %坐标轴颜色为白色,即不可见
    set(gca,'ycolor','w')
end

% 红色中心圆绘制函数
function [] = Center_circle(zo,zr)
    t=0:pi/20:2*pi;                                  %用多边形绘制近似圆
    circle=zo+abs(zr)*exp(1j*t);                     %e^(j*t)即为单位圆
    plot(circle,'r','LineWidth',0.8)
end

% 彩色子圆绘制函数
function [] = Zcircle(zo,zr)
    serial_color = serial_color +1;
    serial_color = mod(serial_color,7);                                  %子圆变色绘制参数加一,使子圆有不同的颜色
    color = [0.90,0.10,0.40; 0.85,0.33,0.1; 0.30,0.75,0.93; 0.93,0.69,0.13; 0.20,0.70,0.20; 0.00,0.45,0.74; 0.49,0.18,0.56];
    t=0:pi/20:2*pi;
    circle=zo+abs(zr)*exp(1j*t);
    plot(circle,'Color',color(serial_color+1,:),'LineWidth',0.6)         %按序拾取color的RGB参数绘制不同颜色的圆
end
%--------------------------------------------------------------------------------------------------------------------------
end

%==========================================================================================================================

3、一些现象

  • 【跟随模式】会把你画图的速度也记录下来(因为代码的逻辑就是直接记录你鼠标的坐标),按照和你画图速度同比例绘制,不是一模一样的速度但是哪里快哪里慢是按比例来的,就类似于录制。

  • 【边缘模式】是使用算法提取图像边缘然后绘制的,输入的是二维图片取连续像素点,绘制线条就是匀速的,观感要好一些。


    动起来就是这样:

  • 【跟随模式】自己把线条首尾画闭合(起笔处与终笔处相连)就还可以:


不相连画面就会出现:



动起来是这样:


看起来不太好看,一条条直线像是连成一个光滑的曲线了,至于原因,应该可以通过观察公式来定性分析。


4、参考链接

  1. 鸭嘎嘎.手把手教你用傅立叶变换画可达鸭.
    https://zhuanlan.zhihu.com/p/72632360
  2. 打浦桥程序员.一把王者荣耀的时间,让你学会MATLAB GUI.
    https://zhuanlan.zhihu.com/p/150792841
  3. 荼荼灰.matlab 傅里叶画任何图.
    https://blog.csdn.net/qq_36553572/article/details/107131750
  4. Rustle.数字图像处理:边缘检测(Edge detection).
    https://zhuanlan.zhihu.com/p/59640437
  5. 打个酱油_就走.MATLAB GUI设计手写输入板.
    https://blog.csdn.net/hardrious/article/details/49226149

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容