ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Computer Vision APIの探求
Core Image、Vision、およびCore MLを組み合わせることで、AppにComputer Vision のインテリジェンスを導入する方法について学びます。機械学習だけにとどまらず、画像や動画の理解を深めましょう。Core ImageおよびVisionにおける新しいAPIを見つけ、新しいしきい値フィルター、輪郭検出(Contour Detection)、フレーム間予測(Optical Flow)などのAppにComputer Visionを取り入れます。また、Core Imageを使用して、これらの結果の前処理と可視化を行う方法を検討します。 基盤となるフレームワークについては、"Vision Framework: Building on Core ML" および "Core Image: Performance, Prototyping, and Python" を参照してください。Computer Vision APIについてさらに詳しく知るには、"Detect Body and Hand Pose with Vision" および "Explore the Action & Vision app"セッションも必ずチェックしてください。
リソース
関連ビデオ
WWDC21
WWDC20
WWDC19
-
ダウンロード
こんにちは WWDCへようこそ
“Computer Vision APIの探求” WWDCへようこそ 私フランク・ドープケと 同僚のデイビッド・ヘイワードが 今日はコンピュータビジョンAPIに関して ご説明します
実はコンピュータビジョンは アプリケーションを強化することができます またビジネスの中心でない場合でも コンピュータビジョンは実際に アプリケーションに新しいものをもたらします
例を挙げてみましょう
銀行アプリケーションでは 小切手を入金することができます 小切手を実際に読み取るために カメラのコンピュータビジョンを使用すると もはや情報を入力する必要はありません コンピュータビジョンが 銀行業界の中心にないことは明らかです しかし コンピュータビジョンを使用することで ユーザーは多くの手順を省けます ユーザーは もはや 何も入力する必要はありません
他にも例えば QRコードを読み取る場合や 領収書を読み取る場合があります これは アプリケーションで 必ず実行したいことではないかもしれませんが カメラを使用することにより ユーザーにとって非常に簡単になります それでは コンピュータビジョンで 利用できるAPIは何でしょうか
最上位レベルには VisionKitがあります これは実際にドキュメントをスキャンするために メモ メッセージまたはメールで確認した― VNDocumentCameraのホームです 次に Core Imageを使用して 実際に画像処理を行い Visionを使用して画像を分析し 最後に Core MLを使って機械学習の推論を行います 本日は Core ImageとVisionに焦点を当てます これらのAPIは ただ並んで立っている 柱のようなものではなく 実際にはうまく結びついています 画像の前処理を行い それをVisionに実行して結果を取得し その結果をCore MLに入力したり Core Imageに戻って エフェクトを作成したりすることができます Core Imageを使ってコンピュータビジョンの 画像を前処理する方法については 同僚のデイビッド・ヘイワードがご説明します
ありがとう この機会に Core Imageを使用して コンピュータビジョンアルゴリズムを 向上させる方法をご説明します
Core ImageはMetal上に構築され 最適化された使いやすい 画像処理フレームワークです 詳細については このテーマに関する WWDC 2017のプレゼンをご覧ください
アプリケーションがVisionと共にCore Imageを 使用するべき主な理由は2つあります
Core Imageを使用して Visionへの入力を前処理することで アルゴリズムをより速く より堅牢にすることができます
Core Imageを使用して Visionからの出力を後処理することで これらの結果をユーザーに表示する 新しい方法をアプリケーションに提供できます
また Core Imageは 機械学習トレーニングを増やす優れたツールです 2018年のWWDCのプレゼンテーションでは このすばらしい例をいくつか紹介しています
分析用に画像を処理する最良の方法の1つは 最高のパフォーマンスを得るために 画像を縮小することです 総合的に最高の品質を持つスケーラーは CILanczosScaleです
このフィルタは コード内で非常に簡単に使用できます まずは CIFilterBuiltinsヘッダーを インポートします その後 フィルタインスタンスを作成し 入力プロパティを設定して outputImageを取得するだけです
しかし これはCore Imageのいくつかの リサンプリングフィルタの1つにすぎません アルゴリズムによっては 線形補間CIAffineTransformを 使用する方がよい場合があります
モルフォロジー演算は 画像内の小さな特徴を より目立たせるための優れた手法です
CIMorphologyRectangleMaximumを使用して ダイレートを実行すると 画像の明るい領域が大きくなります
CIMorphologyRectangleMinimumを使用して エロードを実行すると 明るい領域が小さくなります
さらによいのは CIMorphologyRectangleMinimumに続いて CIMorphologyRectangleMaximumを使用して Closeを実行することです これは アルゴリズムに 影響を与える可能性のある小さなノイズ領域を 画像から削除する場合に非常に便利です
一部のアルゴリズムでは モノクロ入力のみが必要です この場合 Visionは 自動的にRGBをグレースケールに変換します 入力画像に関するドメインの知識がある場合は Core Imageを使用してグレーに変換すると よりよい結果が得られる可能性があります
CIColorMatrixを使用すると この変換に必要な任意の重みを指定できます
または CIMaximumComponentを使用すると 最大信号のチャネルが使用できます
画像解析前のノイズ低減も 考慮する必要があります
CIMedianFilterを2回通すと エッジを 柔らかくすることなくノイズを低減できます
CIGaussianBlurおよびCIBoxBlurも ノイズを低減するための高速な方法です
また CINoiseReductionフィルタの使用も 検討してください
Core Imageには さまざまなエッジ検出フィルタもあります
Sobelエッジ検出では CIConvolution3X3を使用できます
さらに優れているのは CIGaborGradientsを使用することです これにより ノイズに対する耐性が高い 2Dグラデーションベクトルが生成されます
画像のコントラストを強調すると オブジェクトの検出に役立ちます
CIColorPolynomialでは 任意の3次コントラスト関数を指定できます CIColorControlsは 線形コントラストパラメータを提供します
今年は Core Imageには画像を白黒に変換できる 新しいフィルタもいくつか用意されています
例えば CIColorThresholdを使用すると アプリケーションコードで しきい値を設定できます CIColorThresholdOtsuは 画像のヒストグラムに基づいて 最適な“しきい値”を自動的に決定します
Core Imageには2つの画像を 比較するためのフィルタもあります このフィルタは ビデオのフレーム間の動きを 検出するための処理に役立ちます
例えば CIColorAbsoluteDifferenceは この問題を解決するための 今年の新しいフィルタです
また 人間の色に対する知覚に合わせて 設計された数式を使用して CILabDeltaEは2つの画像を比較します
これらは Core Imageに組み込まれている― 200以上のフィルタのうちの 単なるサンプリングです
これらの組み込みフィルタを 使用しやすくするために このドキュメントには パラメータの説明 サンプル画像 そしてサンプルコードが含まれています
これらのフィルタがニーズに合わない場合は CoreImage Metalを使用して 簡単に独自のフィルタを作成できます そして 今年も利用可能にしたセッションを ご覧になることをお勧めします
画像処理とコンピュータビジョンにより 画像をさまざまなカラースペースに入れることが 可能だという点に注意してください
アプリケーションは― 従来のsRGBからワイドガモットP3 さらに現在対応しているHDRカラースペースまで さまざまなスペースで 画像を受け取る可能性があります
アプリケーションは こうした多様なカラースペースに 対応できるように準備しておく必要があり Core Imageを使用すると この作業が非常に簡単になります Core Imageは自動的に入力を機能している スペースに変換します 非クランプ リニア Bt.709プライマリなどです
ただし アルゴリズムでは別のカラースペースの 画像が必要になる場合があります その場合は次の操作を実行する必要があります CGColorSpaceから 使用したいカラースペースの変数を取得します そして image.matchedFromWorkingSpaceを 呼び出します
スペースにアルゴリズムを適用してから image.matchedToWorkingSpaceを呼び出します 行うべき作業は これだけです
最後に Core Imageを使用してVisionからの 出力を後処理する方法をご紹介します この1つの例では Core Imageを使用して Visionバーコード認識から バーコード画像を再生成します
コード内で必要なのは フィルタインスタンスを作成し barcodeDescriptorプロパティを Visionの認識プロパティに設定して 最後に出力画像を取得するだけです 結果はこのようになります
同様にアプリケーションでは Visionの 顔認識に基づいてフィルタを適用できます
例えば この機能を使用すると ビネット効果を非常に簡単に使用できます
コードは実際には非常に単純です 意識する必要があることは Visionの正規化座標系から Core Imageの デカルト座標系に変換することだけです
ビネットフィルタを作成したら 合成を使用して そのビネットを画像の上に配置できます
Core Imageを使用して ベクトルフィールドを使用することもできます このベクトルフィールドについては フランクが後ほど説明します
私からは以上です この後 フランクがVisionについてご説明します
ありがとう デイビッド ここでは Visionを使用して画像を 理解する方法について説明します
タスク 装置 結果がありますね タスクはあなたがしたいことで 装置は実際に作業をするものです そして 結果とはあなたが手に入れたいものです
タスクはコンパイラである VNRequestsの中にあります VNDetectFaceRectanglesRequestと同様です 装置は2つのうちの1つです VNImageRequestHandlerまたは VNSequenceRequestHandlerがあります そして手に入れる結果は VNObservationと呼ばれています 結果は 検出された長方形の VNRectangleObservationなど 実行したタスクによって異なります
まず VNImageRequestHandlerに対して リクエストを実行します そこから認識を取得します 具体的な例を見てみましょう
テキストを読みたいので VNRecognizeTextRequestを使用します
次に 画像を伴う VNImageRequestHandlerを作成します
その中で ただのプレーンテキストである 認識を取得します
それでは 2020年のVisionには 新しく何があるのでしょうか
まず 手と体の姿勢を使用します 詳細については“Hand and Body Pose” セッションを参照してください
次に 軌跡検出を見たことがあると思います 詳細は “Exploring the Action and Vision Application”で参照してください
今日は輪郭検出と オプティカルフローに焦点を当てます
輪郭検出を使用すると 画像内のエッジを検出できます
ご覧のように 赤い線は この図で見つけた輪郭を示しています
まず画像を用意し 次にVNDetectContourRequestを作成します
画像のコントラストを設定して コントラストの一部がどのように 出てくるかなどを強調できます この明るい背景を使用するか 暗い背景で 実行するかどうかを切り替えることができます これにより 前景と背景が 分離される可能性があります 最後に 最大の画像寸法を挿入して パフォーマンスと精度を比較できます
例えば― 低い解像度で見た場合でも 輪郭は得られますが ぴったりとエッジをなぞらない可能性があります しかし 低い解像度で実行できるため はるかに高速に実行されます それとは対照的に 後処理で 実行する可能性がある高い解像度を使用すると より正確な輪郭が得られますが より多くの作業を行う必要があるため 少し時間がかかります
それでは 手に入れる認識を見てみましょう
ここでは 円が描かれた2つの正方形の 非常に単純な画像を表示します
VNContoursObservationを再び取得しています
topLevelContoursでは 2つの長方形が確認できます
それらの中にある子輪郭は 多重化された円です
次に 実際にすべての輪郭を確認するために 使用できるcontourCountを取得します しかし 例えばインデックスパスを使用する方が はるかに簡単です ご覧のように これらは互いに多重化されており 図を横断できます
最後にnormalizedPathも取得します これは レンダリングに簡単に使用できる CGPathです
VNContourとは何でしょうか この例では VNContourを取得しており 親である最も外側の輪郭です その内側には 子輪郭が多重化されており これらは内側の輪郭です
輪郭にはインデックスパスがあります もちろん すべての子輪郭にも インデックスパスがあり これを再度使用して図を横断できます
次に pointCountで normalizedPointsを取得します normalizedPointsは実際に輪郭の基本であるのは 発見した各線を描写しているからです ピクセルを検出するだけでなく パスである輪郭を取得します
アスペクト比については 次のスライドでご説明します
次に レンダリングする normalizedPathを指定します 輪郭を操作する場合は いくつかの点に注意する必要があります ここにある画像を見てみましょう
この画像は1920×1080ピクセルで 中央に円があります 円の高さと幅はちょうど1080ピクセルです ただし Visionでは 正規化された座標空間を使用します そのため画像の高さは1.0 幅は1.0です したがって 円の高さは1.0になります しかし幅は0.5625です そのため検出した形状の寸法を 考慮する場合は 計算された元の画像のアスペクト比を 確認する必要があります
ここで輪郭を解析すると 非常に興味深いものになります またそのためのユーティリティも いくつか用意されています
VNGeometryUtilsはAPIを提供します 例えば検出した輪郭を完全にカプセル化する 最小の円であるboundingCircleがあります これは輪郭を 相互に比較するのに最適です
次に面積を計算します そして境界も計算します 輪郭を使用して次にできることは 実際には単純化されています 画像から輪郭を取得すると ノイズが発生する傾向があります ここで例を見てみましょう
撮影した長方形があります しかし そこには小さなねじれがあり 実際には輪郭はこれらに沿っています そのため角だけにすべてのポイントが あるわけではなく 真ん中などにもあります
Epsilonを使用して ポリゴンの近似を使用できるようになりました Epsilonのおかげでエッジの周囲にある 小さなノイズ部分をすべて除去でき 強力な輪郭エッジのみが 実際に維持されます
ここで完璧な長方形が表示されます 4つのポイントだけです 図形を分析する必要がある場合は 非常に簡単です 単純に“4点あれば四角形だ”と言えば どんな形をしているのかを検出しました
ではこれらの使い方を 具体的な例で見てみましょう
例えばパンチカードで作成された 非常に古いコンピュータコードを復活させて 世界を救うとします
パンチカードリーダーがないため パンチカードのくぼみを識別する必要があります
そこで識別方法について説明している コンピュータビジョンのブログを見つけます しかしPythonで書かれています もちろんプラットフォームに ネイティブに取り込むことで 最善の方法で実行できるようにします
Pythonコードのセクションができました 理解できなくても すぐに説明しますので 安心してください コンセプトは常に同じで まず画像処理を行います
次に画像分析を行い
視覚化する必要がある結果を取得します Pythonを理解していない場合でも 確認するのは最初の3行だけなのです 実際にはいくつかのライブラリを インポートする必要があります Pythonには付属しておらず 実際に含む必要がある サードパーティ製ライブラリです
ではネイティブで行うには?
画像処理部分の場合は 画像をロードする必要があります ご存知の通りCGImageSourceを使用して UIImageを取得しCIImageにロードし 名前を付けます 次にCore Imageを使用して CIFiltersにより画像を処理する方法があります CIAbsoluteThresholdまたは 他の多くの場合と同様です
ここで画像分析を行います そのためには処理したCIImageから VNImageRequestHandlerを作成します 次にVNDetectContourRequestのような リクエストを実行します 画像を前処理する必要さえない かもしれないところが利点です
そして結果を視覚化する場合も Core Imageを使用してこれを行うことができ 実際に持っている画像の上に 同じコンテキストで直接合成できます CIMeshGeneratorまたは CITextGeneratorを使用できます
CoreGraphicsまたはUIKitを使用して 画像の上のレイヤに レンダリングすることもできます
では すべてのスライドの後に 実際のデモを見てみましょう
ここに活動領域を準備しました 画像をロードしたことが確認できます
contourRequestを作成し
そして ただ実行します ご覧ください 求めていたくぼみを含め すべての輪郭が表示されます 387個の輪郭が見つかりましたね 望んだ数よりも少し多いかもしれません そのためこれらの輪郭をすべて 除外する必要があります 少し準備をして ここに隠しておいたコードがあります 一部を明らかにしましょう このコードでは― 実際には輪郭が青色の背景であるという ドメインの知識を使用します CIFilteringを使用して 最初にすべてのノイズをぼかします
次にカラーコントロールを使用して コントラストを出します その後フィルタ処理された画像を使用して 輪郭検出を実行します ここでは最初に気にしていた 32個の輪郭だけが検出されます
それではスライドに戻りましょう
通常はデモで何をしたかを 説明しますが 実際には何をする必要がなかったかが より重要です
これはすべてOSの一部であるため サードパーティのパッケージを ロードしませんでした 私が使ったのはUIKitとCore Image そしてVisionだけです
パイプラインにいたので 最適な処理パスを使用することにより 画像パイプラインを離れませんでした
画像をマトリックスに変換しませんでした メモリをすべて節約し 計算コストも大幅に削減しました
これが輪郭検出です 次にオプティカルフローに進みましょう オプティカルフローとは何でしょう?
2つのフレーム間の移動を解析したい時
従来はレジストレーションを使用していました それはかなり長い間Visionの一部でした 画像全体の位置合わせを行います ここで例を見てみましょう
この2つの点をカメラの画像として捉えてから カメラを移動してみましょう
2つの点が右上に移動しました
レジストレーションにより 画像がどの程度右上へ移動したかを示すことで 2つの画像間の位置合わせを 行うことができます
もう一方のオプティカルフローは 異なります 今年のVisionの新機能で XとYの間のピクセルごとのフローが得られます
この例でも2つの点があります
しかし移動して離れました
そのため画像レジストレーションでは この画像が正しく取得されません しかしオプティカルフローを使用すれば 各ピクセルがどのように移動したかが分かります オプティカルフローの結果を 見てみましょう
オプティカルフローから VNPixelBufferObservationを取得します これは浮動小数点画像です XとYの移動が交互に配置されています
このようなビデオがある場合 これらの値を単独で見ているだけでは 起こっていることを視覚化するのは困難でしょう それは後のアルゴリズムでの処理を 目的としているだけだからです しかしチェックアウトしたい場合は 実際にCore Imageを使用して 結果を視覚化できます デイビッドがセッションの前半に 取り組んでいたようにこれを行う方法があります 小さなカスタムカーネルを作成しました これで すべての移動を確認できます 移動の強さを示すカラーコーディングがあり 小さな三角形が実際に移動の方向を示しています
実行の方法を簡単に説明します カスタムフィルタを作成しました カーネルをロードし スライドの添付ファイルで 利用できるようにします そして基本的には このカーネルを 必要な矢印のサイズのパラメータで適用し フィルタとして 実行することだけです そしてVisionコードでVNGenerateOpticalFlow リクエストを実行するだけです 認識をpixelBufferに入力し これをCIImageにラップすることができます その後それをフィルタに入力して 出力画像を取得します
それでは 本日お話しした内容を まとめましょう
コンピュータビジョンは難しくある必要はなく アプリケーションを強化します 当社のネイティブAPIを使用すると 迅速かつ簡単に導入できます これらの機能を組み合わせることで 興味深いものを作ることができます
皆さんのすばらしいアプリケーションと 大きな革新を楽しみにしています セッションにご参加いただき ありがとうございます 残りのWWDCもお楽しみください
-
-
19:24 - Reading punchcards playgrounds
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") }
-
23:05 - Optical Flow Visualizer (CI kernel)
// // OpticalFlowVisualizer.cikernel // SampleVideoCompositionWithCIFilter // kernel vec4 flowView2(sampler image, float minLen, float maxLen, float size, float tipAngle) { /// Determine the color by calculating the angle from the .xy vector /// vec4 s = sample(image, samplerCoord(image)); vec2 vector = s.rg - 0.5; float len = length(vector); float H = atan(vector.y,vector.x); // convert hue to a RGB color H *= 3.0/3.1415926; // now range [3,3) float i = floor(H); float f = H-i; float a = f; float d = 1.0 - a; vec4 c; if (H<-3.0) c = vec4(0, 1, 1, 1); else if (H<-2.0) c = vec4(0, d, 1, 1); else if (H<-1.0) c = vec4(a, 0, 1, 1); else if (H<0.0) c = vec4(1, 0, d, 1); else if (H<1.0) c = vec4(1, a, 0, 1); else if (H<2.0) c = vec4(d, 1, 0, 1); else if (H<3.0) c = vec4(0, 1, a, 1); else c = vec4(0, 1, 1, 1); // make the color darker if the .xy vector is shorter c.rgb *= clamp((len-minLen)/(maxLen-minLen), 0.0,1.0); /// Add arrow shapes based on the angle from the .xy vector /// float tipAngleRadians = tipAngle * 3.1415/180.0; vec2 dc = destCoord(); // current coordinate vec2 dcm = floor((dc/size)+0.5)*size; // cell center coordinate vec2 delta = dcm - dc; // coordinate relative to center of cell // sample the .xy vector from the center of each cell vec4 sm = sample(image, samplerTransform(image, dcm)); vector = sm.rg - 0.5; len = length(vector); H = atan(vector.y,vector.x); float rotx, k, sideOffset, sideAngle; // these are the three sides of the arrow rotx = delta.x*cos(H) - delta.y*sin(H); sideOffset = size*0.5*cos(tipAngleRadians); k = 1.0 - clamp(rotx-sideOffset, 0.0, 1.0); c.rgb *= k; sideAngle = (3.14159 - tipAngleRadians)/2.0; sideOffset = 0.5 * sin(tipAngleRadians / 2.0); rotx = delta.x*cos(H-sideAngle) - delta.y*sin(H-sideAngle); k = clamp(rotx+size*sideOffset, 0.0, 1.0); c.rgb *= k; rotx = delta.x*cos(H+sideAngle) - delta.y*sin(H+sideAngle); k = clamp(rotx+ size*sideOffset, 0.0, 1.0); c.rgb *= k; /// return the color premultiplied c *= s.a; return c; }
-
23:26 - Optical Flow Visualizer (CIFilter code)
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]) } } }
-
23:42 - Optical Flow Visualizer (Vision code)
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
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。