Thuật toán phát hiện các góc của tờ giấy trong ảnh


97

Cách tốt nhất để phát hiện các góc của hóa đơn / biên lai / tờ giấy trong ảnh là gì? Điều này sẽ được sử dụng để hiệu chỉnh phối cảnh tiếp theo, trước OCR.

Cách tiếp cận hiện tại của tôi là:

RGB> Xám> Phát hiện cạnh Canny với ngưỡng> Dilate (1)> Loại bỏ các đối tượng nhỏ (6)> xóa các đối tượng nội trú> chọn blog lớn dựa trên Khu vực Lồi. > [phát hiện góc - Không được triển khai]

Tôi không thể không nghĩ rằng phải có một cách tiếp cận thống kê / 'thông minh' mạnh mẽ hơn để xử lý loại phân đoạn này. Tôi không có nhiều ví dụ đào tạo, nhưng tôi có thể có được 100 hình ảnh với nhau.

Bối cảnh rộng lớn hơn:

Tôi đang sử dụng matlab để làm nguyên mẫu và dự định triển khai hệ thống trong OpenCV và Tesserect-OCR. Đây là vấn đề đầu tiên trong số các vấn đề về xử lý hình ảnh mà tôi cần giải quyết cho ứng dụng cụ thể này. Vì vậy, tôi đang tìm cách đưa ra giải pháp của riêng mình và làm quen lại với các thuật toán xử lý hình ảnh.

Dưới đây là một số hình ảnh mẫu mà tôi muốn thuật toán xử lý: Nếu bạn muốn thực hiện thử thách, các hình ảnh lớn có tại http://madteckhead.com/tmp

trường hợp 1
(nguồn: madteckhead.com )

trường hợp 2
(nguồn: madteckhead.com )

trường hợp 3
(nguồn: madteckhead.com )

trường hợp 4
(nguồn: madteckhead.com )

Trong trường hợp tốt nhất, điều này mang lại:

trường hợp 1 - canny
(nguồn: madteckhead.com )

trường hợp 1 - bài canny
(nguồn: madteckhead.com )

trường hợp 1 - blog lớn nhất
(nguồn: madteckhead.com )

Tuy nhiên, nó dễ dàng thất bại trong các trường hợp khác:

trường hợp 2 - canny
(nguồn: madteckhead.com )

trường hợp 2 - post canny
(nguồn: madteckhead.com )

trường hợp 2 - blog lớn nhất
(nguồn: madteckhead.com )

Cảm ơn trước cho tất cả những ý tưởng tuyệt vời! Tôi yêu SO!

CHỈNH SỬA: Tiến trình chuyển đổi Hough

H: Thuật toán nào sẽ phân cụm các đường hough để tìm các góc? Theo lời khuyên từ các câu trả lời, tôi đã có thể sử dụng Hough Transform, chọn các dòng và lọc chúng. Cách tiếp cận hiện tại của tôi là khá thô. Tôi đã giả định rằng hóa đơn sẽ luôn nhỏ hơn 15deg so với hình ảnh. Tôi kết thúc với kết quả hợp lý cho các dòng nếu trường hợp này xảy ra (xem bên dưới). Nhưng tôi không hoàn toàn chắc chắn về một thuật toán phù hợp để phân cụm các đường (hoặc bỏ phiếu) để ngoại suy cho các góc. Các đường Hough không liên tục. Và trong các hình ảnh nhiễu, có thể có các đường thẳng song song vì vậy cần có một số hình thức hoặc khoảng cách từ số liệu gốc đường. Bất kỳ ý tưởng?

trường hợp 1 trường hợp 2 trường hợp 3 trường hợp 4
(nguồn: madteckhead.com )


1
Có, tôi đã nhận được nó để làm việc trong khoảng 95% trường hợp. Kể từ đó, tôi đã phải tạm dừng mã do thiếu thời gian. Tôi sẽ đăng bài theo dõi ở một số giai đoạn, vui lòng giao cho tôi nếu bạn cần trợ giúp khẩn cấp. Xin lỗi vì thiếu sự theo dõi tốt. Tôi muốn quay lại làm việc với tính năng này.
Nathan Keller

