ハイライト
Apple は、iOS 14 で Core Image 前処理、Vision 輪郭検出、ピクセル単位のオプティカル フローを同じ画像パイプラインに接続し、アプリが画像のエッジ、パンチカードの形状、ビデオ フレームの動きをデバイス上で直接分析できるようにしました。
主要内容
多くのアプリはコンピューター ビジョンに重点を置いていませんが、特定のステップでコンピューター ビジョンを必要とします。バンキング アプリでは小切手の写真を撮影でき、レシート アプリでは顔情報を読み取ることができ、QR コード スキャン機能では手入力を省略できます。ユーザーに表示されるのは、入力する行が数行減り、開発者は画像の前処理、分析リクエスト、結果の視覚化、およびパフォーマンスのコストに直面することになります。
このセッションでは、Vision、Core Image、Core ML を個別のフレームワークとして紹介しません。まず、Vision の前後に Core Image を配置します。最初に、入力画像のスケーリング、ノイズ除去、しきい値処理、およびコントラストの調整を行うことができます。 Vision の出力は、描画のために Core Image または Core Graphics に返すこともできます。この目的は明らかです。システムの最適化されたパイプラインに画像を保持し、マトリックス変換と追加のメモリ オーバーヘッドを削減することです。
主人公には 2 つの新しい能力があります。 1 つ目は Vision の輪郭検出で、次の結果を返します。VNContoursObservationそして横断可能VNContourレベル、エッジ、穴、外側の輪郭、内側の輪郭を見つけるのに適しています。 2 つ目はオプティカル フローで、2 つのフレーム間の各ピクセルの X/Y 変位を分析します。画像全体の全体的な移動を単に推定するのではなく、ビデオ画像内の局所的なオブジェクトの動きを観察するのに適しています。
セッションでは、パンチカードの例を使用してこのパスをクリアします。元の画像を直接検出すると、387 個の輪郭が得られます。 Core Image のブラーとカラー コントロールを追加すると、結果は 32 個のアウトラインに減りました。これは、探していた穴に正確に一致しました。この変更は API 名よりも重要です。開発者はビジネス知識を利用して、まず入力をサニタイズし、次に Vision に分析を実行させることができます。
詳細
1. まず Core Image を使用してビジョン入力を改善します (02:29)
Core Image はここで 2 種類の作業を行います。最初のカテゴリは前処理です。画像のスケーリング、形態学的処理、グレースケール変換、ノイズ低減、エッジ検出、コントラスト強調、およびしきい値処理です。セッションは明示的に名前が付けられていますCILanczosScale、CIMorphologyRectangleMaximum、CIMorphologyRectangleMinimum、CIColorMatrix、CIMedianFilter、CIGaborGradients、CIColorThresholdそしてCIColorThresholdOtsu。
2 番目のカテゴリは後処理です。 Vision がバーコード観察、顔観察、またはベクトル フィールドを返した後、Core Image は結果を画像に再描画できます。カラー スペースも Core Image によって処理されます。入力はその作業スペースに入り、特別なカラー スペースが必要な場合は、matchedFromWorkingSpaceそしてmatchedToWorkingSpace独自のアルゴリズムをラップします。
キーポイント:
CIColorThresholdアプリに独自のしきい値を設定させます。CIColorThresholdOtsuヒストグラムに基づいてしきい値を自動的に見つけます。CIColorAbsoluteDifferenceそしてCILabDeltaE2 つの画像を比較するために使用され、後続の動き検出プロセスに入るのに適しています。- 画像が最初に残ります
CIImageパイプラインでは、画像を行列に変換する際のメモリと計算コストを回避するために、画像が Vision に渡されます。
2. Vision を使用して輪郭を見つけ、その結果を画像に描画します (19:24)
輪郭検出のリクエストはVNDetectContoursRequest。リクエストが実行された後の結果は次のようになります。VNContoursObservation。それは持っていますtopLevelContours、contourCountそしてnormalizedPath。シングルVNContour子輪郭、インデックス パス、正規化ポイント、ポイント数、アスペクト比、正規化パスも付属します。
この公式のプレイグラウンド コードは、完全なプロセスを示しています。パンチ カード イメージを読み取り、輪郭リクエストを作成し、次を使用します。VNImageRequestHandlerVision リクエストを実行してから、normalizedPath元の画像にオーバーレイを戻します。
import UIKit
import CoreImage
import CoreImage.CIFilterBuiltins
import Vision
public func drawContours(contoursObservation: VNContoursObservation, sourceImage: CGImage) -> UIImage {
let size = CGSize(width: sourceImage.width, height: sourceImage.height)
let renderer = UIGraphicsImageRenderer(size: size)
let renderedImage = renderer.image { (context) in
let renderingContext = context.cgContext
// flip the context
let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: size.height)
renderingContext.concatenate(flipVertical)
// draw the original image
renderingContext.draw(sourceImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
renderingContext.scaleBy(x: size.width, y: size.height)
renderingContext.setLineWidth(3.0 / CGFloat(size.width))
let redUIColor = UIColor.red
renderingContext.setStrokeColor(redUIColor.cgColor)
renderingContext.addPath(contoursObservation.normalizedPath)
renderingContext.strokePath()
}
return renderedImage;
}
let context = CIContext()
if let sourceImage = UIImage.init(named: "punchCard.jpg")
{
var inputImage = CIImage.init(cgImage: sourceImage.cgImage!)
let contourRequest = VNDetectContoursRequest.init()
// Uncomment the follwing section to preprocess the image
// do {
// let noiseReductionFilter = CIFilter.gaussianBlur()
// noiseReductionFilter.radius = 1.5
// noiseReductionFilter.inputImage = inputImage
//
// let monochromeFilter = CIFilter.colorControls()
// monochromeFilter.inputImage = noiseReductionFilter.outputImage!
// monochromeFilter.contrast = 20.0
// monochromeFilter.brightness = 8
// monochromeFilter.saturation = 50
//
// let filteredImage = monochromeFilter.outputImage!
//
// inputImage = filteredImage
// }
let requestHandler = VNImageRequestHandler.init(ciImage: inputImage, options: [:])
try requestHandler.perform([contourRequest])
let contoursObservation = contourRequest.results?.first as! VNContoursObservation
print(contoursObservation.contourCount)
_ = drawContours(contoursObservation: contoursObservation, sourceImage: sourceImage.cgImage!)
} else {
print("could not load image")
}
キーポイント:
VNDetectContoursRequest.init()この例では Vision タスクであり、入力画像は次から取得されます。CIImage。- コメントアウトされました
gaussianBlurそしてcolorControlsコアイメージの前処理です。デモでこのセクションを開いた後、等高線の数は 387 から 32 に減少しました。 VNImageRequestHandler.init(ciImage: inputImage, options: [:])Vision が Core Image 出力を直接消費できるようにします。contoursObservation.normalizedPathレンダリング可能ですCGPath、コードはソース画像のサイズに合わせて拡大縮小し、元の画像上に描画します。- ビジョンは正規化された座標を使用します。セッションは、1920x1080 の図の円で示されています。幾何学的な判断を行う際には、元の画像のアスペクト比を考慮する必要があります。
3. エッジ ピクセルだけでなく、アウトラインの形状を分析する (12:51)
VNContoursObservation階層構造は、外側と内側の輪郭を記述するのに適しています。セッション内の 2 つのボックスと 2 つの円の例では、ボックスは最上位の輪郭であり、円は子輪郭です。開発者はインデックス パスを使用して、この等高線マップを横断できます。
輪郭検出の結果はピクセルの束ではありません。VNContour線分パスを説明する正規化されたポイントとポイント数を提供します。ビジョンは次のサービスも提供していますVNGeometryUtils、境界円、面積、周囲長を計算できます。カメラの揺れによって生じた小さなポリラインを削除する必要がある場合は、ポリゴン近似のイプシロンを使用して、ノイズの多い四角形を 4 つの角の点に単純化できます。
キーポイント:
maximumImageDimensionパフォーマンスと精度を追求したノブです。低解像度ではより高速に実行され、高解像度では輪郭がエッジに近づきます。- アスペクト比は幾何学的な判断に影響します。正規化された座標では、1920x1080 の画像では、高さ 1.0 の円の幅は 0.5625 になります。
- イプシロンは、後から四角形、円、または穴を判断できるように、現実世界のわずかに湾曲した境界を主要なコーナー ポイントに押し込むのに適しています。
4. オプティカル フローを使用して、2 つのフレーム間のローカル モーションを確認します (21:23)
従来の画像位置合わせでは、画像全体の位置合わせが行われます。カメラが全体的に上に移動すると、画像全体がどれだけ移動したかがわかります。オプティカル フローはさらに細かく、各ピクセルの X/Y 変位を返します。 2 つの点が互いに離れている場合、画像全体のレジストレーションではこの変化を表現することが困難ですが、オプティカル フローでは局所的な動きを保存できます。
ビジョンの結果は、VNPixelBufferObservation、浮動小数点画像とインターリーブされた X/Y 移動が含まれています。この結果は後続のアルゴリズム処理に適したものであり、数値を見ても直接理解することは困難です。したがって、セッションは Core Image カスタム フィルターを使用して、変位を色と矢印に変換します。
var requestHandler = VNSequenceRequestHandler()
var previousImage:CIImage?
if (self.previousImage == nil)
{
self.previousImage = request.sourceImage
}
let visionRequest = VNGenerateOpticalFlowRequest(targetedCIImage: source, options: [:])
do {
try self.requestHandler.perform([visionRequest], on: self.previousImage!)
if let pixelBufferObservation = visionRequest.results?.first as? VNPixelBufferObservation
{
source = CIImage(cvImageBuffer: pixelBufferObservation.pixelBuffer)
}
} catch {
print(error)
}
// store the previous image
self.previousImage = request.sourceImage
let ciFilter = OpticalFlowVisualizerFilter()
ciFilter.inputImage = source
let output = ciFilter.outputImage
キーポイント:
VNSequenceRequestHandlerオプティカル フローは現在のフレームと前のフレームを比較するため、クロスフレーム リクエストに使用されます。VNGenerateOpticalFlowRequest(targetedCIImage: source, options: [:])オプティカル フロー リクエストを生成します。perform([visionRequest], on: self.previousImage!)前のフレームを処理要求に渡します。VNPixelBufferObservationピクセルバッファは次のようにパックされます。CIImage、コアイメージの視覚化ステップに進みます。self.previousImage = request.sourceImage現在のフレームを保存し、次のリクエストに備えて参照イメージを準備します。
5. Core Image フィルターを使用してオプティカル フローを視覚化します (23:26)
Vision によって与えられるオプティカル フロー値は、アルゴリズムへの入力としてバイアスされます。デバッグと表示のためにセッションが追加されますOpticalFlowVisualizerFilter。 AppバンドルからロードされますOpticalFlowVisualizer.ci.metallib、という名前のコールflowView2カーネルは、X/Y の動きを色の濃淡と方向矢印にマッピングします。
class OpticalFlowVisualizerFilter: CIFilter {
var inputImage: CIImage?
let callback: CIKernelROICallback = {
(index, rect) in
return rect
}
static var kernel: CIKernel = { () -> CIKernel in
let url = Bundle.main.url(forResource: "OpticalFlowVisualizer",
withExtension: "ci.metallib")!
let data = try! Data(contentsOf: url)
return try! CIKernel(functionName: "flowView2",
fromMetalLibraryData: data)
}()
override var outputImage : CIImage? {
get {
guard let input = inputImage else {return nil}
return OpticalFlowVisualizerFilter.kernel.apply(extent: input.extent, roiCallback: callback, arguments: [input, 0.0, 100.0, 10.0, 30.0])
}
}
}
キーポイント:
Bundle.main.url(forResource:withExtension:)スライドの添付ファイルで提供される Core Image カーネル アーティファクトを読み込みます。CIKernel(functionName: "flowView2", fromMetalLibraryData: data)カスタム カーネルをバインドします。kernel.applyのパラメータには、入力画像、最小/最大長、矢印ユニット サイズ、矢印角度が含まれます。- このステップは、Core Image が Vision の前に入力をクリーンアップし、Vision の後に結果をユーザーまたは開発者が理解できる画像に変換できることを示しています。
重要ポイント
- 紙の作業指示書の穴位置の読み取り: 対処方法: アプリにパンチ カード、機器ラベル、または固定形式の作業指示書の写真を撮影させ、穴の位置を構造化された記録に変換させます。実行する価値がある理由: セッションのパンチカード デモは、Core Image の前処理によって等高線の数を 387 から 32 に収束できることを証明します。
CIImage、最初に使用しますgaussianBlurそしてcolorControls背景を消去して再実行するVNDetectContoursRequest、使用contourCountそしてnormalizedPath結果を確認してください。 - ビデオ移動方向オーバーレイ: 機能: スポーツ トレーニング アプリやビデオ編集アプリでフレームの一部の移動方向を表示します。実行する価値がある理由:
VNGenerateOpticalFlowRequestピクセル単位の X/Y 変位を返します。これにより、ローカル オブジェクトの離れた動きを表現できます。開始方法: 使用するVNSequenceRequestHandler前のフレームを保存して、VNPixelBufferObservationパッケージ化されたCIImageをクリックし、カスタム Core Image フィルターにアクセスします。 - 画像分析前の品質チェック パネル: 対処方法: 内部ツールにプレビュー パネルを追加し、しきい値処理、ノイズ低減、輪郭の数、および最終的なオーバーレイを表示します。実行する価値がある理由: Session は、前処理が Vision の速度と安定性に影響を与えることを繰り返し強調してきました。開始方法: 置く
CIColorThresholdOtsu、CIMedianFilter、CIColorControls調整可能なステップとして、次のように表示されます。VNContoursObservation.contourCount。 - 形状欠陥の検出: 内容: パッケージング、ビルのエッジ、部品の輪郭が予想された形状から逸脱していないかどうかを確認します。実行する価値がある理由:
VNGeometryUtils面積、周囲長、境界円を計算でき、イプシロン多角形近似によりノイズ エッジを主要なコーナー ポイントに単純化できます。開始方法: まず輪郭を検出し、次にアスペクト比、ポイント数、および領域範囲によって異常な形状を除外します。 - Vision Result Debug View: 対処方法: 開発者モードをカメラ アプリに追加し、Vision 出力を画面に直接描画します。やる価値がある理由: セッション
drawContoursとオプティカル フロー ビジュアライザはどちらも視覚化を使用してパラメータ調整のフィードバックを短縮します。始め方:コンターウォークnormalizedPathオーバーレイ、光が流れ去るVNPixelBufferObservationコアイメージフィルターに。
関連セッション
- ビデオ アプリの Core Image パイプラインを最適化する — 引き続きビデオ シーンを確認します
CIContext多重化、メタル コマンド キュー、およびコア イメージのパフォーマンス。 - Xcode を使用してメタルベースのコア イメージ カーネルを構築する — このオプティカル フロー ビジュアライザーの背景を深く掘り下げます
.ci.metalカーネルの構築とロードのプロセス。 - Core Image のデバッグ技術を発見する — Core Image グラフを生成し、色、メモリ、レンダリングの問題を特定する方法を学びます。
- Vision による身体と手のポーズの検出 — このセッションで説明した Vision 2020 の人体のポーズと手のポーズの検出機能に接続されています。
- Action & Vision アプリを探索する — Vision、Create ML、Core ML がどのように統合されて完全なモーション分析アプリになるかをご覧ください。
コメント
GitHub Issues · utterances