MNISTでトレーニングされたモデルの数字認識を改善するにはどうすればよいですか?


12

私は手書きの多桁認識に取り組んでいます JavaOpenCV、前処理とセグメンテーションのためのライブラリ、およびKeras認識のためにMNIST(精度0.98)でトレーニングされたモデルを使用、ます。

認識は、1つの点を除けば、かなりうまく機能しているようです。ネットワークでは、1(番号「1」)を認識できないことがよくあります。セグメンテーションの前処理/不適切な実装が原因で発生したのか、標準のMNISTでトレーニングされたネットワークが、テストケースのように見える一番のものが見当たらないのかわかりません。

以下は、前処理とセグメンテーション後の問題のある数字の様子です。

ここに画像の説明を入力してくださいになりここに画像の説明を入力してください、として分類され4ます。

ここに画像の説明を入力してくださいになりここに画像の説明を入力してください、として分類され7ます。

ここに画像の説明を入力してくださいとなりここに画像の説明を入力してください、次のように分類されます4ます。等々...

これは、セグメンテーションプロセスを改善することで修正できるものですか?それとも、トレーニングセットを強化することによってですか。

編集:トレーニングセット(データ拡張)を強化することは間違いなく役立ちます。これは既にテストしていますが、正しい前処理の問題はまだ残っています。

私の前処理は、サイズ変更、グレースケールへの変換、2値化、反転、および膨張で構成されています。これがコードです:

Mat resized = new Mat();
Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);

Mat grayscale = new Mat();
Imgproc.cvtColor(resized, grayscale, Imgproc.COLOR_BGR2GRAY);

Mat binImg = new Mat(grayscale.size(), CvType.CV_8U);
Imgproc.threshold(grayscale, binImg, 0, 255, Imgproc.THRESH_OTSU);

Mat inverted = new Mat();
Core.bitwise_not(binImg, inverted);

Mat dilated = new Mat(inverted.size(), CvType.CV_8U);
int dilation_size = 5;
Mat kernel = Imgproc.getStructuringElement(Imgproc.CV_SHAPE_CROSS, new Size(dilation_size, dilation_size));
Imgproc.dilate(inverted, dilated, kernel, new Point(-1,-1), 1);

次に、前処理された画像は、次のように個々の数字に分割されます。

List<Mat> digits = new ArrayList<>();
List<MatOfPoint> contours = new ArrayList<>();
Imgproc.findContours(preprocessed.clone(), contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

// code to sort contours
// code to check that contour is a valid char

List rects = new ArrayList<>();

for (MatOfPoint contour : contours) {
     Rect boundingBox = Imgproc.boundingRect(contour);
     Rect rectCrop = new Rect(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height);

     rects.add(rectCrop);
}

for (int i = 0; i < rects.size(); i++) {
    Rect x = (Rect) rects.get(i);
    Mat digit = new Mat(preprocessed, x);

    int border = 50;
    Mat result = digit.clone();
    Core.copyMakeBorder(result, result, border, border, border, border, Core.BORDER_CONSTANT, new Scalar(0, 0, 0));

    Imgproc.resize(result, result, new Size(28, 28));
    digits.add(result);
}

1
分類の入力としてマスクまたは(マスクされた?)元のグレースケールピクセルを使用していますか?
ミカ

@Micka私は前処理(2値化、反転、拡張)バージョンを使用しています。MNISTトレーニングセットに一致するもの。私の投稿の前処理後の数値「1」の例があります。
youngpanda

回答:


5

あなたの問題は拡張プロセスだと思います。画像サイズを正規化したいのですが、縦横比を崩さないでください。1つの軸(別の軸のサイズが最大サイズを超えないように、最大​​の再スケーリングが可能な軸)に合わせてサイズを変更し、塗りつぶす必要があります。背景色で画像の残りの部分。「標準のMNISTは、テストケースのように見えるナンバー1を見たことがないだけ」ではなく、画像を別のトレーニングされた数値(認識される数値)のように見せます。

ソースと処理された画像の重複

画像(ソースと後処理)の正しいアスペクト比を維持した場合、画像のサイズを変更しただけでなく、「歪ませた」ことがわかります。これは、不均一な拡張または不正確なサイズ変更の結果である可能性があります


@SiRにはある程度の重みがあると思います。数値リテラルのアスペクト比は変更しないでください。
ZdaR 2019年

申し訳ありませんが、よくわかりません。私の拡張プロセスまたはサイズ変更プロセスが問題だと思いますか?最初はこの行で画像のサイズを変更するだけImgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);です。ここでアスペクト比は同じままですが、どこでプロポーションを解除しますか?
youngpanda