Nathan, bạn có thể vui lòng đăng bài theo dõi xem bạn đã làm việc đó như thế nào không? Tôi đã mắc kẹt ở cùng một điểm nhận ra các góc / đường viền ngoài của tờ giấy. Tôi gặp phải những vấn đề giống hệt như bạn đã làm nên tôi rất muốn tìm ra giải pháp.
tim

6
Tất cả các hình ảnh trong bài đăng này hiện là 404.
ChrisF

Câu trả lời:


28

Tôi là bạn của Martin, người đã làm việc này vào đầu năm nay. Đây là dự án viết mã đầu tiên của tôi và hơi vội vàng kết thúc, vì vậy mã cần một số giải mã ... sai sót ... Tôi sẽ đưa ra một vài mẹo từ những gì tôi đã thấy bạn đang làm, và sau đó sắp xếp mã của tôi vào ngày nghỉ ngày mai.

Mẹo đầu tiên OpenCVpythonthật tuyệt vời, hãy chuyển đến chúng càng sớm càng tốt. : D

Thay vì loại bỏ các đối tượng nhỏ và hoặc tiếng ồn, hãy hạ thấp các giới hạn canny, để nó chấp nhận nhiều cạnh hơn, và sau đó tìm đường bao kín lớn nhất (trong sử dụng OpenCV findcontour()với một số tham số đơn giản, tôi nghĩ tôi đã sử dụng CV_RETR_LIST). có thể vẫn gặp khó khăn khi nó trên một tờ giấy trắng, nhưng chắc chắn mang lại kết quả tốt nhất.

Đối với Houghline2()Transform, hãy thử với điều CV_HOUGH_STANDARDngược lại với CV_HOUGH_PROBABILISTIC, nó sẽ cung cấp các giá trị rhotheta , xác định đường theo tọa độ cực và sau đó bạn có thể nhóm các đường trong một dung sai nhất định cho các giá trị đó.

Nhóm của tôi hoạt động như một bảng tra cứu, đối với mỗi dòng xuất ra từ phép biến đổi hough, nó sẽ cho một cặp rho và theta. Nếu các giá trị này nằm trong 5% của một cặp giá trị trong bảng, thì chúng sẽ bị loại bỏ, nếu nằm ngoài 5% đó, một mục mới sẽ được thêm vào bảng.

Sau đó, bạn có thể thực hiện phân tích các đường song song hoặc khoảng cách giữa các đường dễ dàng hơn nhiều.

Hi vọng điêu nay co ich.


Xin chào Daniel, cảm ơn vì đã tham gia. Tôi thích bạn tiếp cận. nó thực sự là lộ trình tôi đang nhận được kết quả tốt vào lúc này. Thậm chí, có cả ví dụ OpenCV đã phát hiện ra các hình chữ nhật. Chỉ cần thực hiện một số lọc kết quả. như bạn đã nói màu trắng trên nền trắng rất khó phát hiện bằng phương pháp này. Nhưng đó là một cách tiếp cận đơn giản và ít tốn kém hơn so với phương pháp hough. Tôi thực sự đã bỏ cách tiếp cận hough ra khỏi thuật toán của mình và đã thực hiện một phép gần đúng đa, hãy xem ví dụ hình vuông trong opencv. Tôi muốn thấy bạn thực hiện bỏ phiếu hough. Cảm ơn trước, Nathan
Nathan Keller

Tôi đã gặp vấn đề với cách tiếp cận này, tôi sẽ đăng một giải pháp nếu tôi có thể nghĩ ra một cái gì đó tốt hơn để tham khảo trong tương lai
Anshuman Kumar

@AnshumanKumar Tôi thực sự cần trợ giúp về câu hỏi này, bạn có thể giúp tôi được không? stackoverflow.com/questions/61216402/…
Carlos Diego

19

