長時間の信号処理を行う場合、信号を細かくフレームに区切り、さらにフレームを重ねながら信号を抽出するオーバーラップという手法があります。過去にPythonで実装したこのオーバーラップ処理を、今回はiOSアプリで動作させるためにSwiftで実装する方法と検証結果を紹介します。
こんにちは。wat(@watlablog)です。今回は言語変換回なのでサクッといきますが、Swiftでオーバーラップ処理を実装します!
Pythonで実装したオーバーラップ処理のおさらい
当WATLABブログでは2019年にPythonによるオーバーラップ処理コードを紹介しました。こちらの記事にオーバーラップ処理の概要やアルゴリズムを図解していますので是非ご覧ください。
オーバーラップ処理の図と式を再度示します。今回はこの処理をSwiftで実装します。
Swiftでオーバーラップ処理を実装するコード
オーバーラップ処理の原理等はPython記事に記載したので、今回は簡単にSwiftコードのみを紹介します。以下のコードのfunc ov{}がオーバーラップ処理部分です。
できるだけPythonコードの書き方とそろえてみたので、Pythonユーザーは是非見比べてみてください。
オーバーラップ抽出した波形はChartに全てプロットしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
import SwiftUI import Charts import Accelerate struct PointsData: Identifiable { // 点群データの構造体 var xValue: Float var yValue: Float var id = UUID() } struct ContentView: View { // データを定義 @State private var data: [PointsData] = [] @State private var x: [Float] = [] @State private var y: [Float] = [] @State private var showingAlert = false func linspace(start: Float, stop: Float, num: Int) -> [Float] { // データ点数を指定して配列を作成する関数 let increment = (stop - start) / Float(num - 1) return (0..<num).map { Float($0) * increment + start } } func wavePoints(N: Int, length: Float) -> (xArray: [Float], yArray: [Float]){ // プロットするデータを演算する関数 // データ点数Nと時間長lengthからx軸を作成 let xArray = linspace(start: 0.0, stop: length, num: N) // 正弦波を作成。A:振幅、f:周波数[Hz]、dc:直流成分 let A: Float = 1 let f: Float = 1 let dc: Float = 1 let yArray = xArray.map{ A * sin(2 * .pi * f * $0) + dc } return (xArray, yArray) } func ov(data: [Float], samplerate: Float, Fs: Int, overlap: Float) -> ([[Float]], Int) { // オーバーラップ処理 let Ts = Float(data.count) / samplerate let Fc = Float(Fs) / samplerate let x_ol = Float(Fs) * (1 - (overlap/100)) let N_ave = Int((Ts - (Fc * (overlap/100))) / (Fc * (1-(overlap/100)))) var array = [[Float]]() for i in 0..<N_ave { let ps = Int(x_ol * Float(i)) array.append(Array(data[ps..<ps+Fs])) } return (array, N_ave) } var body: some View { // UI VStack{ Chart { // データ構造からx, y値を取得してプロット ForEach(data) { shape in PointMark( x: .value("x", shape.xValue), y: .value("y", shape.yValue) ) } } .padding(.all, 10) Button("Button"){ // ボタンイベント // 関数を実行してx, y演算結果を取得 let result = wavePoints(N: 100, length: 1) let x = result.xArray let y = result.yArray let dt = x[1] let samplerate = 1 / dt // オーバーラップ関数を呼び出す let Fs = 25 let (overlapData, _) = ov(data: y, samplerate: samplerate, Fs: Fs, overlap: 0.0) // プロットデータの全削除 data.removeAll() // プロットデータの追加 for i in 0..<overlapData.count { let frameData = overlapData[i] let frameX = linspace(start: 0.0, stop: Float(Fs), num: Fs) data.append(contentsOf: zip(frameX, frameData).map { PointsData(xValue: $0, yValue: $1) }) } } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { // プレビュー ContentView() } } |
実行結果①:Fs=25, overlap=0
上記コードのサンプル信号はデータ点数が100です。まずはオーバーラップ率overlap=0[%]でフレームサイズFs=25と設定して(上記コードはデフォルトでこの設定です)、信号を1/4ずつ抽出してみましょう。
こちらが結果です。元信号はFs=100の場合ですが、正弦波が綺麗に1/4ずつ分割されて抽出できています。
実行結果②:Fs=50, overlap=25
続いてFs=50、overlap=25の結果です。全信号が100に対してフレームサイズが50なので、オーバーラップ25[%]だとしても2フレーム抽出するのが限界です。
こちらが実行結果です。元信号と比べてみても正確に抽出できているようですね。
まとめ
今回はショートTips記事ですが、SwiftでもPythonと同様にオーバーラップ処理を実装することができました。
アプリとして成立させるのは別問題ですが、今は材料をそろえている段階です。
次は窓関数や平均化処理を実装してみたいと思います。
Swiftでオーバーラップ処理を実装できました!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!