1.8K Views
November 03, 24
スライド概要
Kanagawa.swift #1 での発表資料です。
https://kanagawaswift.connpass.com/event/332163/
フリーランスiOSエンジニア 「エンジニアと人生」コミュニティ主宰
動画の無音部分を自動で カットする 〜 MLと波形解析のハイブリッド音声処理 〜
自己紹介 • 堤 修一 • @shu223 (GitHub, Zenn, Qiita, note, Docswell, 𝕏, YouTube, Podcast, etc...) • 書籍(商業出版4冊、個人出版多数 @BOOTH):
最近書いた本 エンジニアのための「労働集 約からの脱却」入門 zenn.dev/shu223/books/ monetization
Kanagawaとの関係 今年2月に鎌倉に移住 • 10年前にも3年ほど住んでいた(小 町・雪ノ下) • 材木座 • 駅まで徒歩23分 • 最寄りのコンビニまで徒歩15分 • 海まで徒歩4分
FAQ: 鎌倉って観光で行くイメージしかないけど住むってどうなの? A: 最高 • 海も山も街もある • 散歩してるだけで楽しい • 観光客多いのは小町・由 比ヶ浜・長谷など一部 • 材木座は由比ヶ浜より落ち 着いていて40代後半の子育 て世代にはちょうどいい • 都心よりは安い • 2LDK + 作業用1K → 一 軒家になって家賃は4割 減 • 地方よりは東京に出やすい
本題
話すこと 動画の無音部分を自動でカットする処理のiOSでの実装方法
無音部分のカット? • 人が話している部分「以外」をカットしたい • いわゆるジェットカット
さっそくデモ
Chopper AIで人の声を抽出して動画の無音を自動 でカットするアプリ • 2021年リリース • 2023年に有料化 • そこそこ売れている
Before After 300秒 145秒(52%カット)
Before After 34秒 26秒(24%カット)
動画の無音カット処理全体の流れ 入力 → 音声抽出 → 発話区間検出 → 入力動画の発話区間以外をカット → 出力
動画の無音カット処理全体の流れ 入力 → 音声抽出 → 発話区間検出 → 入力動画の発話区間以外をカット → 出力
「発話区間検出」をどう実現するか?
「音のありなし」で判定すればいい?
→ 周りがガヤガヤしてたり車の音が入 ってたりしてるだけでもう発話と区別で きない
→ MLで「人の声」を判定する
Sound Analysisフレームワーク • MLベースの音声分類 • iOS 13 / WWDC19 で登場 • 当時書いた記事: SoundAnalysis + Create MLで話者認識 - Qiita
音声分類(Sound Classification)
実装 // アナライザを初期化 let audioFileAnalyzer = try SNAudioFileAnalyzer(url: url) // MLModelオブジェクトを渡してリクエストを作成 let request = try SNClassifySoundRequest(mlModel: mlmodel) // リクエストをアナライザに追加 try audioFileAnalyzer.add(request, withObserver: self) // 解析開始 audioFileAnalyzer.analyze()
デモ: Sound Analysisを用 いた話者認識 • WWDC19のキャッチアップイベント 用につくったデモ • 人物ごとの音声を学習したモデルを Create MLで作成
Sound Analysisを用いた発話区間検出 ver. 1 • Human / Background の音 声分類を行うカスタム Sound Classificationモデル を使用 • 分類性能はそこそこ
Sound Analysisを用いた発話区間検出 ver. 2 • iOS 15 / WWDC21でビルトイン(システム組み込み)の音声 分類器が利用可能に(それまではモデルを自作するしかなかった) • 約300種類の音声を判別可能 • その中に "speech" も • 分類性能は以前のカスタムモデルと比較して大幅に向上 • 解説記事: Sound Analysisのビルトイン音声分類器
実装方法 以前とほぼ同じ let audioFileAnalyzer = try SNAudioFileAnalyzer(url: url) // ここだけが新しい let request = try SNClassifySoundRequest(classifierIdentifier: .version1) try audioFileAnalyzer.add(request, withObserver: self) audioFileAnalyzer.analyze()
Chopperにおける発話区間検出の細かい話 1 "speech" 以外にも、「人の発話」として検出したいクラスを拾 っている • 笑い声系 "laughter", "belly_laugh", "giggling", ... • 表現系 ”singing”, "humming", "whispering", "breathing", ... etc...
Chopperにおける発話区間検出の細かい話 2 1位にspeechが来てたら発話区間、みたいな単純な話でもな く、精度向上のため秘伝のタレ的なロジック調整を色々と行っ ている • 順位関係なく、発話クラスのConfidence(確信度)ベースで 判定している • Confidenceのヒストグラムから、カットレベルLow, Mid, Highの閾値を動的に決定 • 発話が断片的にならないよう、連続性も見ている
However
MLは完璧じゃない 普通に無音なところも検出できないことがある
MLは完璧じゃない 普通に無音なところも検出できないことがある → 波形処理ベースの無音区間判定も併用
iOSにおける音声波形処理 • 音声はAVFoudationで扱う(AVAudioPCMBuffer) • 音声波形処理 デジタル信号処理には Accelerateフレームワ ークの vDSP を使用する 解説記事: AVAudioPCMBufferの取り扱いメモ|shu223
RMS(root mean square, 二乗平均平方根) • 音圧を表す指標 • 単位時間ごとに計測され た音量を計算する
RMS計算の実装
絶対値の平均を計算してもいいが、Accelerateフレームワークの
vDSP_measqv 関数を利用して一行で書ける
rms
static func rms(data: UnsafeMutablePointer<Float>, frameLength: UInt) -> Float {
var val : Float = 0
vDSP_measqv(data, 1, &val, frameLength)
}
rms
return val
公式ドキュメントに記載されている vDSP_measqv 関数の計算式は、 C[0] = sum(A[n] * A[n], 0 <= n < N) /
N; ・・・つまり出力される値は二乗されたものであって、最後に平方根を取る計算はこの関数では行われない。
RMSを用いてどのように無音カットするか? Logic Proと同等のパラメータ構成で実装(単純な閾値処理ではない) public struct Configuration: Equatable, Hashable { public init() {} /// 閾値(デシベル) public var threshold: Int = ... /// 無音として扱う最低限の時間 public var minDuration: Double = ... /// カットし始める部分の長さに余裕を持たせる public var preAttackTime: Double = ... /// カットし終わる部分の長さに余裕を持たせる } public var postReleaseTime: Double = ...
まとめ 弊アプリでは、動画の自動無音カットのため以下2つの方法をハ イブリッドで使用: • MLベースの発話区間検出 • Sound Analysis のビルトイン音声分類器を利用 • 音声波形処理ベースの無音区間検出 • AVFoundation / Accelerate を利用
Chopper - 動画の無音部分を自動でカット apps.apple.com/jp/app/id1560947520 ⭐⭐⭐⭐⭐ いただけると大変うれしいです