上記の編集に対する回答の@SiR:はい、画像のサイズを変更するだけではなく、さまざまな操作を適用します。それらの1つは膨張であり、これは形態学的操作であり、わずかな「歪み」を引き起こします。 「拡大」する画像、または画像を28x28にする最後のサイズ変更ですか?
youngpanda '22

@youngpanda、ここでの議論を見つけるかもしれませんstackoverflow.com/questions/28525436/…興味深い。それはあなたのアプローチが良い結果をもたらさない理由の手がかりを与えるかもしれません
SiR

@SiRリンクありがとうございます。私はLeNetに精通していますが、もう一度読むのは良いことです
youngpanda '10 / 10/24

5

すでにいくつかの回答が投稿されていますが、どちらも画像前処理に関する実際の質問に回答していません。

私の順番では、それが研究プロジェクトである限り、あなたの実装に大きな問題は見られません。

しかし、あなたが見逃していることに気づくことが一つあります。数学的形態学には基本的な操作があります:侵食と膨張(ユーザーが使用)。そして、そこには複雑な操作があります。基本的な操作のさまざまな組み合わせ(例:開閉)。 ウィキペディアのリンクは最高のCVリファレンスではありませんが、アイデアを得るためにそれから始めることができます。

この場合、元のバイナリイメージの変化ははるかに少ないため、通常、侵食ではなく開口部を使用し、膨張ではなく閉鎖を使用する方が適切です(ただし、シャープなエッジをクリーニングしたり、ギャップを埋めるという望ましい効果が得られます)。したがって、あなたのケースでは、閉じることを確認する必要があります(同じカーネルで画像の膨張とそれに続く浸食)。非常に小さい画像8 * 8は、1 * 1カーネル(1ピクセルは画像の16%を超える)でも拡張すると大幅に変更され、大きい画像では少なくなります)。

(:OpenCVのチュートリアルからアイデアを視覚化するには、以下の写真参照12):

膨張: 元のシンボルと拡張されたシンボル

閉鎖: 元のシンボルと閉じたシンボル

それが役に立てば幸い。


入力ありがとうございます!実際には調査プロジェクトないので、何が問題になるのでしょうか?..膨張を適用すると、画像が非常に大きくなります。8x8は画像のサイズではなく、高さと幅のサイズ変更要素です。ただし、さまざまな数学演算を試してみると、改善の余地があります。開閉について知らなかったので試してみます!ありがとうございました。
ヤングパンダ

私のせいで、新しいサイズが8 * 8だったため、サイズ変更の呼び出しを誤って読みました。現実の世界でOCRを使用したい場合は、使用領域に典型的なデータで元のネットを転送学習するオプションを検討する必要があります。少なくとも、精度が向上するかどうかを確認します。
f4f

チェックするもう1つのことは、前処理の順序です:グレースケール->バイナリ->逆->サイズ変更。サイズ変更はコストのかかる操作であり、カラー画像に適用する必要はないと思います。また、特定の入力形式がある場合、シンボルのセグメンテーションは輪郭検出なしで(コストの低いもので)実行できますが、実装が難しい場合があります。
f4f

MNISTとは別のデータセットがある場合は、転移学習を試すことができます。:)前処理の順序を変更して、ご連絡いたします。ありがとうございました!私の問題では、輪郭検出よりも簡単なオプションがまだ見つかりませんでした...
youngpanda '24

1
OK。OCRを使用する画像からデータセットを自分で収集できます。これは一般的な方法です。
f4f

4

したがって、以前の結果に基づいて、計算カスケードのすべてのステップを引き起こす複雑なアプローチが必要です。アルゴリズムには次の機能があります。

  1. 画像の前処理

前述のように、サイズ変更を適用すると、画像のアスペクト比に関する情報が失われます。トレーニングプロセスで示唆されたのと同じ結果を得るには、同じ数字の画像を再処理する必要があります。

固定サイズの画像で画像を切り抜く場合は、より良い方法です。そのバリアントでは、トレーニングプロセスの前に数字画像を輪郭線で見つけてサイズを変更する必要はありません。次に、クロップアルゴリズムに少し変更を加えて、認識を向上させます。輪郭を見つけ、数字をサイズ変更せずに関連する画像フレームの中心に配置して、認識します。

