2013-01-04 20 views
8

.Netで画像を傾き補正する信頼性の高い方法を探し求めており、多くの運がない。.Netを使用して画像を傾き補正する

私はAforgeを使用しています。これは私がWPFで作業しているときの痛みです。作業している画像はBitmapオブジェクトではなく、BitmapImageオブジェクトで開始し、メモリストリームに保存し、新しいBitmapオブジェクトを作成する必要がありますデスキュープロセスを経て、デスキューされたイメージを新しいメモリストリームに保存し、その後、前記メモリストリームから新しいBitmapImageオブジェクトを作成する。それだけでなく、傾きを補正することは素晴らしいことではありません。

スキャナにスキャンされた紙片のOMRデータを読み取ろうとしているため、毎回同じ座標にある特定のOMRボックスに依存する必要があるため、スキュー調整は信頼性が必要です。

私はAporgeを使用していますが、.NETでイメージのデスキューのための他のフリー/オープンソースライブラリを見つけることはできません。私が見つけたものはすべて適切に高価であるかC/C++です。

私の質問は、.NETでのイメージデスキューを助ける他のフリー/オープンソースライブラリですか?もしそうなら、彼らは何を呼びますか?私はこの問題にどのように接近すべきですか?

編集:

Initial Image

注:たとえば、のは、私は以下のページを持っているとしましょうこれは例示のみを目的としており、実際の画像は実際の各コーナーに黒い四角形を持っていますページ、おそらくこれが役立ちます。

私はこれをプリントアウトし、戻って私のスキャナにそれをスキャンすると、それは次のようになります。

Scanned Image

私は私のボックスが同じ場所に毎回あるように、この画像をデスキューする必要があります。現実の世界では、箱がたくさんありますが、それらはもっと小さく接近しているので、正確さは重要です。

このため私の現在の方法は、大規模な効果のない痛み・イン・ザ・お尻です:振り返ってみると

using AForge.Imaging; 
using AForge.Imaging.Filters; 
using System.Drawing; 
using System.Drawing.Imaging; 
using System.IO; 
using System.Windows.Media.Imaging; 

public static BitmapImage DeskewBitmap(BitmapImage skewedBitmap) 
{ 
    //Using a memory stream to minimise disk IO 
    var memoryStream = BitmapImageToMemoryStream(skewedBitmap); 

    var bitmap = MemoryStreamToBitmap(memoryStream); 
    var skewAngle = CalculateSkewAngle(bitmap); 

    //Aforge needs a Bppp indexed image for the deskewing process 
    var bitmapConvertedToBbppIndexed = ConvertBitmapToBbppIndexed(bitmap); 

    var rotatedImage = DeskewBitmap(skewAngle, bitmapConvertedToBbppIndexed); 

    //I need to convert the image back to a non indexed format to put it back into a BitmapImage object 
    var imageConvertedToNonIndexed = ConvertImageToNonIndexed(rotatedImage); 

    var imageAsMemoryStream = BitmapToMemoryStream(imageConvertedToNonIndexed); 
    var memoryStreamAsBitmapImage = MemoryStreamToBitmapImage(imageAsMemoryStream); 

    return memoryStreamAsBitmapImage; 
} 

private static Bitmap ConvertImageToNonIndexed(Bitmap rotatedImage) 
{ 
    var imageConvertedToNonIndexed = rotatedImage.Clone(
     new Rectangle(0, 0, rotatedImage.Width, rotatedImage.Height), PixelFormat.Format32bppArgb); 
    return imageConvertedToNonIndexed; 
} 

private static Bitmap DeskewBitmap(double skewAngle, Bitmap bitmapConvertedToBbppIndexed) 
{ 
    var rotationFilter = new RotateBilinear(-skewAngle) { FillColor = Color.White }; 

    var rotatedImage = rotationFilter.Apply(bitmapConvertedToBbppIndexed); 
    return rotatedImage; 
} 

private static double CalculateSkewAngle(Bitmap bitmapConvertedToBbppIndexed) 
{ 
    var documentSkewChecker = new DocumentSkewChecker(); 

    double skewAngle = documentSkewChecker.GetSkewAngle(bitmapConvertedToBbppIndexed); 

    return skewAngle; 
} 

private static Bitmap ConvertBitmapToBbppIndexed(Bitmap bitmap) 
{ 
    var bitmapConvertedToBbppIndexed = bitmap.Clone(
     new Rectangle(0, 0, bitmap.Width, bitmap.Height), PixelFormat.Format8bppIndexed); 
    return bitmapConvertedToBbppIndexed; 
} 

private static BitmapImage ResizeBitmap(BitmapImage originalBitmap, int desiredWidth, int desiredHeight) 
{ 
    var ms = BitmapImageToMemoryStream(originalBitmap); 
    ms.Position = 0; 

    var result = new BitmapImage(); 
    result.BeginInit(); 
    result.DecodePixelHeight = desiredHeight; 
    result.DecodePixelWidth = desiredWidth; 

    result.StreamSource = ms; 
    result.CacheOption = BitmapCacheOption.OnLoad; 

    result.EndInit(); 
    result.Freeze(); 

    return result; 
} 

