2013-01-04 19 views
8

Szukałem wysokiego i niskiego poziomu, aby uzyskać niezawodny sposób na zdjęcie obrazu w .Net, i nie mam dużo szczęścia.Korzystanie z .Net do korekcji obrazu

Z chwilą użycia Aforge. To jest ból, ponieważ pracuję z WPF, więc obrazy, z którymi pracuję są obiektami BitmapImage, w przeciwieństwie do obiektów Bitmap, co oznacza, że ​​muszę zacząć od obiektu BitmapImage, zapisać go w strumieniu pamięci, utworzyć nowy obiekt Bitmap ze strumienia pamięci, przejdź przez proces usuwania, zapisz zniekształcony obraz do nowego strumienia pamięci, a następnie utwórz nowy obiekt BitmapImage ze wspomnianego strumienia pamięci. Nie tylko to, ale nie jest to świetne rozwiązanie.

Próbuję odczytać dane OMR z kawałka papieru zeskanowanego do skanera, a zatem muszę polegać na konkretnym polu OMR, które za każdym razem jest za każdym razem tak samo skorelowane, więc poprawa musi być niezawodna.

Używam Aforge z minuty na minutę, nie mogę znaleźć żadnych innych darmowych bibliotek/open source do tworzenia obrazów w .Net, wszystko co znalazłem jest albo odpowiednio drogie albo w C/C++.

Moje pytanie brzmi: czy istnieją inne biblioteki wolnego/otwartego źródła, które pomagają w tworzeniu obrazów w .Net? Jeśli tak, jak się nazywają, jeśli nie, w jaki sposób powinienem podejść do tego problemu?

Edit: Na przykład, powiedzmy, że mam poniższej strony:

Initial Image

Uwaga: To jest tylko dla celów poglądowych, ale rzeczywisty obraz rzeczywiście ma czarny prostokąt na każdym rogu stronę, może to pomoże.

Podczas drukowania na to uwagę i skanować je z powrotem do mojego skanera, wygląda to tak:

Scanned Image

muszę prostowanie ten obraz tak, że moja skrzynka jest w tym samym miejscu za każdym razem. W prawdziwym świecie istnieje wiele pudeł, są mniejsze i blisko siebie, więc dokładność jest ważna.

Moja obecna metoda ta jest nieskuteczna masywny ból-in-the-ass:

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; 
} 

Z perspektywy czasu, jeszcze kilka pytań:

  1. używam AForge prawidłowo?
  2. Czy należy wybrać najlepszą bibliotekę do wykorzystania w tym zadaniu?
  3. W jaki sposób można poprawić moje obecne podejście, aby uzyskać dokładniejsze wyniki?
+1

Jest to problem używania narzędzi do przetwarzania obrazu jako czarnej skrzynki. Istnieje wiele sposobów na rozluźnienie i ważne jest, aby wiedzieć, że stosowane podejście jest szczególnie użyteczne, gdy "nie jest wspaniałe". W przeciwnym razie, skąd wiadomo, czy inna prostokątna czarna skrzynka ma szansę uzyskać lepsze wyniki niż obecna czarna skrzynka? Letponica ma również metodę black-boxow, ale na http://tpgit.github.com/Leptonica/skew_8c.html możesz przeczytać, co robi. Istnieje również wiele innych sposobów, aby to osiągnąć. – mmgp

+0

@mmgp Zgadzam się i chciałbym mieć czas na poznanie tajników algorytmu transformacji algorytmu i C++, ale niestety mam termin, więc black boxing jest teraz moją jedyną opcją! Dziękuję za link, sprawdzę to. – JMK

+2

John, Czy mógłbyś umieścić link do obrazów, które masz problemy, lub dołączyć je do pytania? Ułatwi to ludziom odpowiedź. – DermFrench

Odpowiedz

6

Biorąc pod uwagę wejście próbki, jest oczywiste, że nie jesteś po zdjęciu prostowanie. Ten rodzaj operacji nie poprawi zniekształceń, ale musisz przeprowadzić transformację perspektywy. Widać to wyraźnie na poniższym rysunku. Cztery białe prostokąty oznaczają krawędzie czterech czarnych skrzynek, żółte linie są wynikiem połączenia czarnych skrzynek. Żółty czworobok nie jest przekrzywiony czerwony (ten, który chcesz osiągnąć).

enter image description here

Tak więc, jeśli rzeczywiście można dostać na powyższym rysunku, problem staje się dużo prostsze. Jeśli nie masz czterech skrzynek narożnych, potrzebujesz czterech innych punktów odniesienia, więc bardzo ci pomogły. Po uzyskaniu powyższego obrazu znasz cztery żółte rogi, a następnie odwzorujesz je na cztery czerwone rogi. To jest transformacja perspektywy, którą musisz wykonać, a według twojej biblioteki może istnieć gotowa do tego funkcja (przynajmniej jedna, sprawdź komentarze do twojego pytania).