Một nhóm sinh viên tại trường đại học của tôi gần đây đã trình diễn một ứng dụng iPhone (và ứng dụng python OpenCV) mà họ đã viết để thực hiện chính xác điều này. Theo tôi nhớ, các bước như sau:

  • Bộ lọc trung vị để loại bỏ hoàn toàn văn bản trên giấy (đây là văn bản viết tay trên giấy trắng với ánh sáng khá tốt và có thể không hoạt động với văn bản in, nó hoạt động rất tốt). Lý do là nó làm cho việc phát hiện góc dễ dàng hơn nhiều.
  • Hough Transform cho các dòng
  • Tìm các đỉnh trong không gian tích lũy Hough Transform và vẽ từng đường trên toàn bộ hình ảnh.
  • Phân tích các đường và loại bỏ bất kỳ đường nào rất gần nhau và ở một góc tương tự (nhóm các đường thành một). Điều này là cần thiết vì Hough Transform không hoàn hảo vì nó hoạt động trong không gian mẫu rời rạc.
  • Tìm các cặp đường gần như song song và cắt các cặp khác để xem đường nào tạo thành tứ.

Điều này dường như hoạt động khá tốt và họ có thể chụp ảnh một mảnh giấy hoặc sách, thực hiện phát hiện góc và sau đó ánh xạ tài liệu trong ảnh lên một mặt phẳng gần như theo thời gian thực (chỉ có một chức năng OpenCV duy nhất để thực hiện ánh xạ). Không có OCR khi tôi thấy nó hoạt động.


Cảm ơn những ý tưởng tuyệt vời Martin. Ive đã nghe theo lời khuyên của bạn và triển khai phương pháp chuyển đổi Hough. (Xem kết quả ở trên). Tôi đang đấu tranh để xác định một thuật toán mạnh mẽ sẽ ngoại suy các đường để tìm các giao điểm. Không có nhiều dòng, và một số dương tính giả. Bạn có lời khuyên nào về cách tôi có thể hợp nhất và loại bỏ các dòng tốt nhất không? Nếu sinh viên của bạn quan tâm, vui lòng khuyến khích họ liên hệ. Tôi muốn nghe kinh nghiệm của họ trong việc làm cho các thuật toán chạy trên nền tảng di động. (Đó là mục tiêu tiếp theo của tôi). Rất cám ơn ý kiến ​​của bạn.
Nathan Keller

1
Có vẻ như HT cho các dòng đã hoạt động tốt trong tất cả trừ hình ảnh thứ hai của bạn, nhưng bạn có đang xác định ngưỡng dung sai cho các giá trị bắt đầu và kết thúc của mình trong bộ tích lũy không? HT không thực sự xác định vị trí bắt đầu và kết thúc, thay vì các giá trị m và c trong y = mx + c. Xem tại đây - lưu ý rằng điều này đang sử dụng tọa độ cực trong bộ tích lũy thay vì cacte. Bằng cách này, bạn có thể nhóm các đường theo c và sau đó theo m để làm mỏng chúng và bằng cách tưởng tượng các đường kéo dài trên toàn bộ hình ảnh, bạn sẽ tìm thấy các giao điểm hữu ích hơn.
Martin Foot

@MartinFoot Tôi thực sự cần trợ giúp về câu hỏi này, bạn có thể giúp tôi được không? stackoverflow.com/questions/61216402/…
Carlos Diego

16

Đây là những gì tôi nghĩ ra sau một chút thử nghiệm:

import cv, cv2, numpy as np
import sys

def get_new(old):
    new = np.ones(old.shape, np.uint8)
    cv2.bitwise_not(new,new)
    return new