private static MemoryStream BitmapImageToMemoryStream(BitmapImage image) 
{ 
    var ms = new MemoryStream(); 

    var encoder = new JpegBitmapEncoder(); 
    encoder.Frames.Add(BitmapFrame.Create(image)); 

    encoder.Save(ms); 

    return ms; 
} 

private static BitmapImage MemoryStreamToBitmapImage(MemoryStream ms) 
{ 
    ms.Position = 0; 
    var bitmap = new BitmapImage(); 

    bitmap.BeginInit(); 

    bitmap.StreamSource = ms; 
    bitmap.CacheOption = BitmapCacheOption.OnLoad; 

    bitmap.EndInit(); 
    bitmap.Freeze(); 

    return bitmap; 
} 

private static Bitmap MemoryStreamToBitmap(MemoryStream ms) 
{ 
    return new Bitmap(ms); 
} 

private static MemoryStream BitmapToMemoryStream(Bitmap image) 
{ 
    var memoryStream = new MemoryStream(); 
    image.Save(memoryStream, ImageFormat.Bmp); 

    return memoryStream; 
} 

、カップルより多くの質問:

  1. 私が正しくAForgeを使用していますか?
  2. このタスクにはAForgeが最適なライブラリですか?
  3. もっと正確な結果を得るために私の現在のアプローチを改善するにはどうすればよいですか?
+1

これは画像処理ツールをブラックボックスとして使用する際の問題です。デスキューするにはさまざまな方法がありますが、それが "偉大ではない"ときに特別に使用されているアプローチを知ることは重要です。さもなければ、別のデスキューブラックボックスがあなたの現在のブラックボックスより良い結果を生み出す機会があるかどうかをどうやって知っていますか? Letponicaには、ブラックボックスのデスキュー方法もありますが、http://tpgit.github.com/Leptonica/skew_8c.htmlには、それが何であるかを読むことができます。これを達成する他の方法もたくさんあります。 – mmgp

+0

@ mmgp私は同意する、私はハフ変換アルゴリズムとC + +の機能を学ぶ時間を持っていたが、悲しいことに私は期限があるので、ブラックボクシングは今私の唯一のオプションです!リンクありがとう、私はそれをチェックします。 – JMK

+2

John、問題にスキューがあるイメージへのリンクを張ったり、質問にそれらを含めることができますか?これにより、人々が簡単に答えることができます。 – DermFrench

答えて

6

は、あなたが画像傾き補正後でないことは明らかです。この種の操作では、歪みを補正するのではなく、代わりに透視変換を実行する必要があります。これは、次の図にはっきりと示されています。 4つの白い長方形は4つの黒いボックスの端を表し、黄色の線は黒いボックスを結んだ結果です。黄色の四角形は歪んだ赤色のものではありません(達成したいもの)。

enter image description here

あなたが実際に上の図を得ることができるのであれば、問題はずっと簡単なります。 4つのコーナーボックスがない場合は、他の4つの参照ポイントが必要なので、多くのことを助けます。上記の画像を取得したら、4つの黄色のコーナーを知っているので、4つの赤いコーナーにマップするだけです。これはあなたが行う必要がある透視変換であり、あなたのライブラリによれば、そのための準備ができている機能があるかもしれません(少なくともあなたの質問にコメントをチェックしてください)。

上記の画像を表示するには複数の方法がありますので、簡単に説明します。まず、グレースケール画像を2値化します。これを行うために、単純なグローバルしきい値100(画像は[0、255]の範囲内にあります)を選んで、ボックスやその他の詳細を画像内に保持しました。 100以上の強度は255に設定され、100未満は0に設定されます。しかし、これは印刷された画像なので、ボックスがどのくらい暗く表示されるかは非常に異なる可能性があります。したがって、ここでより良い方法が必要になるかもしれません。形態学的勾配が潜在的により良く働くような単純なものです。第2のステップは、無関係な細部を排除することです。これを行うには、7×7の正方形(入力画像の幅と高さの間の最小値の約1%)で形態学的クロージングを実行します。ボックスの境界線を取得するには、current_image - erosion(current_image)のような形態学的な侵食を、基本的な3x3の正方形を使用して使用します。今度は、上のように4つの白い輪郭を持つイメージを持っています(これは、箱以外はすべて削除されたと仮定して、私が信じている他の入力を単純化したものです)。これらの白い輪郭のピクセルを取得するには、接続されたコンポーネントのラベル付けを行うことができます。これらの4つのコンポーネントで、右上のもの、左上のもの、右下のもの、左下のものを決定します。これで、黄色の矩形の角を取得するのに必要な点を簡単に見つけることができます。それはC#に次のコードの変換の問題だけであるので、これらのすべての操作は、AForgeで容易に入手可能である:

import sys 
import numpy 
from PIL import Image, ImageOps, ImageDraw 
from scipy.ndimage import morphology, label 

# Read input image and convert to grayscale (if it is not yet). 
orig = Image.open(sys.argv[1]) 
img = ImageOps.grayscale(orig) 

