2013-01-04 40 views
8

Tôi đã tìm kiếm cao và thấp cho một cách đáng tin cậy để xóa một hình ảnh trong. Net, và không có nhiều may mắn.Sử dụng .Net để xóa một hình ảnh

Vào phút tôi đang sử dụng Aforge. Đây là một nỗi đau khi tôi làm việc với WPF, vì vậy những hình ảnh mà tôi đang làm việc với các đối tượng BitmapImage, trái ngược với các đối tượng Bitmap, có nghĩa là tôi cần bắt đầu với một đối tượng BitmapImage, lưu nó vào một luồng bộ nhớ, tạo một đối tượng Bitmap mới từ luồng bộ nhớ, đi qua quy trình deskewing, lưu hình ảnh đã được xóa vào luồng bộ nhớ mới và sau đó tạo đối tượng BitmapImage mới từ luồng bộ nhớ đã nói. Không chỉ vậy, nhưng deskewing không phải là tuyệt vời.

Tôi đang cố gắng đọc dữ liệu OMR của một mẩu giấy được quét vào máy quét, và do đó tôi cần phải dựa vào một hộp OMR cụ thể ở cùng một tọa độ mỗi lần, do đó deskewing cần phải đáng tin cậy.

Vì vậy, tôi đang sử dụng Aforge vào phút, tôi không thể tìm thấy bất kỳ thư viện mã nguồn mở/miễn phí nào khác cho hình ảnh deskewing trong. Net, mọi thứ tôi đã tìm thấy đều đúng hoặc đắt tiền trong C/C++.

Câu hỏi của tôi là các thư viện nguồn mở/miễn phí khác tồn tại có hỗ trợ trong deskewing hình ảnh trong .Net? Nếu vậy họ được gọi là gì, nếu không làm thế nào tôi nên tiếp cận vấn đề này?

Chỉnh sửa: Ví dụ, giả sử tôi có trang bên dưới:

Initial Image

Lưu ý: Đây là chỉ mang tính chất minh họa, nhưng hình ảnh thực tế không thực sự có một hình chữ nhật màu đen ở mỗi góc của trang, có thể điều này sẽ giúp ích.

Khi tôi in này ra, và quét nó trở lại vào máy quét của tôi, nó trông như thế này:

Scanned Image

tôi cần phải deskew hình ảnh này để hộp của tôi là ở cùng một chỗ mỗi lần. Trong thế giới thực, có rất nhiều hộp, chúng nhỏ hơn và gần nhau, vì vậy độ chính xác là quan trọng.

phương pháp hiện tại của tôi cho đây là một đồ sộ không hiệu quả đau-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; 
} 

Nhìn lại, thêm một vài câu hỏi:

  1. Tôi có sử dụng AForge chính xác?
  2. Là AForge thư viện tốt nhất để sử dụng cho tác vụ này?
  3. Cách tiếp cận hiện tại của tôi được cải thiện này để có kết quả chính xác hơn?
+1

Đây là vấn đề khi sử dụng các công cụ xử lý hình ảnh làm hộp đen. Có nhiều cách để giảm bớt, và điều quan trọng là phải biết cách tiếp cận được sử dụng đặc biệt khi nó "không phải là" tuyệt vời ". Nếu không, làm cách nào để bạn biết liệu một hộp đen khác có bất kỳ cơ hội nào để tạo ra kết quả tốt hơn so với hộp đen hiện tại của bạn không? Letponica cũng có một phương pháp khử hộp đen, nhưng tại http://tpgit.github.com/Leptonica/skew_8c.html bạn có thể đọc những gì nó làm. Và có rất nhiều cách khác để đạt được điều này. – mmgp

+0

@mmgp Tôi đồng ý, và tôi ước rằng tôi có thời gian để học các thuật toán biến đổi hough, và C++ nhưng đáng buồn là tôi có thời hạn, vì vậy quyền anh đen là lựa chọn duy nhất của tôi ngay bây giờ! Thankyou cho liên kết, tôi sẽ kiểm tra xem nó ra. – JMK

+2

John, Bạn có thể tạo liên kết đến hình ảnh mà bạn đang gặp sự cố deskewing hoặc đưa chúng vào câu hỏi không? Điều này sẽ giúp mọi người dễ dàng trả lời hơn. – DermFrench

Trả lời

6

Với đầu vào mẫu, rõ ràng là bạn không phải là sau khi deskewing hình ảnh. Loại hoạt động này sẽ không sửa chữa méo mó mà bạn có, thay vào đó bạn cần thực hiện biến đổi phối cảnh. Điều này có thể được thấy rõ trong hình dưới đây. Bốn hình chữ nhật màu trắng đại diện cho các cạnh của bốn hộp đen của bạn, các đường màu vàng là kết quả của việc kết nối các hộp đen. Tứ giác màu vàng không phải là một màu đỏ nghiêng (một trong những bạn muốn đạt được).

enter image description here

Vì vậy, nếu bạn thực sự có thể nhận được hình trên, vấn đề trở nên đơn giản hơn rất nhiều. Nếu bạn không có bốn hộp góc, bạn sẽ cần bốn điểm tham chiếu khác, vì vậy chúng giúp bạn rất nhiều. Sau khi bạn nhận được hình ảnh ở trên, bạn biết bốn góc màu vàng, và sau đó bạn chỉ cần ánh xạ chúng tới bốn góc màu đỏ. Đây là biến đổi phối cảnh bạn cần phải làm, và theo thư viện của bạn có thể có một chức năng sẵn sàng cho điều đó (có ít nhất là, kiểm tra các bình luận cho câu hỏi của bạn).