if __name__ == '__main__':
    orig = cv2.imread(sys.argv[1])

    # these constants are carefully picked
    MORPH = 9
    CANNY = 84
    HOUGH = 25

    img = cv2.cvtColor(orig, cv2.COLOR_BGR2GRAY)
    cv2.GaussianBlur(img, (3,3), 0, img)


    # this is to recognize white on white
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(MORPH,MORPH))
    dilated = cv2.dilate(img, kernel)

    edges = cv2.Canny(dilated, 0, CANNY, apertureSize=3)

    lines = cv2.HoughLinesP(edges, 1,  3.14/180, HOUGH)
    for line in lines[0]:
         cv2.line(edges, (line[0], line[1]), (line[2], line[3]),
                         (255,0,0), 2, 8)

    # finding contours
    contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL,
                                   cv.CV_CHAIN_APPROX_TC89_KCOS)
    contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours)
    contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)

    # simplify contours down to polygons
    rects = []
    for cont in contours:
        rect = cv2.approxPolyDP(cont, 40, True).copy().reshape(-1, 2)
        rects.append(rect)

    # that's basically it
    cv2.drawContours(orig, rects,-1,(0,255,0),1)

    # show only contours
    new = get_new(img)
    cv2.drawContours(new, rects,-1,(0,255,0),1)
    cv2.GaussianBlur(new, (9,9), 0, new)
    new = cv2.Canny(new, 0, CANNY, apertureSize=3)

    cv2.namedWindow('result', cv2.WINDOW_NORMAL)
    cv2.imshow('result', orig)
    cv2.waitKey(0)
    cv2.imshow('result', dilated)
    cv2.waitKey(0)
    cv2.imshow('result', edges)
    cv2.waitKey(0)
    cv2.imshow('result', new)
    cv2.waitKey(0)

    cv2.destroyAllWindows()

Không hoàn hảo, nhưng ít nhất hoạt động cho tất cả các mẫu:

1 2 3 4


4
Tôi đang thực hiện dự án tương tự. Tôi chạy phía trên mã và nó cho tôi lỗi "Không có mô-đun nào có tên là cv". Tôi đã cài đặt phiên bản Open CV 2.4 và nhập cv2 đang hoạt động hoàn hảo cho tôi.
Navneet Singh

Bạn có đủ tử tế để cập nhật mã này để nó hoạt động không? pastebin.com/PMH5Y0M8 nó chỉ cho tôi một trang đen.
the7erm

Bạn có bất cứ ý tưởng về làm thế nào để chuyển đổi mã sau vào java: for line in lines[0]: cv2.line(edges, (line[0], line[1]), (line[2], line[3]), (255,0,0), 2, 8) # finding contours contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL, cv.CV_CHAIN_APPROX_TC89_KCOS) contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours) contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)
aurelianr

Vanuan tôi thực sự cần giúp đỡ về câu hỏi này, bạn có thể giúp tôi được không? stackoverflow.com/questions/61216402/…
Carlos Diego

9

Thay vì bắt đầu từ phát hiện cạnh, bạn có thể sử dụng phát hiện Góc.

Marvin Framework cung cấp việc triển khai thuật toán Moravec cho mục đích này. Bạn có thể tìm thấy các góc của tờ giấy như một điểm bắt đầu. Dưới đầu ra của thuật toán Moravec:

nhập mô tả hình ảnh ở đây


4

Ngoài ra, bạn có thể sử dụng MSER (Các vùng cực ổn định tối đa) trên kết quả toán tử Sobel để tìm các vùng ổn định của hình ảnh. Đối với mỗi vùng do MSER trả về, bạn có thể áp dụng xấp xỉ lồi và xấp xỉ poly để thu được một số như sau:

Nhưng kiểu phát hiện này hữu ích cho việc phát hiện trực tiếp hơn là một bức ảnh không phải lúc nào cũng trả về kết quả tốt nhất.

kết quả


1
Bạn có thể chia sẻ một số chi tiết cho việc này có lẽ một số mã, nhờ một bó trước
Monty

Tôi gặp lỗi trong cv2.CHAIN_APPROX_SIMPLE cho biết quá nhiều giá trị cần giải nén. Bất kỳ ý tưởng? Tôi đang sử dụng hình ảnh 1024 * 1024 làm mẫu của mình
Praveen

1
Cảm ơn tất cả, bạn chỉ cần tìm ra sự thay đổi cú pháp trong nhánh Opencv hiện tại answers.opencv.org/question/40329/…
Praveen