# Convert PIL image to numpy array (minor implementation detail). 
im = numpy.array(img) 

# Binarize. 
im[im < 100] = 0 
im[im >= 100] = 255 

# Eliminate undesidered details. 
im = morphology.grey_closing(im, (7, 7)) 

# Border of boxes. 
im = im - morphology.grey_erosion(im, (3, 3)) 

# Find the boxes by labeling them as connected components. 
lbl, amount = label(im) 
box = [] 
for i in range(1, amount + 1): 
    py, px = numpy.nonzero(lbl == i) # Points in this connected component. 
    # Corners of the boxes. 
    box.append((px.min(), px.max(), py.min(), py.max())) 
box = sorted(box) 
# Now the first two elements in the box list contains the 
# two left-most boxes, and the other two are the right-most 
# boxes. It remains to stablish which ones are at top, 
# and which at bottom. 
top = [] 
bottom = [] 
for index in [0, 2]: 
    if box[index][2] > box[index+1][2]: 
     top.append(box[index + 1]) 
     bottom.append(box[index]) 
    else: 
     top.append(box[index]) 
     bottom.append(box[index + 1]) 

# Pick the top left corner, top right corner, 
# bottom right corner, and bottom left corner. 
reference_corners = [ 
     (top[0][0], top[0][2]), (top[1][1], top[1][2]), 
     (bottom[1][1], bottom[1][3]), (bottom[0][0], bottom[0][3])] 

# Convert the image back to PIL (minor implementation detail). 
img = Image.fromarray(im) 
# Draw lines connecting the reference_corners for visualization purposes. 
visual = img.convert('RGB') 
draw = ImageDraw.Draw(visual) 
draw.line(reference_corners + [reference_corners[0]], fill='yellow') 
visual.save(sys.argv[2]) 

# Map the current quadrilateral to an axis-aligned rectangle. 
min_x = min(x for x, y in reference_corners) 
max_x = max(x for x, y in reference_corners) 
min_y = min(y for x, y in reference_corners) 
max_y = max(y for x, y in reference_corners) 

# The red rectangle. 
perfect_rect = [(min_x, min_y), (max_x, min_y), (max_x, max_y), (min_x, max_y)] 

# Use these points to do the perspective transform. 
print reference_corners 
print perfect_rect 

ご入力画像と上記のコードの最後の出力は次のとおりです。

[(55, 30), (734, 26), (747, 1045), (41, 1036)] 
[(41, 26), (747, 26), (747, 1045), (41, 1045)] 

最初の点のリストは、黄色の矩形の四隅を示し、2番目の点は赤い矩形に関連しています。透視変換を行うには、準備関数を使ってAForgeを使うことができます。

enter image description here

あなたがいることに気づくことがあります。あなたが後にあるアライメントを与える

convert input.png -distort Perspective "55,30,41,26 734,26,747,26 747,1045,747,1045 41,1036,41,1045" result.png 

(青線で、より良い結果を表示する前に見つかった):私は中として簡単にするためのImageMagickを使用しました実際には、左端の2つのボックスはx軸の1ピクセル分の位置がずれています。これは、透視変換の間に使用される異なる補間によって補正することができる。

+0

http://i.imgur.com/tKLNI.pngでは、上記のコードを使用していくつかの他の結果を見ることができます。境界線(これは、視覚化のために最終的な青い線を描くときだけ違いがあります)。 – mmgp

+0

非常に助けをありがとう、ポイントを楽しむ! – JMK

1

ジョンは、Leptonicaライブラリーが非常に速く安定であることを意味する。
ここから、それをc#http://www.leptonica.com/vs2008doc/csharp-and-leptonlib.htmlから呼び出す方法に関するリンクがあります。私はこれが答えであるかどうかわからないので、私はちょうどコメントとして追加しました。

実際には、画像をデスキューするLeptonicaCLR.Utils.DeskewBinaryImage()があります。&イメージです。

処理しようとしている実際のフォームがどれほど優れているかわかりません。

+1

Leptonicaを使用してスキュー角度を決定する方法については、 http://www.leptonicaを参照してください。 com/skew-measurement.html – DermFrench

+0

ありがとうDermot、今読んで – JMK

1

John、 テンプレートマッチングもこれを解決するのに役立つと思います(Leptonicaライブラリが十分でない場合)。

Aforge.netがでテンプレートマッチング組み込まれています:この上の私の限られた知識では http://www.aforgenet.com/framework/docs/html/17494328-ef0c-dc83-1bc3-907b7b75039f.htm

、あなたは、作物/登録マークのソースイメージを持っているし、スキャンした画像にテンプレートマッチングを使用してそれを見つけるだろう。次に、画像をトリミングして、トンボの内側の部分のサブ画像を取得することができます。上記の画像では、かなり小さい初期スキューを仮定して、トリミングされた領域のテンプレートマッチングのみを行い、合計時間を短縮することができます。ここでは、このいくつかの議論があり

:サンプル入力が与えられ How to Locate Alignment Marks in an Image

関連する問題