Алгоритм обнаружения углов бумажного листа на фотографии
Каков наилучший способ определения углов счета-фактуры/квитанции/листа бумаги на фотографии? Это должно использоваться для последующей коррекции перспективы до OCR.
Мой текущий подход:
RGB > Серый > Обнаружение Canny Edge с пороговым значением > Dilate (1) > Удалить мелкие объекты (6) > clear boarder objects > выбрать крупный блог на основе Convex Area. > [обнаружение угла - не реализовано]
Я не могу не думать, что для обработки такого типа сегментации должен быть более надежный "интеллектуальный" /статистический подход. У меня нет примеров обучения, но я мог бы, вероятно, собрать 100 изображений.
Более широкий контекст:
Я использую matlab для прототипа и планирую реализовать систему в OpenCV и Tesserect-OCR. Это первый из многих проблем обработки изображений, которые мне нужно решить для этого конкретного приложения. Поэтому я ищу, чтобы сверлить собственное решение и переосмыслить себя алгоритмами обработки изображений.
Вот пример изображения, с которым мне бы хотелось обработать алгоритм: если вы хотите, чтобы большие изображения находились на http://madteckhead.com/tmp
case 1 http://madteckhead.com/tmp/IMG_0773_sml.jpg
case 2 http://madteckhead.com/tmp/IMG_0774_sml.jpg
case 3 http://madteckhead.com/tmp/IMG_0775_sml.jpg
case 4 http://madteckhead.com/tmp/IMG_0776_sml.jpg
В лучшем случае это дает:
case 1 - canny http://madteckhead.com/tmp/IMG_0773_canny.jpg
case 1 - post canny http://madteckhead.com/tmp/IMG_0773_postcanny.jpg
case 1 - крупнейший блог http://madteckhead.com/tmp/IMG_0773_blob.jpg
Однако в других случаях он легко справляется:
case 2 - canny http://madteckhead.com/tmp/IMG_0774_canny.jpg
case 2 - post canny http://madteckhead.com/tmp/IMG_0774_postcanny.jpg
case 2 - крупнейший блог http://madteckhead.com/tmp/IMG_0774_blob.jpg
Спасибо за все замечательные идеи! Я люблю SO!
РЕДАКТИРОВАТЬ: Ход трансформации прогресса
Q: Какой алгоритм будет класть строки hough, чтобы найти углы?
Следуя советам ответов, я смог использовать Преобразование Хафа, выбрать линии и отфильтровать их. Мой нынешний подход довольно груб. Я сделал предположение, что счет-фактура всегда будет меньше, чем 15deg из выравнивания с изображением. В итоге я получаю разумные результаты для строк, если это так (см. Ниже). Но я не совсем уверен в подходящем алгоритме, чтобы скопировать строки (или проголосовать), чтобы экстраполировать их по углам. Линии Hough не являются непрерывными. И в шумовых изображениях могут быть параллельные линии, поэтому требуется некоторая форма или расстояние от метрик начала линии. Любые идеи?
case 1 http://madteckhead.com/tmp/IMG_0773_hough.jpg
case 2 http://madteckhead.com/tmp/IMG_0774_hough.jpg
case 3 http://madteckhead.com/tmp/IMG_0775_hough.jpg
case 4 http://madteckhead.com/tmp/IMG_0776_hough.jpg
Ответы
Ответ 1
Я друг Мартина, который работал над этим в начале этого года. Это был мой первый проект по кодированию, и вроде бы закончилось немного поспешным, поэтому код нуждается в некотором декодировании...
Я дам несколько советов из того, что я видел, что вы делаете уже, а затем соберите мой код завтра в выходной день.
Первый совет, OpenCV
и python
являются удивительными, перейдите к ним как можно скорее.: D
Вместо того, чтобы удалять мелкие объекты и/или шум, опустите канительные ограничения, поэтому он принимает больше ребер, а затем найдет самый большой замкнутый контур (в OpenCV используйте findcontour()
с некоторыми простыми параметрами, я думаю, что использовал CV_RETR_LIST
), может по-прежнему бороться, когда он на белом листе бумаги, но определенно обеспечивает наилучшие результаты.
Для Houghline2()
Transform, попробуйте с CV_HOUGH_STANDARD
в отличие от CV_HOUGH_PROBABILISTIC
, он даст значения rho и theta, определяя линию в полярных координатах, а затем вы можете группировать строки в пределах определенного толерантность к ним.
Моя группировка работала как таблица поиска, для каждой строки, полученной из преобразования hough, она давала бы пару rho и theta. Если эти значения находились внутри, скажем, 5% пары значений в таблице, они были отброшены, если они были вне этого 5%, в таблицу была добавлена новая запись.
Вы можете сделать анализ параллельных линий или расстояние между линиями намного легче.
Надеюсь, что это поможет.
Ответ 2
Студенческая группа в моем университете недавно продемонстрировала приложение для iPhone (и приложение OpenCV для python), которое они написали, чтобы сделать именно это. Как я помню, шаги были примерно такими:
- Медиа-фильтр полностью удаляет текст на бумаге (это был рукописный текст на белой бумаге с довольно хорошим освещением и может не работать с напечатанным текстом, он работал очень хорошо). Причина заключалась в том, что это упрощает обнаружение углов.
- Преобразование Hough для строк
- Найдите пики в пространстве накопителя Hough Transform и рисуйте каждую линию по всему изображению.
- Проанализируйте линии и удалите все, которые очень близки друг к другу и имеют одинаковый угол (сгруппируйте строки в один). Это необходимо, потому что преобразование Hough не идеально, поскольку оно работает в дискретном пространстве образца.
- Найдите пары строк, которые примерно параллельны и пересекают другие пары, чтобы увидеть, какие линии образуют квадратики.
Это выглядело довольно неплохо, и они смогли сделать снимок листа или книги, выполнить обнаружение углов, а затем отобразить документ на изображении на плоскую плоскость почти в реальном времени (был один OpenCV функция для выполнения отображения). Не было OCR, когда я увидел, что он работает.
Ответ 3
Вот что я придумал после нескольких экспериментов:
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 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()
Не совершенен, но по крайней мере работает для всех образцов:
![1]()
![2]()
![3]()
![4]()
Ответ 4
Вместо начала обнаружения края вы можете использовать обнаружение углов.
Marvin Framework обеспечивает реализацию алгоритма Moravec для этой цели. Вы можете найти углы газет в качестве отправной точки. Ниже выхода алгоритма Moravec:
![enter image description here]()
Ответ 5
Также вы можете использовать MSER (максимально стабильные экстремальные области) по результату оператора Собеля, чтобы найти стабильные области изображения. Для каждого региона, возвращаемого MSER, вы можете применить выпуклую оболочку и поли аппроксимацию, чтобы получить следующее:
Но этот вид обнаружения полезен для обнаружения в реальном времени более чем одной картинки, которая не всегда возвращает лучший результат.
![result]()
Ответ 6
После обнаружения края используйте Hough Transform.
Затем поместите эти точки в SVM (поддерживающий векторный механизм) с их метками, если в примерах есть гладкие линии на них, SVM не будет иметь никаких трудностей для разделения необходимых частей примера и других частей. Мой совет по SVM, поместите параметр, такой как связь и длина. То есть, если точки связаны и длинны, они, вероятно, будут линией квитанции. Затем вы можете устранить все остальные моменты.
Ответ 7
Здесь у вас есть код @Vanuan с использованием С++:
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);
Ответ 8
Ответ 9
Найдите версию Java, используя opencv ниже
package testOpenCV;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Image;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class FindContours {
private Mat srcGray = new Mat();
private Mat src_orig_ref = new Mat();
private Mat srcGray_after = new Mat();
private JFrame frame;
private JLabel imgSrcLabel;
private JLabel imgContoursLabel;
private static final int MAX_THRESHOLD = 255;
private int threshold = 40;
private Random rng = new Random(12345);
private Mat src = new Mat();
public FindContours(String[] args) {
//OpenCVNativeLoader
String filename = "C:\\Desktop\\opencv\\4.PNG";
src = Imgcodecs.imread(filename);
src_orig_ref = Imgcodecs.imread(filename);
if (src.empty()) {
System.err.println("Cannot read image: " + filename);
System.exit(0);
}
Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_BGR2GRAY);
Imgproc.blur(srcGray, srcGray, new Size(3, 3));
Mat kernel = new Mat(new Size(3, 3), CvType.CV_8UC1, new Scalar(255));
Imgproc.morphologyEx(srcGray, srcGray, Imgproc.MORPH_OPEN, kernel);
Imgproc.morphologyEx(srcGray, srcGray, Imgproc.MORPH_CLOSE, kernel);
// Create and set up the window.
frame = new JFrame("Finding contours in your image demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Set up the content pane.
Image img = HighGui.toBufferedImage(src);
addComponentsToPane(frame.getContentPane(), img);
// Use the content pane default BorderLayout. No need for
// setLayout(new BorderLayout());
// Display the window.
frame.pack();
frame.setVisible(true);
update();
}
private void addComponentsToPane(Container pane, Image img) {
if (!(pane.getLayout() instanceof BorderLayout)) {
pane.add(new JLabel("Container doesn't use BorderLayout!"));
return;
}
JPanel sliderPanel = new JPanel();
sliderPanel.setLayout(new BoxLayout(sliderPanel, BoxLayout.PAGE_AXIS));
sliderPanel.add(new JLabel("Canny threshold: "));
JSlider slider = new JSlider(0, MAX_THRESHOLD, threshold);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(10);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
threshold = source.getValue();
update();
}
});
sliderPanel.add(slider);
pane.add(sliderPanel, BorderLayout.PAGE_START);
JPanel imgPanel = new JPanel();
imgSrcLabel = new JLabel(new ImageIcon(img));
imgPanel.add(imgSrcLabel);
Mat blackImg = Mat.zeros(srcGray.size(), CvType.CV_8U);
imgContoursLabel = new JLabel(new ImageIcon(HighGui.toBufferedImage(blackImg)));
imgPanel.add(imgContoursLabel);
pane.add(imgPanel, BorderLayout.CENTER);
}
private void update() {
Mat cannyOutput = new Mat();
Imgproc.Canny(srcGray, cannyOutput, threshold, threshold * 2);
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Mat hierarchy = new Mat();
Imgproc.findContours(cannyOutput, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
Mat drawing = Mat.zeros(cannyOutput.size(), CvType.CV_8UC3);
for (int i = 0; i < contours.size(); i++) {
//rng.nextInt(256)
Scalar color = new Scalar(256, 256, 256);
List<MatOfPoint> contours_ele = new ArrayList<MatOfPoint>();
contours_ele.add(contours.get(i));
if(Imgproc.contourArea(contours.get(i))>10) {
Imgproc.drawContours(src, contours_ele , -1, color, 2, Imgproc.LINE_8, hierarchy, 0, new Point());
}
}
Mat cannyOutput_after = new Mat();
Imgproc.cvtColor(src, srcGray_after, Imgproc.COLOR_BGR2GRAY);
Imgproc.blur(srcGray_after, srcGray_after, new Size(3, 3));
Mat kernel = new Mat(new Size(3, 3), CvType.CV_8UC1, new Scalar(255));
Imgproc.morphologyEx(srcGray_after, srcGray_after, Imgproc.MORPH_OPEN, kernel);
Imgproc.morphologyEx(srcGray_after, srcGray_after, Imgproc.MORPH_CLOSE, kernel);
Imgproc.Canny(srcGray_after, cannyOutput_after, threshold, threshold * 2);
List<MatOfPoint> contours_dw = new ArrayList<MatOfPoint>();
Mat hierarchy_dw = new Mat();
Mat drawing_dw = Mat.zeros(cannyOutput.size(), CvType.CV_8UC3);
Imgproc.findContours(cannyOutput_after, contours_dw, hierarchy_dw, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours_dw.size(); i++) {
Scalar color = new Scalar(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256));
if(Imgproc.contourArea(contours_dw.get(i))>100000) {
Imgproc.drawContours(src_orig_ref, contours_dw, i, color, 2, Imgproc.LINE_8, hierarchy, 0, new Point());
}
}
imgContoursLabel.setIcon(new ImageIcon(HighGui.toBufferedImage(src_orig_ref)));
frame.repaint();
}
}
public class hello
{
public static void main( String[] args )
{
System.loadLibrary( Core.NATIVE_LIBRARY_NAME );
Mat mat = Mat.eye( 3, 3, CvType.CV_8UC1 );
System.out.println( "mat = " + mat.dump() );
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new FindContours(null);
}
});
}
}