作者:フェノール
0x01 序文
iOS 16 システムは、驚くべきデスクトップ ロック画面効果をもたらしました: 深度効果です。通常の画像を背景として使用できますが、同時に、適切な場所でいくつかのデスクトップ コンポーネントを覆い、被写界深度効果を形成することもできます (下の図を参照)。
では、私たち自身のアプリで同様の効果を達成できるでしょうか? 最初は、iOS 16 で新しい UIKit コントロールが追加されたのではないかと考えました。UIVisualEffectView
これは、のような数行の単純なAPIで実装できますが、最終的には存在しないことがわかりました。与えられた画像がレイヤーに分割された複数の画像である場合、実装はサンドイッチ ビスケットのように中央にクロック コントロールを挟むだけです。ただし、実際には、インターネットからランダムにダウンロードした 1 つの画像をロック画面の背景として設定しても、この効果が得られることがわかっています。もう一度押すと、写真の被写体を直接分割してドラッグできる iOS 16 システム アルバムを思い起こさせますが、何らかの画像分割アルゴリズムを使用して前景と背景を分離し、多層画像を取得したに違いないと思います。
0x02 画像セグメンテーション (画像セグメンテーション)
より古典的な画像セグメンテーション アルゴリズムは、ウォーターシェッドアルゴリズム (Watershed) です。セグメント化された画像は非常に正確で、エッジ処理も非常に優れていますが、前景と背景のおおよその位置に手動でストロークする必要があります (1 つのストロークのみで問題ありません)。 )、後者のアルゴリズムは前景と背景を自動的に分離します)、これはこのペーパーの完全に自動化された要件には適用されません。近年、機械学習で多くの成果が生まれていますが、その 1 つが完全に自動化された画像セグメンテーションです。案の定、簡単な検索の後、Apple が事前トレーニング済みのモデルを提供していることがわかりました。
Apple の機械学習公式 Web サイトdeveloper.apple.com/machine-lea...にアクセスして、トレーニング済みモデルDeeplabV3をダウンロードします。モデル ファイルを Xcode プロジェクトにドラッグすると、選択後にいくつかの情報を表示できます。
実際、ここでは主にモデルの入力と出力に焦点を当てています. [予測] タブをクリックすると、モデルが 513x513 の画像の入力を必要とし、出力が Int32 のメンバー型を持つ 2 次元配列であることがわかりますサイズは 513x513. 各値は、対応する画像ピクセルの点の分類を表します。ここでメンバーが単純な Bool ではなく Int32 である理由は、モデルが画像を前景と背景だけでなく、さまざまな部分に分割できるためです。実際には、0 の値は背景と見なすことができ、0 以外の値は前景と見なすことができます。
以下は、サンプル画像でセグメンテーションを実行した後に得られた結果です。
それぞれ背景と前景である0と15の2つの値に分けられます。
0x03 練習
モデルはすでに存在し、実装計画はほぼ準備ができており、次のステップは具体的な実践です。
モデルを Xcode プロジェクトにドラッグすると、Xcode によってクラスが自動的に生成されます: DeepLabV3. インポートなしでインスタンスを直接作成できます。
lazy var model = try! DeepLabV3(configuration: {
let config = MLModelConfiguration()
config.allowLowPrecisionAccumulationOnGPU = true
config.computeUnits = .cpuAndNeuralEngine
return config
}())
复制代码
次に、このインスタンスを使用して、VNCoreMLRequest
機械学習エンジンを介して画像を分析するリクエストを作成し、コールバックで結果を取得します。
lazy var request = VNCoreMLRequest(model: try! VNCoreMLModel(for: model.model)) { [unowned self] request, error in
if let results = request.results as? [VNCoreMLFeatureValueObservation] {
// 最终的分割结果在 arrayValue 中
if let feature = results.first?.featureValue, let arrayValue = feature.multiArrayValue {
let width = arrayValue.shape[0].intValue
let height = arrayValue.shape[1].intValue
let stride = arrayValue.strides[0].intValue
// ...
}
}
}
复制代码
最後に、適切な場所にリクエストVNImageRequestHandler
を作成します。
private func segment() {
if let image = self.imageView.image {
imageSize = image.size
DispatchQueue.global().async { [unowned self] in
self.request.imageCropAndScaleOption = .scaleFill
let handler = VNImageRequestHandler(cgImage: image.resize(to: .init(width: 513, height: 513)).cgImage!)
try? handler.perform([self.request])
}
}
}
复制代码
知らせ:
- リクエストのコールバックとハンドラーがリクエストを開始するコードは同じスレッドにあり、同期的に結果を待っているため、ここでサブスレッド操作にディスパッチするのが最善です
- リクエストは imageCropAndScaleOption を に設定する必要があります
.scallFill
。そうしないと、デフォルトで中央部分が自動的にトリミングされ、予期しない結果が得られます。
次のサンプル画像を入力してください。
返された結果を白黒画像にarrayValue
処理します。
かなり正確であることがわかった。もちろん、コード内でマスクとして使用する場合は、完全に透明な背景と不透明な前景を持つ画像として扱う必要があります。
最後に、元の画像を一番下のレイヤーに配置し、他のコントロールを中央に配置し、元の画像とマスク ビューを一番上のレイヤーに配置して、最終的な効果を形成します。
その背後にある実際の原理は、サンドイッチ ビスケットです。
さらにいくつかのレンダリング:
0x04 追記
もちろん、このモデルは万能ではなく、特定のアプリケーションでの制限はまだあります. 人物が写っている写真の場合は、より適切に分割できますが、大きなシーンのような風景写真の場合は、まったく分割できない場合があります. この記事のデモはGithubにあります。
参考文献
この記事は NetEase Cloud Music Technology Team によって発行されたものであり、許可なく転載することは禁じられています。技術系の職種は年中無休で募集しておりますので、クラウドミュージックが好きな方で転職をお考えの方はgrp.music-fe(at)corp.netease.comまでご連絡ください!