Estoy intentando detectar si una foto representa una plantilla de formulario predefinida llena de datos.
Soy nuevo en el procesamiento de imágenes y en OpenCV, pero mi primer bash es usar FlannBasedMatcher y comparar la cantidad de puntos clave detectados.
¿Hay una mejor manera de hacer esto?
lleno-form.jpg
form-template.jpg
import numpy as np import cv2 from matplotlib import pyplot as plt MIN_MATCH_COUNT = 10 img1 = cv2.imread('filled-form.jpg',0) # queryImage img2 = cv2.imread('template-form.jpg',0) # trainImage # Initiate SIFT detector sift = cv2.xfeatures2d.SIFT_create() # find the keypoints and descriptors with SIFT kp1, des1 = sift.detectAndCompute(img1,None) kp2, des2 = sift.detectAndCompute(img2,None) FLANN_INDEX_KDTREE = 1 index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5) search_params = dict(checks = 50) flann = cv2.FlannBasedMatcher(index_params, search_params) matches = flann.knnMatch(des1,des2,k=2) # store all the good matches as per Lowe's ratio test. good = [] for m,n in matches: if m.distance MIN_MATCH_COUNT: print "ALL GOOD!" else: print "Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT) matchesMask = None
Creo que usar SIFT y un emparejador de puntos clave es el enfoque más sólido para este problema. Debería funcionar bien con muchas plantillas de formulario diferentes. Sin embargo, al estar patentado el algoritmo SIFT, aquí hay otro enfoque que también debería funcionar bien:
THRESH_OTSU
. Mat
resultados binarios con la función bitwise_not
. Para las dos Mat
binarias del Paso 1 :
approxPolyDP
para aproximar el contorno encontrado a un cuadrilátero (vea la imagen de arriba).
En mi código, esto se hace dentro de getQuadrilateral()
.
findHomography
warpPerspective
binario de la foto usando warpPerspective
(y la homografía Mat
calculada previamente).
Mat
. Mat
binaria combada y la Mat
binaria del formulario de plantilla dilatada.
Esto permite extraer las informaciones llenas. Pero también puedes hacerlo al revés:
Forma de plantilla – Mat
deformada dilatada
En este caso, el resultado de la resta debe ser totalmente negro. Entonces usaría mean
para obtener el valor de píxel promedio. Finalmente, si ese valor es menor que (digamos) 2, asumiría que el formulario de la foto coincide con el formulario de la plantilla.
Aquí está el código C ++, no debería ser demasiado difícil de traducir a Python 🙂
vector getQuadrilateral(Mat & grayscale) { vector> contours; findContours(grayscale, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); vector indices(contours.size()); iota(indices.begin(), indices.end(), 0); sort(indices.begin(), indices.end(), [&contours](int lhs, int rhs) { return contours[lhs].size() > contours[rhs].size(); }); vector> polygon(1); approxPolyDP(contours[indices[0]], polygon[0], 5, true); if (polygon[0].size() == 4) // we have found a quadrilateral { return(polygon[0]); } return(vector ()); } int main(int argc, char** argv) { Mat templateImg, sampleImg; templateImg = imread("template-form.jpg", 0); sampleImg = imread("sample-form.jpg", 0); Mat templateThresh, sampleTresh; threshold(templateImg, templateThresh, 0, 255, THRESH_OTSU); threshold(sampleImg, sampleTresh, 0, 255, THRESH_OTSU); bitwise_not(templateThresh, templateThresh); bitwise_not(sampleTresh, sampleTresh); vector corners_template = getQuadrilateral(templateThresh); vector corners_sample = getQuadrilateral(sampleTresh); Mat homography = findHomography(corners_sample, corners_template); Mat warpSample; warpPerspective(sampleTresh, warpSample, homography, Size(templateThresh.cols, templateThresh.rows)); Mat element_dilate = getStructuringElement(MORPH_ELLIPSE, Size(8, 8)); dilate(templateThresh, templateThresh, element_dilate); Mat diff = warpSample - templateThresh; imshow("diff", diff); waitKey(0); return 0; }
Espero que sea lo suficientemente claro! 😉
PD Esta gran respuesta me ayudó a recuperar el contorno más grande.