aptpod Advent Calendar 2023 12月13日を担当するintdashグループの落合です。
みなさんはNVHってご存知ですか?僕は全く知らなかったのですが、
- Noise(騒音)
- Vibration(振動)
- Harshness(ハーシュネス:路面の凹凸による突き上げや、立て付けの悪さから来るガタピシ感など)
の頭文字で、自動車の快適性を推し量る上での一つの基準だそうです(WiKiより)。
この意味を知った時、弊社のintdashを使うと簡単に測定できるかもと思ったので、この記事ではintdashでNVHを簡易的に測定してみます。
やること
Noise、Vibration、Harshnessのデータを弊社のintdashに送ることで、データを可視化して確認してみます。
NoiseとVibrationについては、弊社のEDGEPLANT T1と専用LinuxディストリビューションであるTerminal System 2を利用しました。Terminal System 2ではEDGEPLANT T1に内蔵されている加速度センサーやマイクジャックを使う機能があらかじめ入っているため簡単に音声と加速度を測定できるので、今回は簡易的この値をNoiseとVibrationとして測定します1。
Harshnessは、Vibrationの値から計算します。測定したVibrationの値からHarshnessを計算しintdash保存するには、弊社のintdash SDK for Pythonというクライアントライブラリを利用しました。
最後に、intdashに上がっているデータはVisual M2M Data Visualizerで可視化して確認してみます。
NoiseとVibrationの測定
NoiseとVibrationの測定は、EDGEPLANT T1と専用LinuxディストリビューションであるTerminal System 2を利用します。
データを取得するための設定はドキュメントに記載してあるので割愛させていただき、ここでは使用したデバイスと取れるデータについて説明します。
Noiseの測定
今回は簡易的にデータが取れれば良いので、マイクはサンワサプライ マイク MM-MC32とサンワサプライ 変換アダプタ KM-A25-005を利用しました。
今回測定では車の運転席と助手席の間の床部分に養生テープで固定しています。
次に、データをとって確認してみます。
確認に使用するVisual M2M Data Visualizerには、あらかじめTerminal System 2で測定した音声に適したデータ設定(Audio)があるので、それをインポートして使用して、スペクトグラムで表示します。
スペクトグラムからもカーナビの声、ウインカーの音がわかる状況になっていました。音声を再生するとエンジン音もちゃんと撮れているので問題なさそうです。
Vibrationの測定
こちらも簡易的に取得可能な、EDGEPLANT T1に搭載されている u-blox社のNEO-M8Uを使用します。このモジュールはGPSの位置情報以外にも加速度センサーの情報も取得できるので、今回はこの情報を使用します(ただ、キャリブレーションについてちょっとクセがあるので、その点は付録としてこのブログの最後に記載しておきます)。
設置はマイク同様に運転席と助手席の間の床部分に養生テープで固定しています。EDGEPLANT T1は車進行方向左側にLANのポートが来るように設置します。
それでは、データをとって確認してみます。
こちらもVisual M2M Data Visualizerに、あらかじめTerminal System 2で測定したGPS/加速度情報などに適したデータ設定(UBX)があるので、それをインポートして使用して、ライングラフで表示します。
値はそれぞれ以下のようになります。
- xAccel:車正面への加速度
- yAccel:車側面(右方向)への加速度
- zAccel:車下面への加速度
こちらも必要なデータは取れていそうです。
Harshnessの算出
最後にHarshnessを算出です。
流れとしては、
- 測定済みのVibrationのデータを取得
- Vibrationの値からHarshnessを算出
- Harshnessの値をintdashに保存
となります。
1.と3.の手順は、intdash SDK for Pythonのドキュメントにチュートリアルとしてそれぞれ記載されています。
そのため、ここでは 2. についてのみ詳細を説明します。
Harshnessの計算方法ですが、僕が調べた限りでは、具体的にこうという計算方法の定義は見つけられませんでした。 そのため今回は「路面の凹凸による突き上げ」と限定して、単純な計算式でやってみようと思います。
路面の凹凸に乗り上げた場合にどうなるかを想像すると、上方向への加速度と後ろ方向の加速度が発生しそうです。そこで「上方向への加速度(マイナス方向のxAccel)」+「後ろ方向の加速度(マイナス方向のzAccel)」 = Harshness として簡易的に計算してみます。
具体的には、逆方向の成分は無視したり、重力加速度を考慮する必要があるので以下の計算式で測定してみます。
min(0, xAccel) + max(0, zAccel + 9.8) = Harshness
(後述しますがこの仮説モデルは適切ではないです)
上記仕様で作成したpythonのコード2は付録に記載しておきます。
サーバーのアドレス、APIトークン、対象の計測UUIDを指定して実行すると、Harshnessを計算しサーバーに保存します。
# ./upload-harshness.py $SERVER $TOKEN $MEASUREMENT_UUID ...省略... 2023-12-09 23:26:55.502140890+00:00 x_accel -0.21 z_accel -9.79 harshness -0.21 2023-12-09 23:26:55.692299440+00:00 x_accel -0.27 z_accel -9.89 harshness -0.36 2023-12-09 23:26:55.909226218+00:00 x_accel -0.22 z_accel -9.81 harshness -0.23 2023-12-09 23:26:56.100891502+00:00 x_accel -0.19 z_accel -9.82 harshness -0.21
実車でとってみる
それではデータを測定して確認してみましょう。
今回は私用でレンタカーを借りて移動する機会があったので、そこで測定してみました。 車はダイハツ・ムーヴです
走った場所
道路上にある段差(段差①)と、橋のつなぎ目(段差②)を走行してみました。
測定結果(NoiseとVibration)
段差①、段差②ともに、音声も加速度センサーも段差によって発生した値が出ています。
また、今回の説明では省略しましたが、y軸に対する角加速度 y AngRate も段差で顕著に反応しているようです。
算出結果(Harshness)
上記データに対してpythonのコードを実行してHarshnessもあわせて表示していみます。
一応、段差で反応していますが、段差以外のブレーキをかけたところでも同程度に反応してしまってますね。。。
これは仮説のモデルが適切ではなかったということになります。
仮説では、
「後ろ方向の加速度(マイナス方向のxAccel)」+「上方向への加速度(マイナス方向のzAccel)」 = Harshness
としたため、段差に乗り上げると、「後ろ方向の加速度」と「上方向の加速度」に同等に影響があるという想定になっていたのですが、
実際には「上方向の加速度」への影響がほとんどのようです。
また、測定データの結果から、段差に乗り上げた際には
- 衝突音は声や電子音に比べて、広い周波数で音が発生する
- y軸方向の角加速度が発生する
といった傾向が見られているので、この辺りも利用するとHarshnessの算出に役立ちそうです。
今回はここまでしか行いませんが、一度測定したデータはサーバーに上がっているので、Harshnessの計算式を変えて新たにデータを上げることももちろん可能です。
まとめ
Noise、Vibration、Harshnessのデータを弊社のintdashに送ることで、データを可視化しました。
簡易的なセンサーで Noise、Vibrationのデータ測定を行いましたが、段差に応じたデータを測定できました。
Harshnessの算出には仮説のモデルが適切ではなかったため、期待とは異なる結果でした。
ただ、intdashを利用してデータを集め・解析を行い・モデルを検証するという作業を、簡単に構築できるということは示せているのではないかと思います。
今回は、音声とGPS(加速度センサー込み)しか取得していませんが、弊社のシステムではあわせて、動画、CAN、アナログデータなども同時に簡単に取得できる仕組みも入っていますので、これらのデータもHarshnessの算出に利用していただくことも可能です。
付録
加速度センサーのキャリブレーション
NEO-M8Uの加速度センサーは、キャリブレーションが行われた後でないと値は出力されません。
キャリブレーションはGPSを捕捉してから、左右に曲がることでキャリブレーションが完了するようです。
u-blox 8 / u-blox M8 Receiver description Including protocol specificationには以下のように記載があります。
Drive curves and straight segments during a few minutes by including a few stops lasting at least 30 seconds each. This drive should also include some periods with higher speed (at least 50 km/h) and can typically be carried out on normal open-sky roads with good GNSS signal reception conditions.
僕の感覚ですと、GPSを捕捉した状態で、狭い以下のように、右折→右折→右折→左折や左折→左折→左折→右折という動きを行うとキャリブレーションされやすいようです。
一度キャリブレーションが完了した後ですが、EDGEPLANT T1の場合は電源ケーブルを抜かなければ、IG Off 状態でも保持されており、次回 IG On 時はキャリブレーションは不要になります。
Harshnessの計算に使用したコード
本文中に記載してありますが、このHarshnessの仮説モデルは適切ではないので、使用する場合はモデルとなる式の部分を適宜変更してください。
#!/usr/bin/env python3 # coding: utf-8 import argparse import struct import intdash from intdash import timeutils def harshness_generator(x_accel, z_accel): # 前後方向加速度 x_accel と 上下方向加速度 z_accel からハーシュネスを計算する # x_accel, z_accel は 100倍された値が入ってくる # 前後方向は後方向の値のみ使用する x_accel_positive = -1 * min(x_accel, 0) # 上下方向は上方向の値のみ使用し、重力加速度成分を除去する z_accel_positive = max(980 + z_accel, 0) # ハーシュネスは 後方向加速度 * 上方向加速度とする。倍率も補正する。 return (x_accel_positive + z_accel_positive) / 100 def generate_and_upload(measurement, data_points, generator): it = data_points.list( measurement_uuid=measurement.uuid, # 計測の全てのデータポイントを取得したいので、時間はによる取得制限はしない start=timeutils.str2timestamp("2000-01-01T00:00:00.000000Z"), end=timeutils.str2timestamp("2100-01-01T00:00:00.000000Z"), id_queries=[ intdash.IdQuery( data_type=intdash.DataType.bytes, data_id="UBX-HNR-INS", ) ], iterator=True, ) # 取得したデータポイントのペイロードは、iSCP v1のデータ型 bytes のフォーマットになっている # # 7 6 5 4 3 2 1 0 # +---+---+---+---+---+---+---+---+ # 000 | ID Length (11) | # +---+---+---+---+---+---+---+---+ - - - - # 001 | ID (U) | ^ # + + | ID Length # | (B) | | # + + | # | (X) | | # + + | # | (-) | | # + + | # | (H) | | # + + | # | (N) | | # + + | # | (R) | | # + + | # | (-) | | # + + | # | (I) | | # + + | # | (N) | | # + + | # | (S) | v # +---+---+---+---+---+---+---+---+ - - - - # 012 | Data | # + + # : : # # データ型 bytes のDataの部分には、u-blox M8 のUBX-HNR-INSのバイナリデータが入っている # (u-blox M8 に仕様書 https://content.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_UBX-13003221.pdf) # # 7 6 5 4 3 2 1 0 # +---+---+---+---+---+---+---+---+ # 012 | Header (0xB5) | # + + # | (0x62) | # +---+---+---+---+---+---+---+---+ # 014 | Class (0x28) | # +---+---+---+---+---+---+---+---+ # 015 | ID (0x28) | # +---+---+---+---+---+---+---+---+ # 016 | Length (36) | # + + # | (0) | # +---+---+---+---+---+---+---+---+ # 018 | bitfield0 | # + + # | | # + + # | | # + + # | | # + + # +---+---+---+---+---+---+---+---+ # 022 | reserved1 | # + + # | | # + + # | | # + + # | | # + + # +---+---+---+---+---+---+---+---+ # 026 | iTOW | # + + # | | # + + # | | # + + # | | # + + # +---+---+---+---+---+---+---+---+ # 030 | xAngRate [Scaling 1e-3] | # + + # | | # + + # | | # + + # | | # + + # +---+---+---+---+---+---+---+---+ # 034 | yAngRate [Scaling 1e-3] | # + + # | | # + + # | | # + + # | | # + + # +---+---+---+---+---+---+---+---+ # 038 | zAngRate [Scaling 1e-3] | # + + # | | # + + # | | # + + # | | # + + # +---+---+---+---+---+---+---+---+ # 042 | xAccel [Scaling 1e-3] | # + + # | | # + + # | | # + + # | | # + + # +---+---+---+---+---+---+---+---+ # 046 | yAccel [Scaling 1e-3] | # + + # | | # + + # | | # + + # | | # + + # +---+---+---+---+---+---+---+---+ # 050 | zAccel [Scaling 1e-3] | # + + # | | # + + # | | # + + # | | # + + # +---+---+---+---+---+---+---+---+ # 054 | Checksum | # + + # | | # +---+---+---+---+---+---+---+---+ # # 上記バイナリデータをstructで定義し、 # xAngRate, yAngRate, zAngRate, xAccel, yAccel, zAccel が取り出せるようにする ubx_hnr_ins = struct.Struct("<xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxiiiiiixx") harshness_dps = [] for lst in it: for dp in lst: _, _, _, x_accel, _, z_accel = ubx_hnr_ins.unpack(dp.data_payload) # if z_accel >= 0: # continue # 加速度データからharshnessを計算 harshness = generator(x_accel, z_accel) # 値をログ print( dp.time, "x_accel", x_accel / 100, "z_accel", z_accel / 100, "harshness", harshness, ) # intdash Serverへアップロードするのデータポイントを作成 harshness_dps.append( intdash.DataPoint( elapsed_time=dp.time - measurement.basetime, data_type=intdash.DataType.float, channel=1, data_payload=intdash.data.Float( data_id="Harshness", value=harshness ).to_payload(), ) ) # intdash Serverへアップロード data_points.store( measurement_uuid=measurement.uuid, data_points=harshness_dps, ) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Generate and upload harshness") parser.add_argument( "server", type=str, help="Server URL (e.g. https://xxxx.intdash.jp)", ) parser.add_argument( "token", type=str, help="API token", ) parser.add_argument( "measurement_uuid", type=str, help="Measurement UUID", ) args = parser.parse_args() print(args) client = intdash.Client(url=args.server, edge_token=args.token) measurement = client.measurements.get(uuid=args.measurement_uuid) data_points = client.data_points generate_and_upload(measurement, data_points, harshness_generator)
- device-connector-intdash v2.0.0 以降を利用することでEDGEPLANT T1に内蔵されているIMUを簡単に使用できます。Terminal System 2ではdevice-connector-intdash v2.0.0以降がインストールされています。↩
- データポイントをパースするには、バイナリがどのようなフォーマットなのかを確認する必要があります。今回のようにVisual M2M Data Visualizerにあらかじめデータ設定の定義がある場合は、そちらの内容を確認することで必要な値を取得するためのフォーマットを知ることができます。↩