フーリエ変換の実用例でたまに見るアレ
プログラミングを習ってからというもの、数々のゴミのようなソースコードを量産して遊んできたわけですが、それらのガラクタの中にはそれなりに気に入っている物もあったりするわけです。雑にコードを書いてポイ捨てするときは、だいたいpythonを使います。今回はまず一つ目。
フーリエ変換で絵を周波数へ
これはおそらく去年に作ったものです。なんでも周波数に分解したくなる病気に一時期かかっていたので、その時に作りました。ソースコードは2つ。
データを生成するプログラム
from ctypes import windll, Structure, c_long, byref import time from datetime import datetime import tkinter from PIL import Image, ImageDraw import copy import json datalist = [] posbuffer = [] print("Enter new data file name.") fname = input() class Application(tkinter.Frame): def __init__(self, master=None): super().__init__(master) self.master = master self.master.title('tkinter canvas trial') self.pack() self.create_widgets() self.setup() def create_widgets(self): self.vr = tkinter.IntVar() self.vr.set(1) self.write_radio = tkinter.Radiobutton(self, text='write', variable=self.vr, value=1, command=self.change_radio) self.write_radio.grid(row=0, column=0) self.erase_radio = tkinter.Radiobutton(self, text='erase', variable=self.vr, value=2, command=self.change_radio) self.erase_radio.grid(row=0, column=1) self.clear_button = tkinter.Button(self, text='clear all and output data.', command=self.clear_canvas) self.clear_button.grid(row=0, column=2) self.save_button = tkinter.Button(self, text='save', command=self.save_canvas) self.save_button.grid(row=0, column=3) self.test_canvas = tkinter.Canvas(self, bg='white', width=700, height=700) self.test_canvas.grid(row=1, column=0, columnspan=4) self.test_canvas.bind('<B1-Motion>', self.paint) self.test_canvas.bind('<ButtonRelease-1>', self.reset) def setup(self): self.old_x = None self.old_y = None self.color = 'black' self.eraser_on = False self.im = Image.new('RGB', (700, 700), 'white') self.draw = ImageDraw.Draw(self.im) def change_radio(self): if self.vr.get() == 1: self.eraser_on = False else: self.eraser_on = True def clear_canvas(self): self.test_canvas.delete(tkinter.ALL) f = open('data\\' + fname + '.json',"w") if f == None: print('Write failed.') exit() bf = {} i = 0 j = 0 for s in datalist: for p in s: bf[str(i) + ':' + str(j)] = p j += 1 j = 0 i += 1 bf['length'] = i - 1 output = json.dumps(bf) f.write(output) f.close() exit() def save_canvas(self): self.test_canvas.postscript(file='out.ps', colormode='color') def paint(self, event): if self.eraser_on: paint_color = 'white' else: paint_color = 'black' if self.old_x and self.old_y: self.test_canvas.create_line(self.old_x, self.old_y, event.x, event.y, width=5.0, fill=paint_color, capstyle=tkinter.ROUND, smooth=tkinter.TRUE, splinesteps=36) self.draw.line((self.old_x, self.old_y, event.x, event.y), fill=paint_color, width=5) self.old_x = event.x self.old_y = event.y posbuffer.append([self.old_x,self.old_y]) def reset(self, event): self.old_x, self.old_y = None, None tmp = copy.deepcopy(posbuffer) datalist.append(tmp) posbuffer.clear() root = tkinter.Tk() app = Application(master=root) app.mainloop()
tkinterとPIL等を使って、線を描いたらそれをソースコードと同じ階層のdataというフォルダにjson形式で保存するプログラムがこれ。起動すると、お絵描きをすることができ、上のボタンを押すとデータを保存できます。
ドットを打つタイミングで座標データを回収してるみたいです。どこかのサイトからプログラムをコピペして、改造して作った記憶があります。データの保存の仕方があまりにも雑なので、このようなコードはあんまりおすすめしません。(絵を描くやつはとても助かりました。コピペ元へ感謝を。)
フーリエ変換して周波数に分解、sinとcosで戻す
import json import copy import cmath as cm import numpy as np import tkinter as tk import matplotlib.pyplot as plt import matplotlib.animation as animation import matplotlib.patches as patches Picture_size = 700 def dft(data): #合ってんの?これ ret = [] n = len(data) for k in range(0,n): w = cm.exp(-1j * cm.pi * 2.0 * k / float(n)) s = 0 for i in range(0,n): s += data[i] * (w ** i) / n * 2 ret.append(s) return ret #json読み取り print('Enter read data name') c = input() try: f = open('data\\' + c + '.json','r') except: print('Failed to open the file.') exit() print("File loading.....") rawdata = f.read() f.close() data = json.loads(rawdata) pos = [] i = 0 j = 0 length = data['length'] breakflag = False print('Converting data.....') while True: while True: try: pos.append(data[str(i) + ':' + str(j)]) except: if(length < i):breakflag = True if breakflag:break i += 1 j = 0 break j += 1 if breakflag:break #変換 x_ = [] y_ = [] for s in pos: x,y = s[0] - Picture_size // 2,Picture_size // 2 - s[1] x_.append(x) y_.append(y) n = len(x_) #LPF lpf_rate = 1 lpx = [0] * (n - lpf_rate) lpy = [0] * (n - lpf_rate) for i in range(0,n - lpf_rate): for j in range(0,lpf_rate): lpx[i] += x_[i + j] lpy[i] += y_[i + j] lpx[i] /= lpf_rate lpy[i] /= lpf_rate print("LPF rate is",lpf_rate,".") N = n - lpf_rate print("Number of data is",N) print('Fourier transrating with DFT......') #DFTで変換 Fx = np.fft.fft(lpx) Fy = np.fft.fft(lpy) print("Fourier transrating was successfully.") print("Getting frequency list......") #周波数リストを取得 x_freq = [] y_freq = [] for i in range(0,N // 2 - 1): x_freq.append(i + 1) y_freq.append(i + 1) print("Data format") #有効な範囲だけにぎゅむっと Fx = Fx[1:Fx.size // 2] Fy = Fy[1:Fy.size // 2] print("Restorationing with cos and sin function....") #周波数から復元 div = 300 N = Fx.size x = [0] * div y = [0] * div #実部 * cos + |虚部| * sin sin_table = [] cos_table = [] resol = 1600 for i in range(0,resol): sin_table.append(cm.sin(2 * cm.pi * i / resol)) cos_table.append(cm.cos(2 * cm.pi * i / resol)) def fsin(t:int): if t > 0: return sin_table[int(t) % 1600] else: return -sin_table[int(abs(t)) % 1600] def fcos(t:int): return cos_table[int(abs(t)) % 1600] for i in range(0,N): for j in range(0,div): #x[j] += (Fx[i].real * cm.cos(x_freq[i] * 2 * cm.pi * (1 - j / div)) + int(Fx[i].imag) * cm.sin(x_freq[i] * 2 * cm.pi * (1 - j / div))) / N #y[j] += (Fy[i].real * cm.cos(y_freq[i] * 2 * cm.pi * (1 - j / div)) + int(Fy[i].imag) * cm.sin(y_freq[i] * 2 * cm.pi * (1 - j / div))) / N x[j] += (Fx[i].real * fcos(x_freq[i] * resol * (1 - j / div)) + (Fx[i].imag * fsin(x_freq[i] * resol * (1 - j / div)))) / N y[j] += (Fy[i].real * fcos(y_freq[i] * resol * (1 - j / div)) + (Fy[i].imag * fsin(y_freq[i] * resol * (1 - j / div)))) / N #描画 fig = plt.figure() ims = [] plt.axes().set_aspect('equal') for i in range(0,div): #グラフ描画 im = plt.plot(x[0:i],y[0:i],'b') ims.append(im) ani = animation.ArtistAnimation(fig,ims,interval = 50) plt.show() #plt.plot(x,y) #plt.show()
まず、データを読み込んだらx座標とy座標でそれぞれフーリエ変換を実施して、周波数成分に分解します。計算結果の実部がcos(偶関数)成分で、虚部がsin(奇関数)成分を構成しているので、それぞれ分けて計算した後に足して出力データにつなげていき、Plot上に出力しているようです。ふつうは各周波数のゲインを調べたいので、この複素数の絶対値を取るのが一般的です。リアルタイムの音声データをぶち込んで変換し、絶対値を取って周波数でプロットしてやると、高い音とか低い音とかで伸びる場所が違うバランみたいなメーターを再現できます(なんていうんだアレ)。
numpyのFFT機能を使っています(dftって表示してるけどおそらくデータ数をうまいこと調整してFFTしているはず)。フーリエ変換(FFT、DFT、STFTなど、特に音声解析)に特化したライブラリはどうやらたくさんあるようで、コピペだけでリアルタイム音声の変換ができるやつもあります。librosaとか結構よかったです(これのスペクトログラムを表示する機能を使って、特定の音のパターンに反応する女児向け玩具を騙すためにその周波数を解析して、マイコンとスピーカーを使って騙す遊びをしたけど動画取り損ねた)。最初にdft関数を定義していますが、めちゃくちゃ遅かったような気がします。結局numpy製に落ち着いたのでしょうか、当時のことはまるで覚えていません。でもちゃんと今でも動きます。すごい。(率直)
一筆書きだと元の絵がかなり再現できますが、一度でもマウスのクリック離したりすると座標に不連続な部分が発生するため、その部分が高周波成分がクソデカになるので、めちゃくちゃわかりやすく発振します(ギブス現象って言うらしい)。
書きたかったことは書いたのでこの辺で。
ロボコンで作ったやつ
作ったもの思い出そうの一つ目。
とりあえず、ロボコン部としての活動で作ったものを置いておこうと思ったけど、1年と2年の時に作ったものは写真が見つからないのでパス。あ、でも2年の冬に移動用のライブラリ作ったっけ。これは動画がありますね。
カチカチ言ってるのは当時のリレーモタドラです。
たのしい pic.twitter.com/CgClx47RCv
— あぐちゃんさん❄️ (@Agchan_Luice) 2019年12月27日
プログラムの仕組みは秘密です。卒業したら原理の解説だけはするかも。
次。
3年の時のロボコンは2020年度。
https://www.cemedine.co.jp/cemedine_reports/kousenrobocon2020-kyusyuokinawa.html
ここに載ってる久留米のマシンの両方のプログラムをそれぞれ一部書きました。
Aチームはパソコンのペンタブのペンの位置と書いてる/書いてないをマシンに送信するプログラムを、Bチームは首の制御とマシン全体の制御と回路を担当しました。
首の制御の動画ありました。確かarcsinあたりで近似して動かしてたはず。
#ロボコン#久留米高専
— あぐちゃんさん❄️ (@Agchan_Luice) 2020年11月2日
ロールとピッチは軸ごとにモーターがあるんじゃなくて背中から2つのサーボで動かしてたりします
(この動画だとヨー軸がドリフトしてますが) pic.twitter.com/xOGnMUutrJ
本編はロボコンの映像がyoutubeに公式から上がってるはずなのでそっち見てください。
表立ってロボコンでやったことといえばこのくらいでしょうか。
飽きたのでおわり。
はつかきこ
自分用の掃き溜めです。読んでもためにならないと思います。たぶん。
何やってきたか自分でもよくわかんなくなってきたので、物置き場として活用します。めんどくさいので適当にしか書きません。
これまで作ってきた物のうち、記憶と記録があるものを一通り掘り出したら、進行中のお遊びを置いていく予定。気分が乗ったらちまちま書きます。
これは飯テロです。