Có nhiều cách để truy cập hình ảnh ở trên, vì vậy tôi sẽ chỉ mô tả một cách đơn giản. Trước tiên, hãy binarize hình ảnh màu xám của bạn. Để làm điều đó, tôi đã chọn một ngưỡng toàn cầu đơn giản là 100 (hình ảnh của bạn nằm trong khoảng [0, 255]), giúp giữ các hộp và các chi tiết khác trong hình ảnh (như các đường nét mạnh xung quanh hình ảnh). Cường độ trên hoặc bằng 100 được đặt là 255 và dưới 100 được đặt thành 0. Tuy nhiên, vì đây là hình ảnh được in, các hộp tối xuất hiện rất có khả năng thay đổi. Vì vậy, bạn có thể cần một phương pháp tốt hơn ở đây, một cái gì đó đơn giản như một gradient hình thái học có thể hoạt động tốt hơn. Bước thứ hai là loại bỏ các chi tiết không liên quan. Để làm điều đó, thực hiện một hình thái đóng cửa với một hình vuông 7x7 (khoảng 1% tối thiểu giữa chiều rộng và chiều cao của hình ảnh đầu vào). Để có được đường viền của các hộp, sử dụng xói mòn hình thái như trong current_image - erosion(current_image) sử dụng hình vuông 3x3 cơ bản. Bây giờ bạn có một hình ảnh với bốn đường viền màu trắng như trên (đây là giả định tất cả mọi thứ nhưng các hộp đã được loại bỏ, đơn giản hóa các đầu vào khác mà tôi tin). Để có được điểm ảnh của các đường viền màu trắng này, bạn có thể tạo nhãn thành phần được kết nối. Với 4 thành phần này, hãy xác định phần trên cùng bên phải, phần trên cùng bên trái, phần dưới cùng bên phải và phần dưới cùng bên trái. Bây giờ bạn có thể dễ dàng tìm thấy các điểm cần thiết để có được các góc của hình chữ nhật màu vàng. Tất cả các hoạt động này là có sẵn trong AForge, vì vậy nó chỉ là một vấn đề của bản dịch đoạn mã sau vào 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 

Kết quả cuối cùng của đoạn mã trên với hình ảnh đầu vào của bạn là:

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

Danh sách các điểm đầu tiên mô tả bốn góc của hình chữ nhật màu vàng, và điểm thứ hai liên quan đến hình chữ nhật màu đỏ. Để thực hiện chuyển đổi phối cảnh, bạn có thể sử dụng AForge với chức năng sẵn sàng.Tôi sử dụng ImageMagick vì đơn giản như trong:

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

Mà cho sự liên kết bạn sau (với dòng màu xanh thấy như trước đây để hiển thị tốt hơn các kết quả):

enter image description here

Bạn có thể nhận thấy rằng các đường thẳng đứng màu xanh bên trái không hoàn toàn thẳng, trên thực tế, hai hộp bên trái nhiều nhất không được căn chỉnh 1 pixel ở trục x. Điều này có thể được sửa chữa bởi một nội suy khác nhau được sử dụng trong quá trình chuyển đổi phối cảnh.

+0

Tại http://i.imgur.com/tKLNI.png bạn có thể thấy một số kết quả khác bằng cách sử dụng mã ở trên, với sự khác biệt duy nhất của việc loại bỏ các thành phần được kết nối với border (điều này tạo ra sự khác biệt chỉ khi vẽ các đường màu xanh cuối cùng để hiển thị). – mmgp

+0

Cảm ơn rất nhiều vì đã giúp đỡ, hãy tận hưởng những điểm! – JMK

1

John thư viện Leptonica có nghĩa là rất nhanh và ổn định.
Đây là một liên kết về cách gọi nó từ C# http://www.leptonica.com/vs2008doc/csharp-and-leptonlib.html. Tôi không chắc đây có phải là câu trả lời vì vậy tôi vừa mới thêm vào làm bình luận.

Nó có một LeptonicaCLR.Utils.DeskewBinaryImage() để thực sự deskew a b & w hình ảnh.

Tôi không chắc nó sẽ tốt như thế nào với các biểu mẫu thực sự bạn đang cố xử lý.

+1

Ngoài ra một cuộc thảo luận về việc xác định góc nghiêng bằng cách sử dụng Leptonica: http: //www.leptonica. com/skew-measurement.html – DermFrench

+0

Cảm ơn Dermot, đang đọc – JMK

1

John, Tôi cũng nghĩ rằng việc so khớp mẫu có thể giúp giải quyết vấn đề này (nếu thư viện của Leptonica không đủ tốt).

Aforge.net đã mẫu phù hợp với xây dựng trong: http://www.aforgenet.com/framework/docs/html/17494328-ef0c-dc83-1bc3-907b7b75039f.htm

Trong kiến ​​thức hạn chế của tôi về vấn đề này, bạn muốn có một hình ảnh nguồn của cây trồng số hiệu đăng ký/và thấy nó sử dụng mẫu tương ứng trong các hình ảnh quét. Sau đó bạn có thể cắt hình ảnh của bạn để có được một hình ảnh phụ chỉ là một phần bên trong các nhãn hiệu đăng ký. Đối với hình ảnh mà bạn đã cung cấp ở trên, tôi nghĩ bạn có thể giả sử một hình chữ nhật ban đầu khá nhỏ và chỉ thực hiện mẫu phù hợp trên khu vực được cắt của hình ảnh để giảm tổng thời gian.

Có một số cuộc thảo luận về điều này ở đây: How to Locate Alignment Marks in an Image

Các vấn đề liên quan