Istnieje wiele sposobów, aby dostać się do powyższego obrazu, więc po prostu opiszę stosunkowo prosty. Najpierw binaryzuj swój obraz w skali szarości. Aby to zrobić, wybrałem prosty globalny próg 100 (obraz znajduje się w zakresie [0, 255]), który utrzymuje pola i inne szczegóły na obrazie (np. Silne linie wokół obrazu). Intensje powyżej lub równe 100 są ustawione na 255, a poniżej 100 na 0. Jednak, ponieważ jest to wydrukowany obraz, prawdopodobieństwo pojawienia się ciemności w polach jest różne. Więc możesz potrzebować lepszej metody, coś tak prostego jak gradient morfologiczny może potencjalnie działać lepiej. Drugim krokiem jest wyeliminowanie nieistotnych szczegółów. Aby to zrobić, wykonaj morfologiczne zamknięcie z kwadratem 7x7 (około 1% minimum między szerokością a wysokością obrazu wejściowego). Aby uzyskać obramowanie skrzynek, użyj erozji morfologicznej, jak w current_image - erosion(current_image), używając elementarnego kwadratu 3x3. Teraz masz obraz z czterema białymi konturami jak powyżej (to zakładając wszystko, ale pola zostały wyeliminowane, co jest uproszczeniem innych danych wejściowych, jak sądzę). Aby uzyskać piksele tych białych konturów, można wykonać etykietowanie podzespołów. W przypadku tych 4 komponentów określ górny prawy, górny lewy, dolny prawy i lewy dolny. Teraz możesz łatwo znaleźć potrzebne punkty, aby uzyskać rogi żółtego prostokąta. Wszystkie te operacje są łatwo dostępne w AForge, więc jest to tylko kwestia tłumaczenia następujący kod C#:

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 

Ostateczny wynik powyższego kodu z obrazu wejściowego:

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

Pierwsza lista punktów opisuje cztery rogi żółtego prostokąta, a druga odnosi się do czerwonego prostokąta. Aby wykonać transformację perspektywy, możesz użyć funkcji AForge z funkcją gotowości.Kiedyś ImageMagick dla uproszczenia, jak w:

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

co daje wyrównanie jesteś po (z niebieskimi liniami znalezione wcześniej, aby lepiej pokazać wynik):

enter image description here

można zauważyć, że lewa pionowa niebieska linia nie jest w pełni prosta, w rzeczywistości dwa najbardziej po lewej stronie pola nie są wyrównane o 1 piksel na osi x. Można to skorygować za pomocą innej interpolacji zastosowanej podczas transformacji perspektywy.

+0

Na http://i.imgur.com/tKLNI.png możesz zobaczyć inne wyniki używając powyższego kodu, z unikalną różnicą odrzucania komponentów połączonych z border (to zrobiło różnicę tylko przy rysowaniu końcowych niebieskich linii do wizualizacji). – mmgp

+0

Bardzo dziękuję za pomoc, ciesz się z punktów! – JMK

1

Biblioteka Johna Leptonicy ma być bardzo szybka i stabilna.
Oto link, jak go nazwać z C# http://www.leptonica.com/vs2008doc/csharp-and-leptonlib.html. Nie jestem pewien, czy to jest odpowiedź, więc właśnie dodałem komentarz.

Ma LeptonicaCLR.Utils.DeskewBinaryImage(), aby faktycznie wyrównać obraz w b &.

Nie jestem pewien, jak dobrze byłoby z rzeczywistymi formularzami, które próbujesz przetwarzać.

+1

Również dyskusja na temat określenia kąta pochylenia za pomocą Leptonica: http: //www.leptonica. com/skew-measurement.html – DermFrench

+0

Dzięki Dermot, czytając teraz – JMK

1

John, Myślę też, że dopasowanie szablonów może pomóc rozwiązać ten problem (jeśli biblioteka Leptonica nie jest wystarczająco dobra).

Aforge.net został zbudowany w szablon pasujący: http://www.aforgenet.com/framework/docs/html/17494328-ef0c-dc83-1bc3-907b7b75039f.htm

W mojej ograniczonej wiedzy na ten temat, można by mieć obraz źródłowy upraw/znaku rejestracyjnego i znaleźć go za pomocą szablonu dopasowanie w skanowanym obrazie. Następnie możesz przyciąć obraz, aby uzyskać pod-obraz tylko części wewnątrz znaków rejestracyjnych. W przypadku obrazu dostarczonego powyżej, myślę, że można założyć dość małe początkowe pochylenie i wykonać tylko dopasowanie do szablonu na przyciętym obszarze obrazu, aby zmniejszyć całkowity czas.

Istnieje pewna dyskusja o tym tutaj: How to Locate Alignment Marks in an Image

Powiązane problemy