また、2値化アルゴリズムにもっと注意を払う必要があります。2値化のしきい値が学習エラーに及ぼす影響を調べた経験があります。これは非常に重要な要素であると言えます。このアイデアを確認するには、2値化の別のアルゴリズムを試すことができます。たとえば、このライブラリを使用して代替の2値化アルゴリズムをテストできます。

  1. 学習アルゴリズム

認識の品質を向上させるには、相互検証を使用します、トレーニングプロセスでます。これにより、トレーニングデータの過剰適合の問題を回避できます。たとえば、この記事を読むことができます、Kerasでの使用方法を説明した。

正確度の測定率が高い場合でも、実際の認識品質について何も示されないことが原因で、訓練されたANNが訓練データにパターンを見つけられませんでした。上で説明したように、トレーニングプロセスまたは入力データセットと接続されている場合と、ANNアーキテクチャの選択によって発生する場合があります。

  1. ANNアーキテクチャ

それは大きな問題です。タスクを解決するためのより良いANNアーキテクチャを定義する方法は?そのことを行う一般的な方法はありません。しかし、理想に近づくにはいくつかの方法があります。たとえば、この本を読むことができます。それはあなたがあなたの問題のより良いビジョンを作るのに役立ちます。また、ここでは、ANNの非表示のレイヤー/要素の数に適合するヒューリスティック式をいくつか見つけることができます。またここではは、この概要について説明します。

これがお役に立てば幸いです。


1.正しく理解している場合、固定サイズにトリミングできません。数桁の数字の画像であり、すべてのケースでサイズ/場所などが異なります。または、何か別の意味ですか?はい、そうですが、私はさまざまな2値化手法と微調整されたパラメーターを試しました。2.実際、MNISTでの認識は素晴らしく、過剰適合は発生していません。私が述べた精度はテスト精度です。ネットワークもそのトレーニングも問題ではありません。3.すべてのリンクに感謝しますが、アーキテクチャには非常に満足しています。もちろん、改善の余地は常にあります。
ヤングパンダ

はい、わかります。しかし、データセットをより統一する可能性は常にあります。あなたの場合、あなたがすでにしているように、輪郭によって数字の画像をトリミングするほうが良いです。ただし、その後、数字の画像の最大サイズに合わせて、数字の画像をxとyのスケールで統一したサイズに拡大することをお勧めします。あなたはそれをするために指の輪郭領域の中心を好むかもしれません。これにより、トレーニングアルゴリズムの入力データがよりクリーンになります。
Egor Zamotaev、

拡張をスキップする必要があるということですか?最後に、境界線(両側に50ピクセル)を適用すると、画像は既に中央に配置されます。その後、MNISTに必要なサイズであるため、各桁を28x28にサイズ変更しています。28x28に別のサイズに変更できるということですか?
youngpanda

1
はい、拡張は望ましくありません。輪郭の高さと幅によって比率が異なる場合があるため、ここでアルゴリズムを改善する必要があります。少なくとも、同じ比率で画像サイズを作成する必要があります。28x28の入力画像サイズがあるため、xとyのスケールで同じ1:1の比率の画像を準備する必要があります。各画像の側に50 pxの境界線を取得するのではなく、contourSizeX + borderSizeX == contourSizeY + borderSizeYの条件を満たすX、Y pxの境界線を取得する必要があります。それで全部です。
Egor Zamotaev

私はすでに拡張なしで試しました(投稿で言及するのを忘れていました)。結果は変わりませんでした...私の境界線番号は実験的なものでした。理想的には、20x20ボックス(データセット内でサイズが正規化されている)に合わせて数字を必要とし、その後、重心を使用してシフトします...
youngpanda

1

いくつかの研究と実験の後、画像の前処理自体は問題ではないという結論に達しました(拡張サイズや形状など、提案されたパラメーターをいくつか変更しましたが、結果には重要ではありませんでした)。ただし、次の2つが役立ちました。

  1. @ f4fが気づいたように、私は実際のデータを含む自分のデータセットを収集する必要がありました。これはすでに非常に役立ちました。

  2. セグメンテーションの前処理に重要な変更を加えました。個々の輪郭を取得した後、まず20x20ピクセルボックスに収まるように画像をサイズ正規化します(MNIST)。その後28x28、重心(バイナリイメージの場合は両方の次元の平均値です)を使用して、イメージの中央にボックスを配置します。

もちろん、桁の重複や接続など、難しいセグメンテーションのケースはまだありますが、上記の変更により私の最初の質問に答え、分類のパフォーマンスが向上しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.