MSER không phải là để giải nén các đốm màu? Tôi đã thử nó và nó chỉ phát hiện hầu hết văn bản
Anshuman Kumar

3

Sau khi phát hiện cạnh, hãy sử dụng Hough Transform. Sau đó, đặt các điểm đó vào một SVM (máy vectơ hỗ trợ) với nhãn của chúng, nếu ví dụ có các đường trơn trên chúng, SVM sẽ không gặp khó khăn gì để phân chia các phần cần thiết của ví dụ và các phần khác. Lời khuyên của tôi về SVM, hãy đặt một tham số như kết nối và độ dài. Có nghĩa là, nếu các điểm được kết nối và dài, chúng có khả năng là một dòng của biên nhận. Sau đó, bạn có thể loại bỏ tất cả các điểm khác.


Xin chào Ares, cảm ơn vì những ý tưởng của bạn! Tôi đã thực hiện chuyển đổi Hough (xem ở trên). Tôi không thể tìm ra một cách hiệu quả để tìm ra các góc được cho là dương tính giả và các đường không liên tục. Bạn có ý kiến ​​gì thêm không? Đã một thời gian dài kể từ khi tôi xem xét các kỹ thuật SVM. Đây có phải là cách tiếp cận có giám sát không? Tôi không có bất kỳ dữ liệu đào tạo nào, nhưng tôi có thể tạo một số. Tôi muốn khám phá cách tiếp cận này vì tôi muốn tìm hiểu thêm về SVM. Bạn có thể giới thiệu bất kỳ nguồn lực. Trân trọng. Nathan
Nathan Keller

3

Ở đây bạn có mã của @Vanuan bằng C ++:

cv::cvtColor(mat, mat, CV_BGR2GRAY);
cv::GaussianBlur(mat, mat, cv::Size(3,3), 0);
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Point(9,9));
cv::Mat dilated;
cv::dilate(mat, dilated, kernel);

cv::Mat edges;
cv::Canny(dilated, edges, 84, 3);

std::vector<cv::Vec4i> lines;
lines.clear();
cv::HoughLinesP(edges, lines, 1, CV_PI/180, 25);
std::vector<cv::Vec4i>::iterator it = lines.begin();
for(; it!=lines.end(); ++it) {
    cv::Vec4i l = *it;
    cv::line(edges, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), cv::Scalar(255,0,0), 2, 8);
}
std::vector< std::vector<cv::Point> > contours;
cv::findContours(edges, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_TC89_KCOS);
std::vector< std::vector<cv::Point> > contoursCleaned;
for (int i=0; i < contours.size(); i++) {
    if (cv::arcLength(contours[i], false) > 100)
        contoursCleaned.push_back(contours[i]);
}
std::vector<std::vector<cv::Point> > contoursArea;

for (int i=0; i < contoursCleaned.size(); i++) {
    if (cv::contourArea(contoursCleaned[i]) > 10000){
        contoursArea.push_back(contoursCleaned[i]);
    }
}
std::vector<std::vector<cv::Point> > contoursDraw (contoursCleaned.size());
for (int i=0; i < contoursArea.size(); i++){
    cv::approxPolyDP(Mat(contoursArea[i]), contoursDraw[i], 40, true);
}
Mat drawing = Mat::zeros( mat.size(), CV_8UC3 );
cv::drawContours(drawing, contoursDraw, -1, cv::Scalar(0,255,0),1);

Định nghĩa biến dòng ở đâu? Phải là dòng std :: vector <cv :: Vec4i>;
Có thể Ürek

@ CanÜrek Bạn nói đúng. std::vector<cv::Vec4i> lines;được khai báo trong phạm vi toàn cầu trong dự án của tôi.
GBF_Gabriel

1
  1. Chuyển đổi sang không gian phòng thí nghiệm

  2. Sử dụng cụm kmeans phân đoạn 2

  3. Sau đó, sử dụng các đường viền hoặc hough trên một trong các cụm (intenral)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.