1、一點(diǎn)叨叨
傅里葉原理表明:任何連續(xù)測(cè)量的時(shí)序或信號(hào),都可以表示為不同頻率的正弦波信號(hào)的無(wú)限疊加。在信號(hào)分析中,我們可以通過(guò)傅里葉變換把時(shí)域信號(hào)變換到頻域,讓我們轉(zhuǎn)換視角觀察信號(hào),同樣我們還能夠在頻域選擇我們所需要的頻率的信號(hào),而這在時(shí)域是完全做不到的。
除了時(shí)域頻域的視角,傅里葉繪圖的玩法使我們可以通過(guò)幾何方面來(lái)理解傅里葉變換。

如上圖所示,傅里葉級(jí)數(shù)的幾何描述便是一組首尾相連的向量,每個(gè)向量代表著傅里葉級(jí)數(shù)中的一項(xiàng),不同的向量以不同角速度旋轉(zhuǎn),向量末端的點(diǎn)在一個(gè)方向上的投影便是原信號(hào),即級(jí)數(shù)各項(xiàng)之和。而如果把向量末端看作一個(gè)坐標(biāo)點(diǎn)的話,此坐標(biāo)在相互正交的方向上投影得到的兩個(gè)信號(hào)便是此點(diǎn)在正交方向上投影長(zhǎng)度變化的描述,反過(guò)來(lái),這兩個(gè)信號(hào)組合便可以在平面上繪制此坐標(biāo)點(diǎn)繪制的曲線。
通過(guò)以上對(duì)傅里葉變換的理解,我們便可以對(duì)一組使用復(fù)坐標(biāo)描述的在二維平面上的連續(xù)點(diǎn)(間隔很?。┳鲭x散傅里葉變換,再通過(guò)離散傅里葉逆變換得到原復(fù)坐標(biāo)的一種新的表述形式,而這種形式就如上面的傅里葉級(jí)數(shù)一樣,使用一組有限個(gè)以不同角速度旋轉(zhuǎn)的首尾相連的向量來(lái)描述這些連續(xù)點(diǎn)的,基于此原理,我們可以通過(guò)Matlab編寫(xiě)程序,來(lái)實(shí)現(xiàn)通過(guò)傅里葉方法繪制曲線的功能。
主要要實(shí)現(xiàn)的就是復(fù)坐標(biāo)向量轉(zhuǎn)傅里葉繪制矩陣,通過(guò)對(duì)復(fù)坐標(biāo)向量進(jìn)行離散傅里葉變換和離散傅里葉逆變換得到有限個(gè)以不同角速度旋轉(zhuǎn)的首尾相連的向量來(lái)描述復(fù)坐標(biāo)向量,并將此繪制矩陣供給繪制函數(shù)使用。

對(duì)于
上式中,
不難看出,隨著
2、代碼
其實(shí)是我大學(xué)MATLAB大作業(yè)寫(xiě)的,貼出來(lái)湊點(diǎn)Cyber Junk吧。不需要.fig文件,窗口都寫(xiě)代碼里的,一個(gè)文件運(yùn)行就行。
%% ----****手繪圖形轉(zhuǎn)傅里葉繪制小程序****----
%==========================================================================================================================
% 【基本功能】:“跟隨模式”記錄手繪板繪制的線條(記錄鼠標(biāo)繪制速度),使用傅里葉方法繪制線條;
% “邊緣模式”提取手繪板繪制圖案的邊緣,使用傅里葉方法勻速的繪制圖案邊緣。
%
% 【使用方法】:選擇繪制模式在手繪板上隨意繪制圖案,點(diǎn)擊‘傅里葉繪制’打開(kāi)窗口進(jìn)行動(dòng)態(tài)繪制,點(diǎn)擊‘清除’清除手繪板上已
% 繪制的圖形。
%
% 【注 意】:點(diǎn)擊‘傅里葉繪制’后在繪制過(guò)程中無(wú)法操作手繪板窗口,等待繪制完成后再次進(jìn)行手繪板操作,傅里葉繪制如需停止
% 請(qǐng)按<Ctrl>+<C>。
%
%==========================================================================================================================
%% 主程序(窗口設(shè)置)
function Fourier_Draw() %主程序,創(chuàng)建小程序窗口
%--------------------------------------------------------------------------------------------------------------------------
close all
global GUI; %全局GUI變量,方便控件交互
global Fdraw_able; %可以進(jìn)行傅里葉繪制的判斷變量
Fdraw_able = 0; %剛打開(kāi)程序,手繪板上無(wú)圖像,設(shè)置不能進(jìn)行傅里葉繪制
global axes_x axes_y axes_l; %主窗口可繪圖區(qū)域參數(shù)
global h_l; %方形主窗口邊長(zhǎng)
h_l = 400; %設(shè)置方形主窗口邊長(zhǎng)
GUI.h=figure('unit','pixels',... %窗口顯示單位為像素
'position',[100, 100, h_l, h_l,],... %窗口位置
'Name','手繪板',... %窗口標(biāo)題
'Color',[0.15 0.15 0.15],... %窗口顏色
'menubar','none',... %窗口菜單欄不顯示
'numbertitle','off',... %窗口數(shù)字標(biāo)記不顯示
'resize','off',... %窗口尺寸不可調(diào)整
'WindowButtonDownFcn',@WindowButtonDownFcn,... %鼠標(biāo)點(diǎn)擊窗口時(shí)調(diào)用WindowButtonDownFcn函數(shù)
'WindowButtonMotionFcn',@WindowButtonMotionFcn,... %鼠標(biāo)點(diǎn)擊并移動(dòng)時(shí)調(diào)用WindowButtonMotionFcn函數(shù)
'WindowButtonUpFcn',@WindowButtonUpFcn); %鼠標(biāo)松開(kāi)時(shí)調(diào)用WindowButtonUpFcn函數(shù)
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,... %文字字號(hào)
'FontWeight','bold',... %文字粗體
'callback',@StartFourier_Draw); %點(diǎn)擊按鍵時(shí)調(diào)用StartFourier_Draw函數(shù)
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); %點(diǎn)擊按鍵時(shí)調(diào)用Clean_Screen函數(shù)
GUI.popupmenu1 = uicontrol('Parent',GUI.h,...
'Style','popupmenu',... %控件類型為彈出欄
'String',['跟隨模式';'邊緣模式'],... %彈出欄的可選項(xiàng)
'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); %切換選項(xiàng)時(shí)調(diào)用Clean_Screen函數(shù)
axes_x = 0.125; %window1坐標(biāo)系原點(diǎn)x所在位置(窗口比例值)
axes_y = 0.2; %window1坐標(biāo)系原點(diǎn)y所在位置(窗口比例值)
axes_l = 0.75; %window1坐標(biāo)系長(zhǎng)和寬(窗口比例值)
GUI.window1 = axes('Parent',GUI.h,... %屬于GUI.h的坐標(biāo)系
'Position',[axes_x axes_y axes_l axes_l],...
'xtick',[],... %坐標(biāo)軸無(wú)刻度
'ytick',[],...
'xcolor','w',... %坐標(biāo)軸顏色為白色,即不可見(jiàn)
'ycolor','w');
%--------------------------------------------------------------------------------------------------------------------------
end
%% 鼠標(biāo)點(diǎn)擊窗口反饋函數(shù)
function WindowButtonDownFcn(~,~)
%--------------------------------------------------------------------------------------------------------------------------
global GUI
global axes_l h_l; %window1坐標(biāo)系長(zhǎng)和寬(窗口比例值);方形主窗口邊長(zhǎng)
set(GUI.window1,'XLim',[0,1],'YLim',[0,1]) %固定window1窗口坐標(biāo)范圍
global mode; %繪制模式參數(shù)
global draw_enable; %手繪板可繪制標(biāo)志
global x y; %存取鼠標(biāo)位置坐標(biāo)
global width; %邊緣模式手繪板畫(huà)筆粗細(xì)
global D_Path; %跟隨模式連續(xù)點(diǎn)復(fù)坐標(biāo)向量存取
global add_point; %跟隨模式線條插入點(diǎn)
global Rsl; %傅里葉繪制分辨率,用于減少邊緣模式繪制出現(xiàn)的鋸齒
Rsl = 1.67; %設(shè)置分辨率,建議在0~3之內(nèi)
D_Path = []; %清空D_Path
mode = get(GUI.popupmenu1,'Value'); %讀取彈出欄選項(xiàng),跟隨模式為1,邊緣模式為2
add_point = 5; %設(shè)置插入點(diǎn),越大存取連續(xù)點(diǎn)越多,跟隨模式繪制越慢
width = 0; %初始筆觸粗細(xì)為0
position=get(gca,'currentpoint'); %獲取最近一次鼠標(biāo)位置,gca代表當(dāng)前坐標(biāo)系
x(1)=position(1); %點(diǎn)擊位置橫坐標(biāo)
y(1)=position(3); %點(diǎn)擊位置縱坐標(biāo)
if x(1)>0 && x(1)<1 && y(1)>0 && y(1)<1 %判斷點(diǎn)擊位置是否在window1內(nèi)
draw_enable=1; %打開(kāi)繪制
if mode == 1 %跟隨模式存儲(chǔ)起點(diǎn)復(fù)坐標(biāo)
D_Path(1) = Rsl*x(1)*axes_l*h_l + Rsl*y(1)*axes_l*h_l*1i;
end
end
%--------------------------------------------------------------------------------------------------------------------------
end
%% 鼠標(biāo)點(diǎn)擊移動(dòng)反饋函數(shù)
function WindowButtonMotionFcn(~,~)
%--------------------------------------------------------------------------------------------------------------------------
global axes_l h_l; %window1坐標(biāo)系長(zhǎng)和寬(窗口比例值);方形主窗口邊長(zhǎng)
global mode; %繪制模式參數(shù)
global draw_enable; %手繪板可繪制標(biāo)志
global Fdraw_able; %傅里葉繪制可繪制標(biāo)志
global x y; %存取鼠標(biāo)位置坐標(biāo)
global width; %邊緣模式手繪板畫(huà)筆粗細(xì)
global D_Path; %跟隨模式連續(xù)點(diǎn)復(fù)坐標(biāo)向量臨時(shí)存儲(chǔ)
global add_point; %跟隨模式線條插入點(diǎn)
global Rsl; %傅里葉繪制分辨率
if draw_enable %判斷是否可以繪制
Fdraw_able = 1; %鼠標(biāo)點(diǎn)擊并移動(dòng)手繪板上就一定有圖像,設(shè)置傅里葉繪制可繪制
position=get(gca,'currentpoint'); %獲取最近一次鼠標(biāo)位置,gca代表當(dāng)前坐標(biāo)系
x(end)=position(1); %存儲(chǔ)短線條終點(diǎn)
y(end)=position(3);
if mode == 1 %如果是跟隨模式
if x(end)>0 && x(end)<1 && y(end)>0 && y(end)<1 %如果鼠標(biāo)在window1內(nèi)
x = linspace(x(1),x(end),add_point); %細(xì)分線條并插入點(diǎn)
y = linspace(y(1),y(end),add_point);
line(x,y,'LineWidth',2,'color','k'); %繪制短線條,起點(diǎn)為前一次調(diào)用WindowButtonMotionFcn的鼠標(biāo)坐標(biāo),
%終點(diǎn)為本次調(diào)用WindowButtonMotionFcn的鼠標(biāo)坐標(biāo)
for add = 2:add_point
D_Path(end+1) =Rsl*x(add)*axes_l*h_l + Rsl*y(add)*axes_l*h_l*1i; %跟隨模式存儲(chǔ)后續(xù)復(fù)坐標(biāo)
end
end
end
if mode == 2 %如果是邊緣模式
x = linspace(x(1),x(end),10); %細(xì)分線條并插入8點(diǎn)
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; %邊緣模式手繪板鼠標(biāo)快速繪制偽筆觸,繪制過(guò)快線條會(huì)變細(xì)
end
line(x,y,'LineWidth',width,'color','k','Marker','.'); %繪制短線條,起點(diǎn)為前一次調(diào)用WindowButtonMotionFcn的鼠標(biāo)坐標(biāo),
%終點(diǎn)為本次調(diào)用WindowButtonMotionFcn的鼠標(biāo)坐標(biāo)
end
x(1)=x(end); %短線條起始點(diǎn)變換為本次調(diào)用WindowButtonMotionFcn
y(1)=y(end);
end
%--------------------------------------------------------------------------------------------------------------------------
end
%% 鼠標(biāo)松開(kāi)反饋函數(shù)
function WindowButtonUpFcn(~,~)
%--------------------------------------------------------------------------------------------------------------------------
global mode; %繪制模式參數(shù)
global draw_enable; %手繪板可繪制標(biāo)志
global Follow_Lines; %跟隨模式連續(xù)點(diǎn)復(fù)坐標(biāo)向量存取結(jié)構(gòu)體
global D_Path; %跟隨模式連續(xù)點(diǎn)復(fù)坐標(biāo)向量臨時(shí)存儲(chǔ)
if draw_enable %判斷是否可以繪制
if mode == 1 %如果是跟隨模式
Follow_Lines(end+1).path = D_Path; %存儲(chǔ)這一筆繪制的線條
end
end
draw_enable = 0; %關(guān)閉繪制
%--------------------------------------------------------------------------------------------------------------------------
end
%% 清除手寫(xiě)板函數(shù)
function Clean_Screen(~,~)
%--------------------------------------------------------------------------------------------------------------------------
global GUI;
global Fdraw_able; %傅里葉繪制可繪制標(biāo)志
global Follow_Lines; %跟隨模式連續(xù)點(diǎn)復(fù)坐標(biāo)向量存取結(jié)構(gòu)體
cla(GUI.window1); %清除window1上的圖像
Fdraw_able = 0; %設(shè)置傅里葉繪制不可繪制
Follow_Lines = struct('path', {}); %清空跟隨模式存儲(chǔ)連續(xù)點(diǎn)復(fù)坐標(biāo)向量的結(jié)構(gòu)體
%--------------------------------------------------------------------------------------------------------------------------
end
%% 傅里葉繪制按鍵反饋函數(shù)
function StartFourier_Draw(~,~)
%--------------------------------------------------------------------------------------------------------------------------
global GUI;
global axes_l h_l; %window1坐標(biāo)系長(zhǎng)和寬(窗口比例值);方形主窗口邊長(zhǎng)
global Fdraw_able; %傅里葉繪制可繪制標(biāo)志
global mode; %繪制模式參數(shù)
global Follow_Lines; %跟隨模式連續(xù)點(diǎn)復(fù)坐標(biāo)向量存取結(jié)構(gòu)體
if Fdraw_able == 1 %如果可以傅里葉繪制,打開(kāi)子窗口sh
GUI.sh=figure('unit','pixels',... %窗口顯示單位為像素
'position',[100+h_l 100 600 600],... %窗口位置
'Name','傅里葉繪圖',... %窗口標(biāo)題
'Color',[0.15 0.15 0.15],... %窗口顏色
'menubar','none',... %窗口菜單欄不顯示
'numbertitle','off',... %窗口數(shù)字標(biāo)記不顯示
'resize','on',... %窗口尺寸可調(diào)整
'visible','on',... %窗口不可視
'DeleteFcn',@Clean_Screen); %窗口關(guān)閉時(shí)調(diào)用Clean_Screen函數(shù)
pause(0.01) %等待子窗口打開(kāi)
set(GUI.sh,'windowstyle','modal') %設(shè)置子窗口打開(kāi)時(shí)主窗口不可操作
pause(0.01) %等待設(shè)置完成
if mode == 1 %如果是跟隨模式
F1 = DFT_IDFT(Follow_Lines); %求解Follow_Line的繪制矩陣F1
FFT_Draw(F1); %由繪制矩陣F1進(jìn)行動(dòng)態(tài)傅里葉繪制
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); %提取轉(zhuǎn)換window1圖片的邊緣連續(xù)點(diǎn)為
%連續(xù)點(diǎn)復(fù)坐標(biāo)向量結(jié)構(gòu)體Graph_Line
F2 = DFT_IDFT(Graph_Lines); %求解Follow_Line的繪制矩陣F2
FFT_Draw(F2); %由繪制矩陣F2進(jìn)行動(dòng)態(tài)傅里葉繪制
end
end
Follow_Lines = struct('path', {}); %清空跟隨模式存儲(chǔ)連續(xù)點(diǎn)復(fù)坐標(biāo)向量的結(jié)構(gòu)體
%--------------------------------------------------------------------------------------------------------------------------
end
%% =========================================我是明顯的分割線=====================================================
% 分界線上是有關(guān)GUI設(shè)置以及控件交互的代碼,包含了少部分跟隨模式存儲(chǔ)線條的代碼,下面的代碼是數(shù)據(jù)處理與繪圖的代碼,它們是程序的
% 核心部分,程序想要展示的部分都由它們來(lái)實(shí)現(xiàn)。
%% 連續(xù)點(diǎn)提取函數(shù)(邊緣繪制模式)
function Line_Points = Pick_Point(img)
%--------------------------------------------------------------------------------------------------------------------------
global G Path; %預(yù)處理圖像矩陣;邊緣模式連續(xù)點(diǎn)復(fù)坐標(biāo)向量臨時(shí)存儲(chǔ)
global FindG; %尋找初始繪制點(diǎn)矩陣
global axes_l h_l; %window1坐標(biāo)系長(zhǎng)和寬(窗口比例值);方形主窗口邊長(zhǎng)
global msizex msizey; %圖像矩陣行數(shù)和列數(shù)
global sx sy; %初始繪制點(diǎn)坐標(biāo)
global Rsl; %傅里葉繪制分辨率
msizex = Rsl*floor(axes_l*h_l);
msizey = Rsl*floor(axes_l*h_l);
resize = imresize(img, [msizex, msizey]); %對(duì)圖像矩陣進(jìn)行縮放
grey = rgb2gray(resize); %真彩圖轉(zhuǎn)換為灰度圖
B_eg = edge(grey,'Canny'); %轉(zhuǎn)二值化圖像,邊緣為1,其他地方為0
Line_Points = struct('path', {}); %邊緣模式連續(xù)點(diǎn)復(fù)坐標(biāo)向量存取結(jié)構(gòu)體,函數(shù)輸出
G = rot90(B_eg, 3); %eg矩陣逆時(shí)針旋轉(zhuǎn)90*3度,即順時(shí)針旋轉(zhuǎn)90度,因?yàn)榫仃囅聵?biāo)與坐標(biāo)系坐標(biāo)存在一個(gè)
FindG = rot90(B_eg, 3); %順時(shí)針旋轉(zhuǎn)90度的關(guān)系,存入G和FindG中
Path = []; %Path清空
sx = 0;sy =0;
for x = 1:msizex %1值點(diǎn)掃描
for y = 1:msizey
if G(x,y) ~= 0 %找到1值點(diǎn)
Find_StartPoint(x,y); %尋找此線條的初始繪制點(diǎn)
Zchange(sx,sy); %將連續(xù)1值點(diǎn)轉(zhuǎn)化為連續(xù)點(diǎn)復(fù)坐標(biāo)向量
Line_Points(end + 1).path = Path; %存儲(chǔ)此連續(xù)線條
Path = []; %Path清空
end
end
end
% 尋找初始繪制點(diǎn)函數(shù) (*深度優(yōu)先遍歷算法*)
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; %當(dāng)前1值點(diǎn)清零,避免無(wú)限遞歸
sx = x; sy = y; %記錄當(dāng)前坐標(biāo)
for scan = 1:length(dx) %周邊點(diǎn)掃描
if (x+dx(scan)>0)&&(y+dy(scan)>0)&&(x+dx(scan)<msizex)&&(y+dy(scan)<msizey) %限制搜尋范圍在矩陣內(nèi)
if FindG(x+dx(scan),y+dy(scan)) ~= 0 %找到臨近1值點(diǎn)
Find_StartPoint(x+dx(scan),y+dy(scan)); %遞歸調(diào)用直到搜尋到此線條的終點(diǎn)
break %回溯到初始點(diǎn)后跳出循環(huán),不改變已保存的線條終點(diǎn)坐標(biāo)sx,sy
end %終點(diǎn)坐標(biāo)sx,sy便可作為繪制的起點(diǎn)坐標(biāo)
end
end
end
% 連續(xù)點(diǎn)轉(zhuǎn)換連續(xù)點(diǎn)復(fù)坐標(biāo)向量函數(shù) (*深度優(yōu)先遍歷算法*)
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; %當(dāng)前1值點(diǎn)清零,避免無(wú)限遞歸
Path(end + 1) = x + y * 1i; %輪廓點(diǎn)的坐標(biāo)用復(fù)數(shù)表示
for scan = 1:length(dx) %周邊點(diǎn)掃描
if (x+dx(scan)>0)&&(y+dy(scan)>0)&&(x+dx(scan)<msizex)&&(y+dy(scan)<msizey) %限制搜尋范圍在矩陣內(nèi)
if G(x+dx(scan),y+dy(scan)) ~= 0 %找到臨近1值點(diǎn)
Zchange(x+dx(scan),y+dy(scan)); %遞歸調(diào)用轉(zhuǎn)換周邊的連續(xù)1值點(diǎn)
break %回溯到初始點(diǎn)后跳出循環(huán),防止產(chǎn)生跳躍線
end
end
end
end
%--------------------------------------------------------------------------------------------------------------------------
end
%% 連續(xù)線轉(zhuǎn)傅里葉矩陣函數(shù)(**核心原理代碼**)
function XnSet = DFT_IDFT(line_points)
%--------------------------------------------------------------------------------------------------------------------------
XnSet = struct('Xn',{},'XnSum', {}); %用于動(dòng)態(tài)傅里葉繪制的矩陣結(jié)構(gòu)體
for serial_L = 1:length(line_points) %serial_L代表第serial_L條連續(xù)線
N = length(line_points(serial_L).path);
if N < 1000
Xk = dft(line_points(serial_L).path,N); %對(duì)連續(xù)點(diǎn)復(fù)向量做傅里葉變換
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個(gè)連續(xù)點(diǎn)
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按行求累積和得到動(dòng)態(tài)繪制指向邊緣點(diǎn)的組合向量所需的矩陣
end
% 離散傅里葉變換函數(shù) ( -提高運(yùn)算速度可以直接使用fft函數(shù),這里只為把代碼原理表述的更詳盡- )
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
%% 傅里葉動(dòng)態(tài)繪圖函數(shù)
function [] = FFT_Draw(XnSet)
%--------------------------------------------------------------------------------------------------------------------------
global axes_l h_l; %window1坐標(biāo)系長(zhǎng)和寬(窗口比例值);方形主窗口邊長(zhǎng)
global serial_color; %子圓變色繪制參數(shù);
global Rsl; %傅里葉繪制分辨率
for serial_L = 1:length(XnSet) %第serial_L條連續(xù)線
for serial_P = 1:ceil(Rsl):length(XnSet(serial_L).XnSum) %第serial_P個(gè)連續(xù)點(diǎn)
serial_color = 6; %每畫(huà)一幀子圓變色繪制參數(shù)回歸初始值,達(dá)到每個(gè)子圓的顏色一直保持不變
clf %清除serial_P-1時(shí)繪制的圖案達(dá)到動(dòng)態(tài)效果
hold on %開(kāi)啟保留繪圖痕跡
if serial_L > 1 %第serial_L條連續(xù)線繪制時(shí)保持前幾條邊緣線不消失
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); %最后一列即為原來(lái)的邊緣點(diǎn)復(fù)數(shù)坐標(biāo)值
end
end
end
plot(XnSet(serial_L).XnSum(1:serial_P,end),'-k','LineWidth',2); %繪制serial_P之前的所有邊緣點(diǎn)
if serial_L~= length(XnSet) || serial_P~= length(XnSet(length(XnSet)).XnSum) -...
mod(length(XnSet(length(XnSet)).XnSum)-1,ceil(Rsl)) %如果不是最后的線條的最后一點(diǎn)
plot(XnSet(serial_L).XnSum(serial_P,end),'k.','MarkerSize',10); %繪制組合向量筆頭點(diǎn)
for d = 2:length(XnSet(serial_L).XnSum(serial_P,:))-1
rd = length(XnSet(serial_L).XnSum(serial_P,:))+1-d; %由末端開(kāi)始繪圓使中心圓覆蓋邊緣圓
if abs(XnSet(serial_L).Xn(serial_P,rd+1)) > 2 %如果Xkn向量長(zhǎng)度大于2
Zcircle(XnSet(serial_L).XnSum(serial_P,rd),XnSet(serial_L).Xn(serial_P,rd+1)); %繪制Xkn向量旋轉(zhuǎn)子圓
end
end
if length(XnSet(serial_L).Xn(serial_P,:)) > 1 %如果線條不止一個(gè)點(diǎn)
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); %繪制最終指向邊緣點(diǎn)的組合向量
plot(XnSet(serial_L).XnSum(serial_P,1),'r.','MarkerSize',20); %繪制組合向量中心點(diǎn)
plot(XnSet(serial_L).XnSum(serial_P,1),'ro','MarkerSize',8); %繪制組合向量中心小圓
end
hold off %關(guān)閉不保留繪圖痕跡
xlim([2,Rsl*axes_l*h_l-2]);ylim([2,Rsl*axes_l*h_l-2]) %限制畫(huà)框大小不變
set(gca,'xtick',[]) %坐標(biāo)軸無(wú)刻度
set(gca,'ytick',[])
set(gca,'xcolor','w') %坐標(biāo)軸顏色為白色,即不可見(jiàn)
set(gca,'ycolor','w')
pause(0.01/(10^Rsl)) %控制繪圖速度
end
hold on %開(kāi)啟保留繪圖痕跡
if length(XnSet(end).XnSum(1,:)) > 1 %如果最后一條線不是單個(gè)點(diǎn)
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 %關(guān)閉保留繪圖痕跡
xlim([2,Rsl*axes_l*h_l-2]);ylim([2,Rsl*axes_l*h_l-2]) %限制畫(huà)框大小不變
set(gca,'xtick',[]) %坐標(biāo)軸無(wú)刻度
set(gca,'ytick',[])
set(gca,'xcolor','w') %坐標(biāo)軸顏色為白色,即不可見(jiàn)
set(gca,'ycolor','w')
end
% 紅色中心圓繪制函數(shù)
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
% 彩色子圓繪制函數(shù)
function [] = Zcircle(zo,zr)
serial_color = serial_color +1;
serial_color = mod(serial_color,7); %子圓變色繪制參數(shù)加一,使子圓有不同的顏色
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參數(shù)繪制不同顏色的圓
end
%--------------------------------------------------------------------------------------------------------------------------
end
%==========================================================================================================================
自己寫(xiě)的代碼用了一堆全局變量,很亂,后面用AI優(yōu)化了一下代碼:
%% 主程序(窗口設(shè)置)
function Fourier_Draw()
close all;
% 創(chuàng)建主窗口
h_fig = figure('unit','pixels', ...
'position',[100, 100, 400, 400], ...
'Name','手繪板', ...
'Color',[0.15 0.15 0.15], ...
'menubar','none', ...
'numbertitle','off', ...
'resize','off', ...
'WindowButtonDownFcn', @WindowButtonDownFcn, ...
'WindowButtonMotionFcn', @WindowButtonMotionFcn, ...
'WindowButtonUpFcn', @WindowButtonUpFcn);
% 初始化 handles 結(jié)構(gòu)體
handles = struct();
handles.figure = h_fig;
handles.h_l = 400; % 方形主窗口邊長(zhǎng)
handles.axes_x = 0.125;
handles.axes_y = 0.2;
handles.axes_l = 0.75;
handles.Rsl = 1.67; % 傅里葉繪制分辨率
handles.add_point = 5; % 跟隨模式插入點(diǎn)數(shù)量
handles.Fdraw_able = 0; % 是否可進(jìn)行傅里葉繪制
handles.mode = 1; % 繪制模式(1:跟隨,2:邊緣)
handles.draw_enable = 0; % 是否允許繪制
handles.x = []; % 臨時(shí)鼠標(biāo)坐標(biāo)
handles.y = [];
handles.width = 0; % 邊緣模式筆觸粗細(xì)
handles.D_Path = []; % 跟隨模式臨時(shí)點(diǎn)向量
handles.Follow_Lines = struct('path', {}); % 跟隨模式存儲(chǔ)的線條
% 創(chuàng)建坐標(biāo)系
handles.window1 = axes('Parent', h_fig, ...
'Position', [handles.axes_x, handles.axes_y, handles.axes_l, handles.axes_l], ...
'xtick', [], 'ytick', [], ...
'xcolor', 'w', 'ycolor', 'w');
% 創(chuàng)建按鈕和彈出菜單
handles.button1 = uicontrol('Parent', h_fig, ...
'Style','pushbutton', ...
'String','傅里葉繪制', ...
'Position',[handles.h_l/8, handles.h_l/16, handles.h_l/4, handles.h_l/8], ...
'FontName','黑體', 'FontSize',10, 'FontWeight','bold', ...
'callback', @StartFourier_Draw);
handles.button2 = uicontrol('Parent', h_fig, ...
'Style','pushbutton', ...
'String','清除', ...
'Position',[5*handles.h_l/8, handles.h_l/16, handles.h_l/4, handles.h_l/8], ...
'FontName','黑體', 'FontSize',10, 'FontWeight','bold', ...
'callback', @Clean_Screen);
handles.popupmenu1 = uicontrol('Parent', h_fig, ...
'Style','popupmenu', ...
'String',['跟隨模式';'邊緣模式'], ...
'Position',[33*handles.h_l/80, 9*handles.h_l/80, 7*handles.h_l/40, 3*handles.h_l/80], ...
'FontName','黑體', 'FontSize',8, ...
'callback', @Clean_Screen); % 切換模式時(shí)調(diào)用清除(同時(shí)更新模式)
% 存儲(chǔ) handles
guidata(h_fig, handles);
end
%% 鼠標(biāo)點(diǎn)擊窗口反饋函數(shù)
function WindowButtonDownFcn(hObject, ~)
handles = guidata(hObject);
set(handles.window1, 'XLim', [0,1], 'YLim', [0,1]);
position = get(handles.window1, 'CurrentPoint');
x0 = position(1,1);
y0 = position(1,2);
if x0 > 0 && x0 < 1 && y0 > 0 && y0 < 1
handles.draw_enable = 1;
handles.x = x0;
handles.y = y0;
if handles.mode == 1
handles.D_Path = handles.Rsl * x0 * handles.axes_l * handles.h_l + ...
handles.Rsl * y0 * handles.axes_l * handles.h_l * 1i;
end
guidata(hObject, handles);
end
end
%% 鼠標(biāo)點(diǎn)擊移動(dòng)反饋函數(shù)
function WindowButtonMotionFcn(hObject, ~)
handles = guidata(hObject);
if ~handles.draw_enable
return;
end
handles.Fdraw_able = 1; % 有圖像,允許傅里葉繪制
position = get(handles.window1, 'CurrentPoint');
x_end = position(1,1);
y_end = position(1,2);
if handles.mode == 1 % 跟隨模式
if x_end > 0 && x_end < 1 && y_end > 0 && y_end < 1
x_vals = linspace(handles.x, x_end, handles.add_point);
y_vals = linspace(handles.y, y_end, handles.add_point);
line(x_vals, y_vals, 'LineWidth',2, 'color','k');
for add = 2:handles.add_point
handles.D_Path(end+1) = handles.Rsl * x_vals(add) * handles.axes_l * handles.h_l + ...
handles.Rsl * y_vals(add) * handles.axes_l * handles.h_l * 1i;
end
handles.x = x_end;
handles.y = y_end;
end
elseif handles.mode == 2 % 邊緣模式
x_vals = linspace(handles.x, x_end, 10);
y_vals = linspace(handles.y, y_end, 10);
if handles.width < 4
handles.width = handles.width + 0.5;
end
if handles.width > 2 && sqrt((x_end - handles.x)^2 + (y_end - handles.y)^2) > 0.1
handles.width = handles.width - 0.7;
end
line(x_vals, y_vals, 'LineWidth', handles.width, 'color','k', 'Marker','.');
handles.x = x_end;
handles.y = y_end;
end
guidata(hObject, handles);
end
%% 鼠標(biāo)松開(kāi)反饋函數(shù)
function WindowButtonUpFcn(hObject, ~)
handles = guidata(hObject);
if handles.draw_enable && handles.mode == 1
handles.Follow_Lines(end+1).path = handles.D_Path;
end
handles.draw_enable = 0;
guidata(hObject, handles);
end
%% 清除手寫(xiě)板函數(shù)(可被按鈕或子窗口關(guān)閉觸發(fā),同時(shí)處理模式切換)
function Clean_Screen(hObject, ~)
% 判斷調(diào)用來(lái)源
if isa(hObject, 'matlab.ui.Figure')
% 子窗口關(guān)閉時(shí)觸發(fā)
mainFig = get(hObject, 'UserData');
if isempty(mainFig) || ~ishandle(mainFig)
return;
end
else
% 按鈕或彈出菜單回調(diào)
mainFig = ancestor(hObject, 'figure');
% 如果是彈出菜單,需要更新模式
if strcmp(get(hObject, 'Style'), 'popupmenu')
handles = guidata(mainFig);
handles.mode = get(hObject, 'Value'); % 更新模式
guidata(mainFig, handles);
end
end
if ~ishandle(mainFig)
return;
end
handles = guidata(mainFig);
cla(handles.window1);
handles.Fdraw_able = 0;
handles.Follow_Lines = struct('path', {});
guidata(mainFig, handles);
end
%% 傅里葉繪制按鍵反饋函數(shù)
function StartFourier_Draw(hObject, ~)
mainFig = ancestor(hObject, 'figure');
handles = guidata(mainFig);
if handles.Fdraw_able == 0
return;
end
% 創(chuàng)建子窗口
subFig = figure('unit','pixels', ...
'position', [100+handles.h_l, 100, 600, 600], ...
'Name','傅里葉繪圖', ...
'Color', [0.15 0.15 0.15], ...
'menubar','none', ...
'numbertitle','off', ...
'resize','on', ...
'visible','on', ...
'DeleteFcn', @Clean_Screen);
set(subFig, 'windowstyle', 'modal');
set(subFig, 'UserData', mainFig); % 存儲(chǔ)主窗口句柄
pause(0.01);
if handles.mode == 1 % 跟隨模式
XnSet = DFT_IDFT(handles.Follow_Lines);
FFT_Draw(XnSet, handles, subFig);
elseif handles.mode == 2 % 邊緣模式
% 截取主窗口圖像
rect = [2, 2, handles.axes_l*handles.h_l-2, handles.axes_l*handles.h_l-2];
img = getframe(handles.window1, rect);
% 提取邊緣點(diǎn)
Graph_Lines = Pick_Point(img.cdata, handles.Rsl, handles.axes_l, handles.h_l);
XnSet = DFT_IDFT(Graph_Lines);
FFT_Draw(XnSet, handles, subFig);
end
% 繪圖完成后清除跟隨模式存儲(chǔ)(子窗口關(guān)閉時(shí)也會(huì)清除)
handles.Follow_Lines = struct('path', {});
guidata(mainFig, handles);
end
%% ========================================= 輔助函數(shù) =====================================================
%% 連續(xù)點(diǎn)提取函數(shù)(邊緣繪制模式)
function Line_Points = Pick_Point(img, Rsl, axes_l, h_l)
msizex = Rsl * floor(axes_l * h_l);
msizey = msizex;
resize = imresize(img, [msizex, msizey]);
grey = rgb2gray(resize);
B_eg = edge(grey, 'Canny');
G = rot90(B_eg, 3);
FindG = rot90(B_eg, 3);
Line_Points = struct('path', {});
% 遞歸函數(shù)(嵌套,可訪問(wèn)外部變量)
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;
sx = x; sy = y;
for scan = 1:length(dx)
nx = x + dx(scan);
ny = y + dy(scan);
if nx>0 && ny>0 && nx<msizex && ny<msizey && FindG(nx,ny)~=0
Find_StartPoint(nx, ny);
break;
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;
path(end+1) = x + y*1i;
for scan = 1:length(dx)
nx = x + dx(scan);
ny = y + dy(scan);
if nx>0 && ny>0 && nx<msizex && ny<msizey && G(nx,ny)~=0
Zchange(nx, ny);
break;
end
end
end
% 掃描所有點(diǎn)
for x = 1:msizex
for y = 1:msizey
if G(x,y) ~= 0
Find_StartPoint(x, y);
path = [];
Zchange(x, y);
if ~isempty(path)
Line_Points(end+1).path = path;
end
end
end
end
end
%% 連續(xù)線轉(zhuǎn)傅里葉矩陣函數(shù)
function XnSet = DFT_IDFT(line_points)
XnSet = struct('Xn', {}, 'XnSum', {});
for serial_L = 1:length(line_points)
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:N-1]'*[0:N-1])/N) / N;
for serial_P = 1:N
Wnk(serial_P, :) = Xk .* Wnk(serial_P, :);
end
XnSet(end+1).Xn = Wnk;
XnSet(end).XnSum = cumsum(Wnk, 2);
end
end
function Xk = dft(xn, N)
xn = [xn, zeros(1, N-length(xn))];
n = 0:N-1;
k = 0:N-1;
WN = exp(-1j*2*pi/N);
WNnk = WN .^ (n' * k);
Xk = xn * WNnk;
end
%% 傅里葉動(dòng)態(tài)繪圖函數(shù)
function FFT_Draw(XnSet, handles, fig)
% 參數(shù)提取
axes_l = handles.axes_l;
h_l = handles.h_l;
Rsl = handles.Rsl;
% 預(yù)定義顏色表(7種顏色)
colorMap = [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];
for serial_L = 1:length(XnSet)
for serial_P = 1:ceil(Rsl):length(XnSet(serial_L).XnSum)
clf(fig);
hold on;
% 繪制之前已完成的線條
if serial_L > 1
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), '-k', 'LineWidth', 2);
plot([XnSet(k).XnSum(end,end), lastpoint], '-k', 'LineWidth', 2);
end
end
end
% 繪制當(dāng)前線條已完成的點(diǎn)
plot(XnSet(serial_L).XnSum(1:serial_P, end), '-k', 'LineWidth', 2);
% 如果不是最后一個(gè)點(diǎn),繪制輔助圖形
if ~(serial_L == length(XnSet) && serial_P == length(XnSet(end).XnSum) - mod(length(XnSet(end).XnSum)-1, ceil(Rsl)))
plot(XnSet(serial_L).XnSum(serial_P, end), 'k.', 'MarkerSize', 10);
% 繪制向量鏈中的各個(gè)圓,顏色根據(jù)圓在鏈中的位置固定
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
% 使用圓在鏈中的位置 rd 確定顏色(固定)
colorIdx = mod(rd, 7) + 1;
Zcircle(XnSet(serial_L).XnSum(serial_P, rd), ...
XnSet(serial_L).Xn(serial_P, rd+1), colorMap(colorIdx,:));
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));
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', [], 'ytick', [], 'xcolor', 'w', 'ycolor', 'w');
pause(0.01 / (10^Rsl));
end
% 當(dāng)前線條繪制完成,補(bǔ)充閉合線段
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;
end
end
%% 輔助繪圖函數(shù)
function Center_circle(zo, zr)
t = 0:pi/20:2*pi;
circle = zo + abs(zr) * exp(1j*t);
plot(circle, 'r', 'LineWidth', 0.8);
end
function Zcircle(zo, zr, color)
t = 0:pi/20:2*pi;
circle = zo + abs(zr) * exp(1j*t);
plot(circle, 'Color', color, 'LineWidth', 0.6);
end
3、一些現(xiàn)象
【跟隨模式】會(huì)把你畫(huà)圖的速度也記錄下來(lái)(因?yàn)榇a的邏輯就是直接記錄你鼠標(biāo)的坐標(biāo)),按照和你畫(huà)圖速度同比例繪制,不是一模一樣的速度但是哪里快哪里慢是按比例來(lái)的,就類似于錄制。
-
【邊緣模式】是使用算法提取圖像邊緣然后繪制的,輸入的是二維圖片取連續(xù)像素點(diǎn),繪制線條就是勻速的,觀感要好一些。
動(dòng)起來(lái)就是這樣:
-
【跟隨模式】自己把線條首尾畫(huà)閉合(起筆處與終筆處相連)就還可以:
不相連畫(huà)面就會(huì)出現(xiàn):

動(dòng)起來(lái)是這樣:

看起來(lái)不太好看,一條條直線像是連成一個(gè)光滑的曲線了,至于原因,或許可以代入一組樣本數(shù)據(jù)到公式中,看看有什么貓膩。
4、參考鏈接
- 鴨嘎嘎.手把手教你用傅立葉變換畫(huà)可達(dá)鴨.
https://zhuanlan.zhihu.com/p/72632360 - 打浦橋程序員.一把王者榮耀的時(shí)間,讓你學(xué)會(huì)MATLAB GUI.
https://zhuanlan.zhihu.com/p/150792841 - 荼荼灰.matlab 傅里葉畫(huà)任何圖.
https://blog.csdn.net/qq_36553572/article/details/107131750 - Rustle.數(shù)字圖像處理:邊緣檢測(cè)(Edge detection).
https://zhuanlan.zhihu.com/p/59640437 - 打個(gè)醬油_就走.MATLAB GUI設(shè)計(jì)手寫(xiě)輸入板.
https://blog.csdn.net/hardrious/article/details/49226149


