あぐちゃんさんの物置き場

オタク、ブレボを溶かしがち

フーリエ変換の実用例でたまに見るアレ

プログラミングを習ってからというもの、数々のゴミのようなソースコードを量産して遊んできたわけですが、それらのガラクタの中にはそれなりに気に入っている物もあったりするわけです。雑にコードを書いてポイ捨てするときは、だいたい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形式で保存するプログラムがこれ。起動すると、お絵描きをすることができ、上のボタンを押すとデータを保存できます。
ドットを打つタイミングで座標データを回収してるみたいです。どこかのサイトからプログラムをコピペして、改造して作った記憶があります。データの保存の仕方があまりにも雑なので、このようなコードはあんまりおすすめしません。(絵を描くやつはとても助かりました。コピペ元へ感謝を。)

f:id:Agchan_Luice:20210415231742p:plain
絵を描く画面はこんな感じ
フーリエ変換して周波数に分解、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製に落ち着いたのでしょうか、当時のことはまるで覚えていません。でもちゃんと今でも動きます。すごい。(率直)

f:id:Agchan_Luice:20210415231955p:plainf:id:Agchan_Luice:20210415232118p:plain
一筆書きじゃないとき
f:id:Agchan_Luice:20210415233224p:plainf:id:Agchan_Luice:20210415233227p:plain
一筆書きの時

一筆書きだと元の絵がかなり再現できますが、一度でもマウスのクリック離したりすると座標に不連続な部分が発生するため、その部分が高周波成分がクソデカになるので、めちゃくちゃわかりやすく発振します(ギブス現象って言うらしい)。

書きたかったことは書いたのでこの辺で。

ロボコンで作ったやつ

作ったもの思い出そうの一つ目。

とりあえず、ロボコン部としての活動で作ったものを置いておこうと思ったけど、1年と2年の時に作ったものは写真が見つからないのでパス。あ、でも2年の冬に移動用のライブラリ作ったっけ。これは動画がありますね。

 

カチカチ言ってるのは当時のリレーモタドラです。

 プログラムの仕組みは秘密です。卒業したら原理の解説だけはするかも。

 

次。

 

3年の時のロボコンは2020年度。

https://www.cemedine.co.jp/cemedine_reports/kousenrobocon2020-kyusyuokinawa.html

ここに載ってる久留米のマシンの両方のプログラムをそれぞれ一部書きました。

Aチームはパソコンのペンタブのペンの位置と書いてる/書いてないをマシンに送信するプログラムを、Bチームは首の制御とマシン全体の制御と回路を担当しました。

首の制御の動画ありました。確かarcsinあたりで近似して動かしてたはず。

 本編はロボコンの映像がyoutubeに公式から上がってるはずなのでそっち見てください。

 

表立ってロボコンでやったことといえばこのくらいでしょうか。

飽きたのでおわり。

はつかきこ

自分用の掃き溜めです。読んでもためにならないと思います。たぶん。

何やってきたか自分でもよくわかんなくなってきたので、物置き場として活用します。めんどくさいので適当にしか書きません。

これまで作ってきた物のうち、記憶と記録があるものを一通り掘り出したら、進行中のお遊びを置いていく予定。気分が乗ったらちまちま書きます。

f:id:Agchan_Luice:20210413224216j:plain

これは飯テロです。