[Tutorial de OpenCV] Ingeniería de funciones

Directorio de artículos


1. Coincidencia de plantillas

1.1 Principio

La imagen de la plantilla se mueve desde el origen a la imagen original y se calcula la diferencia entre la plantilla y la imagen original cubierta por la plantilla . Existen varios métodos de cálculo y luego los resultados de cada cálculo se colocan en la matriz de salida. Si la imagen original es de tamaño A*B y la plantilla es de tamaño a*b, la matriz de salida es de tamaño (A-a+1)*(B-b+1) .

1.2API

CV_EXPORTS_W void matchTemplate( InputArray image, InputArray templ,
                                 OutputArray result, int method, InputArray mask = noArray() );
  • Los parámetros son los siguientes.
parámetro significado
imagen Imagen de entrada, tipo de datos Mat
templo(plantilla) Imagen de plantilla, tipo de datos Mat
resultado Matriz de salida, la profundidad es CV_32FC1 . Si la imagen original es de tamaño A*B y la plantilla es de tamaño a*b, la matriz de salida es (A-a+1)*(B-b+1) .
método Método de cálculo de coincidencia de plantillas. Vea los detalles abajo
mascarilla Imagen de máscara. Debe tener el mismo tamaño que la imagen de la plantilla y debe estar en escala de grises . Al hacer coincidir, el algoritmo de coincidencia funciona para píxeles de la máscara que no son 0. El algoritmo de coincidencia no funciona para posiciones de píxeles con un valor de gris de 0 en la máscara.

1.3 Método de cálculo de coincidencia de plantillas

enum TemplateMatchModes {
    
    
    TM_SQDIFF        = 0, 
    TM_SQDIFF_NORMED = 1, 
    TM_CCORR         = 2, 
    TM_CCORR_NORMED  = 3, 
    TM_CCOEFF        = 4, 
    TM_CCOEFF_NORMED = 5 
};

  • Los valores opcionales para el método son los siguientes
valor opcional del método significado
TM_SQDIFF Calcule el error al cuadrado: cuanto menor sea el valor calculado, mejor será la coincidencia.
TM_CCORR Calcule la correlación: cuanto mayor sea el valor calculado, mejor será la coincidencia.
TM_CCOEFF Calcule el coeficiente de correlación: cuanto mayor sea el valor calculado, mejor será la coincidencia.
TM_SQDIFF_NORMED Calcule el error cuadrático normalizado. Cuanto más cerca esté el valor calculado de 0, mejor será la coincidencia.
TM_CCORR_NORMED Calcule la correlación normalizada. Cuanto más cerca esté el valor calculado de 1, mejor será la coincidencia.
TM_CCOEFF_NORMED Calcule el coeficiente de correlación normalizado. Cuanto más cerca esté el valor calculado de 1, mejor será la coincidencia.

Insertar descripción de la imagen aquí

1.4 Uso de mascarillas

Al realizar una coincidencia de características, a veces no necesitamos usar la imagen completa como plantilla, porque el fondo de la plantilla puede interferir con los resultados de la coincidencia . Por lo tanto, necesitamos agregar una máscara para bloquear el fondo para la coincidencia de plantillas.

conseguir mascara

  1. Convertir imagen de plantilla a escala de grises
  2. Fondo de enmascaramiento binario

1.5 Efecto

        Mat xuenai = imread("xuenai.jpg");
        imshow("xuenai",xuenai);
        
        Mat templ= imread("xuenai_rect.jpg");
        imshow("template",templ);
        Mat match_result;
        matchTemplate(xuenai,templ,match_result,TM_SQDIFF);

        Point temLoc;
        Point minLoc;
        Point maxLoc;
        double min,max;
        minMaxLoc(match_result,&min,&max,&minLoc,&maxLoc);
        temLoc=minLoc;

        rectangle(xuenai,Rect(temLoc.x,temLoc.y,templ.cols,templ.rows),Scalar(0,0,255));
        imshow("xuenai_match",xuenai);
        waitKey();

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

1.5 Defectos de coincidencia de plantillas

No puedo manejar el giro

        Mat xuenai = imread("xuenai.jpg");
        rotate(xuenai,xuenai,ROTATE_90_CLOCKWISE);
        imshow("xuenai",xuenai);

        Mat templ= imread("xuenai_rect.jpg");
        Mat match_result;
        matchTemplate(xuenai,templ,match_result,TM_SQDIFF);

        Point temLoc;
        Point minLoc;
        Point maxLoc;
        double min,max;
        minMaxLoc(match_result,&min,&max,&minLoc,&maxLoc);
        temLoc=minLoc;

        rectangle(xuenai,Rect(temLoc.x,temLoc.y,templ.cols,templ.rows),Scalar(0,0,255));
        imshow("xuenai_match",xuenai);
        waitKey();

Insertar descripción de la imagen aquí

No puedo manejar el zoom

        Mat xuenai = imread("xuenai.jpg");
        resize(xuenai,xuenai,Size(500,500));
        imshow("xuenai",xuenai);

        Mat templ= imread("xuenai_rect.jpg");
        Mat match_result;
        matchTemplate(xuenai,templ,match_result,TM_SQDIFF);

        Point temLoc;
        Point minLoc;
        Point maxLoc;
        double min,max;
        minMaxLoc(match_result,&min,&max,&minLoc,&maxLoc);
        temLoc=minLoc;

        rectangle(xuenai,Rect(temLoc.x,temLoc.y,templ.cols,templ.rows),Scalar(0,0,255));
        imshow("xuenai_match",xuenai);
        waitKey();

Insertar descripción de la imagen aquí

2.cornerHarris (para imágenes en escala de grises)

2.1 Descripción de los puntos de esquina

  • El píxel correspondiente al máximo local de la primera derivada (es decir, el gradiente de escala de grises);
  • La intersección de dos o más aristas;
  • Puntos de la imagen donde la tasa de cambio tanto del valor del gradiente como de la dirección del gradiente es muy alta;
  • La primera derivada en el punto de la esquina es la más grande y la segunda derivada es cero, lo que indica la dirección en la que el borde del objeto cambia de forma discontinua.

2.2 Principios (conocimiento previo: álgebra lineal) (bolcksize=2)

Utilice una ventana fija para deslizarse en cualquier dirección de la imagen. Compare las dos situaciones antes y después del deslizamiento. El grado de cambio de escala de grises de los píxeles en la ventana. Si se desliza en cualquier dirección, habrá un gran cambio de escala de grises. Entonces podemos pensar que hay puntos de esquina en esta ventana.
Considere una imagen en escala de grises. Al deslizar la ventana (con desplazamientos en la dirección x y en la dirección x) se calculan los cambios en la escala de grises de los píxeles.

Insertar descripción de la imagen aquí

en:

  • w(x,y) es la ventana en la posición (x,y)
  • I(x,y) es la intensidad en (x,y)
  • I(x+u,y+v) es la intensidad en la ventana movida (x+u,y+v)、

Para encontrar ventanas con puntos de esquina, busque ventanas con grandes cambios en la escala de grises de píxeles. Por lo tanto, esperamos maximizar la siguiente ecuación:
Insertar descripción de la imagen aquí
Expansión de Taylor:
Insertar descripción de la imagen aquí

  • Ix, Iy son las primeras derivadas calculadas por el operador sobel

Matrizamiento:
Insertar descripción de la imagen aquí
Se obtiene forma cuadrática:
Insertar descripción de la imagen aquí
por lo tanto hay una ecuación:
Insertar descripción de la imagen aquí
se calcula un valor en cada ventana. Este valor determina si los puntos de las esquinas se incluyen en esta ventana.
Insertar descripción de la imagen aquí

Entre ellos, det(M) = determinante de la matriz M, trace(M) = traza de la matriz M

  • Cuando R es un valor positivo, se detectan puntos de esquina, cuando R es negativo, se detectan bordes y cuando R es pequeño, se detectan áreas planas.

2.3 API

CV_EXPORTS_W void cornerHarris( InputArray src, OutputArray dst, int blockSize,
                                int ksize, double k,
                                int borderType = BORDER_DEFAULT );
  • Los parámetros son los siguientes.
parámetro significado
origen (fuente) Imagen de entrada (imagen en escala de grises) , requisito de profundidad: CV_8UC1 o CV_32FC1
horario de verano (destino) Imagen de salida, tipo de datos Mat
bolckTamaño El tamaño de la ventana de detección. Cuanto más grande es, más sensible es a los puntos de las esquinas. Generalmente es 2.
ksize(tamaño del núcleo) El tamaño del filtro cuando se utiliza el operador sobel para calcular la primera derivada es generalmente 3.
k Generalmente se reconoce que el coeficiente utilizado en el cálculo está entre 0,02 y 0,06.
tipo de borde Método de relleno de borde, el valor predeterminado es borde negro.

2.4 Proceso

  1. Convertir a imagen en escala de grises
  2. Detectar usando la función cornerHarris
  3. Utilice la función de normalización para normalizar y convertirScaleAbs a absoluto
  4. Recorra la imagen de salida y filtre los puntos de las esquinas. No utilice el recorrido del iterador, ¡es demasiado lento!
  • Después de las pruebas reales, el siguiente método transversal para llamar a la función ptr con el número de filas es el más rápido.
        Mat xuenai = imread("xuenai.jpg");
        imshow("xuenai", xuenai);

//转灰度图
        Mat xuenai_gray(xuenai.size(),xuenai.type());
        cvtColor(xuenai,xuenai_gray,COLOR_BGR2GRAY);

        Mat xuenai_harris;
        cornerHarris(xuenai_gray,xuenai_harris,2,3,0.04);
        normalize(xuenai_harris,xuenai_harris,0,255,NORM_MINMAX,-1);
        convertScaleAbs(xuenai_harris,xuenai_harris);

        namedWindow("xuenai_harris");
        createTrackbar("threshold","xuenai_harris", nullptr,255);

        while (1) {
    
    
            int thres = getTrackbarPos("threshold", "xuenai_harris");
            if(thres==0)thres=100;
            Mat harris_result=xuenai.clone();
            for(int i=0;i<xuenai_harris.rows;i++){
    
    
                uchar * ptr =xuenai_harris.ptr(i);
                for(int j=0;j<xuenai_harris.cols;j++){
    
    
                    int value=(int) *ptr;
                    if(value>thres){
    
    
                        circle(harris_result, Point(j,i), 3, Scalar(0, 0, 255));
                    }
                    ptr++;
                }
            }
            imshow("xuenai_harris",harris_result);
            if (waitKey(0) == 'q')break;
        }

Insertar descripción de la imagen aquí

2.5 Ventajas y Desventajas

código de prueba

        Mat xuenai = imread("xuenai.jpg");
        imshow("xuenai", xuenai);
        namedWindow("panel");
        createTrackbar("threshold","panel", nullptr,255);
        createTrackbar("angle","panel", nullptr,360);
        createTrackbar("width","panel", nullptr,1000);
        createTrackbar("height","panel", nullptr,1000);

        while (1) {
    
    
            int thres = getTrackbarPos("threshold", "panel");
            if(thres==0)thres=100;
            int width = getTrackbarPos("width", "panel");
            if(width==0)width=xuenai.cols;
            int height = getTrackbarPos("height", "panel");
            if(height==0)height=xuenai.rows;
            int angle = getTrackbarPos("angle","panel");
            

            Mat xuenai_harris, xuenai_transform=xuenai.clone();

            resize(xuenai_transform,xuenai_transform,Size(width,height));

            Mat M= getRotationMatrix2D(Point2f(xuenai.cols/2,xuenai.rows/2),angle,1);
            warpAffine(xuenai_transform,xuenai_transform,M,xuenai_transform.size());

            Mat xuenai_gray(xuenai.size(),xuenai.type());
            cvtColor(xuenai_transform,xuenai_gray,COLOR_BGR2GRAY);

            cornerHarris(xuenai_gray,xuenai_harris,2,3,0.04);
            normalize(xuenai_harris,xuenai_harris,0,255,NORM_MINMAX,-1);
            convertScaleAbs(xuenai_harris,xuenai_harris);

            Mat harris_result=xuenai_transform.clone();
            for(int i=0;i<xuenai_harris.rows;i++){
    
    
                uchar * ptr =xuenai_harris.ptr(i);
                for(int j=0;j<xuenai_harris.cols;j++){
    
    
                    int value=(int) *ptr;
                    if(value>thres){
    
    
                        circle(harris_result, Point(j,i), 3, Scalar(0, 0, 255));
                    }
                    ptr++;
                }
            }
            imshow("xuenai_harris",harris_result);
            if (waitKey(0) == 'q')break;
        }

La imagen se gira y las esquinas permanecen sin cambios.

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

Escalado de imagen, puntos de esquina cambiados.

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

3.Shi-Tomasi (para imágenes en escala de grises)

3.1 Principio

Dado que la estabilidad de la verificación de esquinas de cornerHarris está estrechamente relacionada con k, y k es un valor empírico, es difícil establecer el valor óptimo. Shi-Tomasi realizó mejoras en este punto.

  • Calcular puntos de esquina
    Insertar descripción de la imagen aquí

3.2 API

CV_EXPORTS_W void goodFeaturesToTrack( InputArray image, OutputArray corners,
                                     int maxCorners, double qualityLevel, double minDistance,
                                     InputArray mask = noArray(), int blockSize = 3,
                                     bool useHarrisDetector = false, double k = 0.04 );

CV_EXPORTS_W void goodFeaturesToTrack( InputArray image, OutputArray corners,
                                     int maxCorners, double qualityLevel, double minDistance,
                                     InputArray mask, int blockSize,
                                     int gradientSize, bool useHarrisDetector = false,
                                     double k = 0.04 );
  • Los parámetros son los siguientes.
parámetro significado
imagen Imagen de entrada (imagen en escala de grises) , requisito de profundidad: CV_8UC1 o CV_32FC1
esquinas Genera el conjunto de puntos de puntos de esquina, tipo de datos vector<Point2f>
maxEsquinas Controle el límite superior del conjunto de puntos de esquina de salida, es decir, controle las esquinas.size (). Introduzca 0 para indicar que no hay límite superior
nivel de calidad Coeficiente de calidad (un número positivo inferior a 1,0, generalmente entre 0,01 y 0,1), que indica el nivel mínimo de calidad de los puntos de esquina aceptables . Este coeficiente se multiplica por la puntuación máxima de las esquinas en la imagen de entrada como puntuación mínima aceptable; por ejemplo, si la puntuación máxima de las esquinas en la imagen de entrada es 1500 y el factor de calidad es 0,01, entonces todas las esquinas con una puntuación de esquina inferior a 15 serán ignorados.
minDistancia Distancia euclidiana mínima entre puntos de esquina; los puntos menores que esta distancia se ignorarán .
mascarilla Imagen de máscara. Debe tener el mismo tamaño que la imagen de entrada y debe estar en escala de grises . Al realizar el cálculo, el algoritmo funciona para píxeles distintos de cero en la máscara y no funciona para posiciones de píxeles con un valor de gris de 0 en la máscara.
tamaño de bloque El tamaño de la ventana de detección. Cuanto más grande es, más sensible es a los puntos de las esquinas .
utilizarHarrisDetector Se utiliza para especificar el método de detección de esquinas. Si es verdadero, use la detección de esquinas de Harris, y si es falso, use el algoritmo de Shi Tomasi. El valor predeterminado es Falso.
k El valor predeterminado es 0.04, solo funciona cuando el parámetro useHarrisDetector es verdadero.

3.3 Proceso

  1. Convertir a imagen en escala de grises
  2. Detección mediante la función Shi-Tomasi
  3. Simplemente recorra el conjunto de puntos de esquina

3.4 Efecto

  • Shi-Tomasi también tiene invariancia de rotación y variabilidad de escala.
        Mat xuenai = imread("xuenai.jpg");
        imshow("xuenai", xuenai);
        namedWindow("panel");
        createTrackbar("threshold","panel", nullptr,255);
        createTrackbar("angle","panel", nullptr,360);
        createTrackbar("width","panel", nullptr,1000);
        createTrackbar("height","panel", nullptr,1000);

        while (1) {
    
    
            int thres = getTrackbarPos("threshold", "panel");
            if(thres==0)thres=100;
            int width = getTrackbarPos("width", "panel");
            if(width==0)width=xuenai.cols;
            int height = getTrackbarPos("height", "panel");
            if(height==0)height=xuenai.rows;
            int angle = getTrackbarPos("angle","panel");

            Mat xuenai_transform=xuenai.clone();

            resize(xuenai_transform,xuenai_transform,Size(width,height));

            Mat M= getRotationMatrix2D(Point2f(xuenai.cols/2,xuenai.rows/2),angle,1);
            warpAffine(xuenai_transform,xuenai_transform,M,xuenai_transform.size());

            Mat xuenai_gray(xuenai.size(),xuenai.type());
            cvtColor(xuenai_transform,xuenai_gray,COLOR_BGR2GRAY);

            vector<Point2f>xuenai_cornersSet;
            goodFeaturesToTrack(xuenai_gray,xuenai_cornersSet,0,0.1,10);
            for(auto corner:xuenai_cornersSet){
    
    
                circle(xuenai_transform,corner,3,Scalar(0,0,255));
            }

            imshow("xuenai_corners",xuenai_transform);
            if (waitKey(0) == 'q')break;
        }

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

4.SIFT y SURF (para imágenes en escala de grises)

4.1 Descripción general

Ni cornerHarris ni Shi-Tomasi pueden garantizar la estabilidad de los puntos de las esquinas de la báscula, por lo que SIFT y SURF están optimizados para esta característica . Dado que sus principios matemáticos son relativamente complejos, consulte usted mismo los artículos y la literatura relevantes, no entraré en detalles aquí.
En comparación con cornerHarris y Shi-Tomasi, las ventajas de SIFT y SURF son significativas. Los puntos de esquina detectados permanecen invariantes a la rotación, escala, cambios de brillo, etc., y también permanecen constantes a la transformación de perspectiva, cambios afines y ruido . Es un excelente algoritmo de descripción de características locales con un alto grado de estabilidad .
Cabe señalar que SIFT y SURF requieren una gran cantidad de cálculos y son difíciles de realizar cálculos en tiempo real . En comparación con SIFT y SURF, SIFT es más preciso y SURF es más eficiente.

4.2 API

Constructor

    CV_WRAP static Ptr<SIFT> SIFT::create(int nfeatures = 0, int nOctaveLayers = 3,
        double contrastThreshold = 0.04, double edgeThreshold = 10,
        double sigma = 1.6);
        
    CV_WRAP static Ptr<SIFT> SIFT::create(int nfeatures, int nOctaveLayers,
        double contrastThreshold, double edgeThreshold,
        double sigma, int descriptorType);
        
    CV_WRAP static Ptr<SURF> SURF::create(double hessianThreshold=100,
                  int nOctaves = 4, int nOctaveLayers = 3,
                  bool extended = false, bool upright = false);
  • Los parámetros del constructor están diseñados con principios matemáticos complejos, que no se explican aquí, y la construcción predeterminada es suficiente cuando se usa.

Detección de puntos clave

    CV_WRAP virtual void Feature2D::detect( InputArray image,
                                 CV_OUT std::vector<KeyPoint>& keypoints,
                                 InputArray mask=noArray() );
                                 
    CV_WRAP virtual void Feature2D::detect( InputArrayOfArrays images,
                         CV_OUT std::vector<std::vector<KeyPoint> >& keypoints,
                         InputArrayOfArrays masks=noArray() );
  • Los parámetros son los siguientes.
parámetro significado
imagen 输入图像 (灰度图)深度要求:CV_8UC1或CV_32FC1
keypoints 含多个关键点的vector<KeyPoint>。使用detect时作为输出,使用compute时作为输入,使用detectAndCompute时可以作为输入也可以作为输出。
mask 掩码图像。其大小与输入图像必须相同,且必须为灰度图。计算时,对于掩码中的非0像素算法起作用,掩码中的灰度值为0的像素位置,算法不起作用。

描述子计算

    CV_WRAP virtual void Feature2D::compute( InputArray image,
                                  CV_OUT CV_IN_OUT std::vector<KeyPoint>& keypoints,
                                  OutputArray descriptors );
    CV_WRAP virtual void Feature2D::compute( InputArrayOfArrays images,
                          CV_OUT CV_IN_OUT std::vector<std::vector<KeyPoint> >& keypoints,
                          OutputArrayOfArrays descriptors );
    CV_WRAP virtual void Feature2D::detectAndCompute( InputArray image, InputArray mask,
                                           CV_OUT std::vector<KeyPoint>& keypoints,
                                           OutputArray descriptors,
                                           bool useProvidedKeypoints=false );
  • 参数如下
参数 含义
image 输入图片 (灰度图)深度要求:CV_8UC1或CV_32FC1
keypoints 含多个关键点的vector<KeyPoint>。使用detect时作为输出,使用compute时作为输入,使用detectAndCompute时可以作为输入也可以作为输出。
descriptors 描述子,数据类型Mat。在进行特征匹配的时候会用到
useProvidedKeypoints false时,keypoints作为输出,并根据keypoints算出descriptors。true时,keypoints作为输入,不再进行detect,即不修改keypoints,并根据keypoints算出descriptors。

drawKeypoints绘制关键点

CV_EXPORTS_W void drawKeypoints( InputArray image, const std::vector<KeyPoint>& keypoints, InputOutputArray outImage,
                               const Scalar& color=Scalar::all(-1), DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT );
enum struct DrawMatchesFlags
{
    
    
  DEFAULT = 0, //!< Output image matrix will be created (Mat::create),
               //!< i.e. existing memory of output image may be reused.
               //!< Two source image, matches and single keypoints will be drawn.
               //!< For each keypoint only the center point will be drawn (without
               //!< the circle around keypoint with keypoint size and orientation).
  DRAW_OVER_OUTIMG = 1, //!< Output image matrix will not be created (Mat::create).
                        //!< Matches will be drawn on existing content of output image.
  NOT_DRAW_SINGLE_POINTS = 2, //!< Single keypoints will not be drawn.
  DRAW_RICH_KEYPOINTS = 4 //!< For each keypoint the circle around keypoint with keypoint size and
                          //!< orientation will be drawn.
};
  • 参数如下
参数 含义
image 输入图像,数据类型Mat
keypoints 含多个关键点的vector<KeyPoint>
outImage 输出图像,数据类型Mat
color 绘制颜色信息,默认绘制的是随机彩色。
flags 特征点的绘制模式,其实就是设置特征点的那些信息需要绘制,那些不需要绘制。详见下表
  • flags可选值如下
flags可选值 含义
DrawMatchesFlags::DEFAULT 只绘制特征点的坐标点,显示在图像上就是一个个小圆点,每个小圆点的圆心坐标都是特征点的坐标。
DrawMatchesFlags::DRAW_OVER_OUTIMG 函数不创建输出的图像,而是直接在输出图像变量空间绘制,要求本身输出图像变量就是一个初始化好了的,size与type都是已经初始化好的变量。
DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS 单点的特征点不被绘制
DrawMatchesFlags::DRAW_RICH_KEYPOINTS 绘制特征点的时候绘制的是一个个带有方向的圆,这种方法同时显示图像的坐标,size和方向,是最能显示特征的一种绘制方式。

4.3 流程

  1. 实例化SIFT或SURF对象
  2. 将输入图像转灰度图
  3. 根据需要,调用detect函数或compute函数或detectAndCompute函数,检测关键点和计算描述子
  4. 调用drawKeypoints函数绘制关键点

4.4 效果

        Mat xuenai = imread("xuenai.jpg");
        imshow("xuenai", xuenai);
        namedWindow("panel");
        createTrackbar("threshold","panel", nullptr,255);
        createTrackbar("angle","panel", nullptr,360);
        createTrackbar("width","panel", nullptr,1000);
        createTrackbar("height","panel", nullptr,1000);

        while (1) {
    
    
            int thres = getTrackbarPos("threshold", "panel");
            if(thres==0)thres=100;
            int width = getTrackbarPos("width", "panel");
            if(width==0)width=xuenai.cols;
            int height = getTrackbarPos("height", "panel");
            if(height==0)height=xuenai.rows;
            int angle = getTrackbarPos("angle","panel");

            Mat xuenai_transform=xuenai.clone();

            resize(xuenai_transform,xuenai_transform,Size(width,height));

            Mat M= getRotationMatrix2D(Point2f(xuenai_transform.cols/2,xuenai_transform.rows/2),angle,1);
            warpAffine(xuenai_transform,xuenai_transform,M,xuenai_transform.size());

            Mat xuenai_gray(xuenai.size(),xuenai.type());
            cvtColor(xuenai_transform,xuenai_gray,COLOR_BGR2GRAY);

            Ptr<SIFT> sift=SIFT::create();
            Ptr<SURF> surf=SURF::create();
            vector<KeyPoint>xuenai_SiftKp,xuenai_Surfp;
            sift->detect(xuenai_gray,xuenai_SiftKp);
            surf->detect(xuenai_gray,xuenai_Surfp);

            Mat sift_result=xuenai_transform.clone(),surf_result=xuenai_transform.clone();
            drawKeypoints(sift_result,xuenai_SiftKp,sift_result,Scalar::all(-1),DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
            drawKeypoints(surf_result,xuenai_Surfp,surf_result,Scalar::all(-1),DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
            imshow("sift_result",sift_result);
            imshow("surf_result",surf_result);
            if (waitKey(0) == 'q')break;
        }

Insertar descripción de la imagen aquí

进行缩放和旋转

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

  • 可以看到,无论是旋转还是缩放,关键点都保持得非常稳定。

5.FAST到OBR(对灰度图)

5.1 概述

前文已经阐述,SIFT和SURF已经做到了角点在旋转和缩放下的稳定性,但是它们还有一个致命的缺陷,就是它们难以做到实时运算,因此,FAST和OBR应运而生了。

FAST原理

从图片中选取一个坐标点P,获取该点的像素值,接下来判定该点是否为特征点.
选取一个以选取点P坐标为圆心的半径等于r的Bresenham圆(一个计算圆的轨迹的离散算法,得到整数级的圆的轨迹点),一般来说,这个圆上有16个点,如下所示
Insertar descripción de la imagen aquí

  1. p在图像中表示一个被识别为兴趣点的像素。令它的强度为 Ip;
  2. 选择一个合适的阈值t;
  3. 考虑被测像素周围的16个像素的圆圈。 如果这16个像素中存在一组ñ个连续的像素的像素值,比 Ip+t 大,或比 Ip−t小,则像素p是一个角点。ñ被设置为12。
  4. 使用一种快速测试(high-speed test)可快速排除了大量的非角点。这个方法只检测在1、9、5、13个四个位置的像素,(首先检测1、9位置的像素与阈值比是否太亮或太暗,如果是,则检查5、13)。如果p是一个角点,则至少有3个像素比 Ip+t大或比 Ip−t暗。如果这两者都不是这样的话,那么p就不能成为一个角点。然后可以通过检查圆中的所有像素,将全部分段测试标准应用于通过的对候选的角点。这种探测器本身表现出很高的性能,但有一些缺点:
  • 它不能拒绝n <12的候选角点。当n<12时可能会有较多的候选角点出现
  • 检测到的角点不是最优的,因为它的效率取决于问题的排序和角点的分布。
  • 角点分析的结果被扔掉了。过度依赖于阈值
  • 多个特征点容易挤到一起。
  • 前三点是用机器学习方法解决的。最后一个是使用非极大值抑制来解决。具体不再展开。

FAST算法虽然很快,但是没有建立关键点的描述子,也就无法进行特征匹配

OBR简介

ORB 是 Oriented Fast and Rotated Brief 的简称,从这个简介就可以看出,OBR算法是基础FAST算法的改进。其中,Fast 和 Brief 分别是特征检测算法和向量创建算法。ORB 首先会从图像中查找特殊区域,称为关键点。关键点即图像中突出的小区域,比如角点,比如它们具有像素值急剧的从浅色变为深色的特征。然后 ORB 会为每个关键点计算相应的特征向量。ORB 算法创建的特征向量只包含 1 和 0,称为二元特征向量。1 和 0 的顺序会根据特定关键点和其周围的像素区域而变化。该向量表示关键点周围的强度模式,因此多个特征向量可以用来识别更大的区域,甚至图像中的特定对象。
关于Brief算法的具体原理本文不再赘述,请自行查阅相关论文和文献。

5.2 API

构造函数

    CV_WRAP static Ptr<FastFeatureDetector> create( int threshold=10,
                                                    bool nonmaxSuppression=true,
                                                    FastFeatureDetector::DetectorType type=FastFeatureDetector::TYPE_9_16 );
                                                    
    CV_WRAP static Ptr<ORB> create(int nfeatures=500, float scaleFactor=1.2f, int nlevels=8, int edgeThreshold=31,
                                   int firstLevel=0, int WTA_K=2, ORB::ScoreType scoreType=ORB::HARRIS_SCORE, int patchSize=31, int fastThreshold=20);

threshold:进行FAST检测时用到的阈值,阈值越大检测到的角点越少

5.3 流程

  1. 实例化FAST或OBR对象
  2. 将输入图像转灰度图
  3. 根据需要,调用detect函数或compute函数或detectAndCompute函数,检测关键点和计算描述子
  4. 调用drawKeypoints函数绘制关键点

5.4 效果

        Mat xuenai = imread("xuenai.jpg");
        imshow("xuenai", xuenai);
        namedWindow("panel");
        createTrackbar("threshold","panel", nullptr,255);
        createTrackbar("angle","panel", nullptr,360);
        createTrackbar("width","panel", nullptr,1000);
        createTrackbar("height","panel", nullptr,1000);

        while (1) {
    
    
            int thres = getTrackbarPos("threshold", "panel");
            if(thres==0)thres=100;
            int width = getTrackbarPos("width", "panel");
            if(width==0)width=xuenai.cols;
            int height = getTrackbarPos("height", "panel");
            if(height==0)height=xuenai.rows;
            int angle = getTrackbarPos("angle","panel");

            Mat xuenai_transform=xuenai.clone();

            resize(xuenai_transform,xuenai_transform,Size(width,height));

            Mat M= getRotationMatrix2D(Point2f(xuenai_transform.cols/2,xuenai_transform.rows/2),angle,1);
            warpAffine(xuenai_transform,xuenai_transform,M,xuenai_transform.size());

            Mat xuenai_gray(xuenai.size(),xuenai.type());
            cvtColor(xuenai_transform,xuenai_gray,COLOR_BGR2GRAY);

            Ptr<FastFeatureDetector>fast=FastFeatureDetector::create(thres);
            Ptr<ORB>obr=ORB::create();
            vector<KeyPoint>xuenai_FastKp,xuenai_ObrKp;
            fast->detect(xuenai_gray,xuenai_FastKp);
            obr->detect(xuenai_gray,xuenai_ObrKp);
            Mat fast_result=xuenai_transform.clone(),obr_result=xuenai_transform.clone();
            drawKeypoints(fast_result,xuenai_FastKp,fast_result,Scalar::all(-1),DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
            drawKeypoints(obr_result,xuenai_ObrKp,obr_result,Scalar::all(-1),DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
            imshow("fast_result",fast_result);
            imshow("obr_result",obr_result);
            if (waitKey(0) == 'q')break;
        }

调整threshold

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

进行缩放和旋转

Insertar descripción de la imagen aquí

错误

fast->detectAndCompute(xuenai_gray,noArray(),fast_kp,fast_des);

前文已经提及,FAST算法不支持描述子的计算

error: (-213:The function/feature is not implemented)  in function 'detectAndCompute'

6.Brute-Force与FLANN特征匹配

6.1 概述

Brute-Force

暴力匹配(Brute-force matcher)是最简单的二维特征点匹配方法。对于从两幅图像中提取的两个特征描述符集合,对第一个集合中的每个描述符Ri,从第二个集合中找出与其距离最小的描述符Sj作为匹配点。
暴力匹配显然会导致大量错误的匹配结果,还会出现一配多的情况。通过交叉匹配或设置比较阈值筛选匹配结果的方法可以改进暴力匹配的质量。

  • 如果参考图像中的描述符Ri与检测图像中的描述符Sj的互为最佳匹配,则称(Ri , Sj)为一致配对。交叉匹配通过删除非一致配对来筛选匹配结果,可以避免出现一配多的错误。
  • 比较阈值筛选是指对于参考图像的描述符Ri,从检测图像中找到距离最小的描述符Sj1和距离次小的描述符Sj2。设置比较阈值t∈[0.5 , 0.9],只有当最优匹配距离与次优匹配距离满足阈值条件d (Ri , Sj1) ⁄ d (Ri , Sj2) < t时,表明匹配描述符Sj1具有显著性,才接受匹配结果(Ri , Sj1)。

FLANN

  • 相比于Brute-Force,FLANN的速度更快
  • 由于使用的是邻近近似值,所以精度较差

6.2 API

构造函数

    CV_WRAP static Ptr<BFMatcher> BFMatcher::create( int normType=NORM_L2, bool crossCheck=false ) ;
    CV_WRAP static Ptr<FlannBasedMatcher> create();
    enum NormTypes {
    
    
                 NORM_INF       = 1,
                 NORM_L1        = 2,
                 NORM_L2        = 4,
                 NORM_L2SQR     = 5,
                 NORM_HAMMING   = 6,
                 NORM_HAMMING2  = 7,
                 NORM_TYPE_MASK = 7, 
                 NORM_RELATIVE  = 8,
                 NORM_MINMAX    = 32 
               };
  • 参数如下
参数 含义
normType 计算距离用到的方法,默认是欧氏距离。详见下表
crossCheck 是否使用交叉验证,默认不使用。
  • normType可选值如下
normType可选值 含义
NORM_L1 L1范数,曼哈顿距离
NORM_L2 L2范数,欧氏距离
NORM_HAMMING 汉明距离
NORM_HAMMING2 汉明距离2,对每2个比特相加处理。
  • NORM_L1、NORM_L2适用于SIFT和SURF检测算法
  • NORM_HAMMING、NORM_HAMMING2适用于OBR算法

描述子匹配

匹配方式一

    CV_WRAP void DescriptorMatcher::match( InputArray queryDescriptors, InputArray trainDescriptors,
                CV_OUT std::vector<DMatch>& matches, InputArray mask=noArray() ) const;
  • 参数如下
参数 含义
queryDescriptors 描述子的查询点集,数据类型Mat,即参考图像的特征描述符的集合。
trainDescriptors 描述子的训练点集,数据类型Mat,即检测图像的特征描述符的集合。
matches 匹配结果,长度为成功匹配的数量。
mask 掩码图像。其大小与输入图像必须相同,且必须为灰度图。计算时,对于掩码中的非0像素算法起作用,掩码中的灰度值为0的像素位置,算法不起作用。
  • 特别注意和区分哪个是查询集,哪个是训练集

匹配方式二

    CV_WRAP void DescriptorMatcher::knnMatch( InputArray queryDescriptors, InputArray trainDescriptors,
                   CV_OUT std::vector<std::vector<DMatch> >& matches, int k,
                   InputArray mask=noArray(), bool compactResult=false ) const;
  • 参数如下
参数 含义
queryDescriptors 描述子的查询点集,数据类型Mat,即参考图像的特征描述符的集合。
trainDescriptors 描述子的训练点集,数据类型Mat,即检测图像的特征描述符的集合。
matches vector<std::vector<DMatch>>类型,对每个特征点返回k个最优的匹配结果
k 返回匹配点的数量
mask 掩码图像。其大小与输入图像必须相同,且必须为灰度图。计算时,对于掩码中的非0像素算法起作用,掩码中的灰度值为0的像素位置,算法不起作用。
  • 特别注意和区分哪个是查询集,哪个是训练集

Brute-Force与FLANN对输入描述子的要求

  • Brute-Force要求输入的描述子必须是CV_8U或者CV_32S
  • FLANN要求输入的描述子必须是CV_32F

drawMatches绘制匹配结果

CV_EXPORTS_W void drawMatches( InputArray img1, const std::vector<KeyPoint>& keypoints1,
                             InputArray img2, const std::vector<KeyPoint>& keypoints2,
                             const std::vector<DMatch>& matches1to2, InputOutputArray outImg,
                             const Scalar& matchColor=Scalar::all(-1), const Scalar& singlePointColor=Scalar::all(-1),
                             const std::vector<char>& matchesMask=std::vector<char>(), DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT );
                             
CV_EXPORTS_W void drawMatches( InputArray img1, const std::vector<KeyPoint>& keypoints1,
                             InputArray img2, const std::vector<KeyPoint>& keypoints2,
                             const std::vector<DMatch>& matches1to2, InputOutputArray outImg,
                             const int matchesThickness, const Scalar& matchColor=Scalar::all(-1),
                             const Scalar& singlePointColor=Scalar::all(-1), const std::vector<char>& matchesMask=std::vector<char>(),
                             DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT );
                             
CV_EXPORTS_AS(drawMatchesKnn) void drawMatches( InputArray img1, const std::vector<KeyPoint>& keypoints1,
                             InputArray img2, const std::vector<KeyPoint>& keypoints2,
                             const std::vector<std::vector<DMatch> >& matches1to2, InputOutputArray outImg,
                             const Scalar& matchColor=Scalar::all(-1), const Scalar& singlePointColor=Scalar::all(-1),
                             const std::vector<std::vector<char> >& matchesMask=std::vector<std::vector<char> >(), DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT );
  • 参数如下
参数 含义
img1(image1) 源图像1,数据类型Mat
keypoints1 源图像1的关键点
img2(image2) 源图像2,数据类型Mat
keypoints2 源图像2的关键点
matches1to2 源图像1的描述子匹配源图像2的描述子的匹配结果
outImg(out image) 输出图像,数据类型Mat
matchColor 匹配的颜色(特征点和连线),默认Scalar::all(-1),颜色随机
singlePointColor 单个点的颜色,即未配对的特征点,默认Scalar::all(-1),颜色随机
matchesMask 掩码,决定哪些点将被画出,若为空,则画出所有匹配点
flags 特征点的绘制模式,其实就是设置特征点的那些信息需要绘制,那些不需要绘制。

6.3 流程

  1. 实例化BFMatcher对象
  2. 根据需要,调用match函数或knnMatch函数,进行特征匹配
  3. 调用drawMatches函数呈现原图,并且绘制匹配点

6.4 效果

        Mat xuenai = imread("xuenai.jpg");
        Mat xuenai_rect = imread("xuenai_rect.jpg");
        Mat xuenai_rect_gray;cvtColor(xuenai_rect,xuenai_rect_gray,COLOR_BGR2GRAY);
        imshow("xuenai", xuenai);
        namedWindow("panel");
        createTrackbar("threshold","panel", nullptr,255);
        createTrackbar("angle","panel", nullptr,360);
        createTrackbar("width","panel", nullptr,1000);
        createTrackbar("height","panel", nullptr,1000);

        while (true) {
    
    
            int thres = getTrackbarPos("threshold", "panel");
            if(thres==0)thres=100;
            int width = getTrackbarPos("width", "panel");
            if(width==0)width=xuenai.cols;
            int height = getTrackbarPos("height", "panel");
            if(height==0)height=xuenai.rows;
            int angle = getTrackbarPos("angle","panel");

            Mat xuenai_transform=xuenai.clone();
            
            //调整尺寸
            resize(xuenai_transform,xuenai_transform,Size(width,height));
            
            //进行旋转
            Mat M= getRotationMatrix2D(Point2f((float )xuenai_transform.cols/2,(float )xuenai_transform.rows/2),angle,1);
            warpAffine(xuenai_transform,xuenai_transform,M,xuenai_transform.size());

            //灰度图
            Mat xuenai_gray(xuenai.size(),xuenai.type());
            cvtColor(xuenai_transform,xuenai_gray,COLOR_BGR2GRAY);

            //准备工作
            Ptr<ORB>obr=ORB::create();
            vector<KeyPoint>xuenai_ObrKp;
            Mat BFMmatch_result;Mat FLANNmatch_result;
            vector<KeyPoint>xuenai_rect_ObrKp;
            Mat xuenai_obr_descriptorsForBF;Mat xuenai_rect_obr_descriptorsForBF;Mat xuenai_obr_descriptorsForFLANN;Mat xuenai_rect_obr_descriptorsForFLANN;
            vector<vector<DMatch>>xuenai_BFMmatch_results;vector<vector<DMatch>>xuenai_FLANNmatch_results;
            obr->detectAndCompute(xuenai_gray,noArray(),xuenai_ObrKp,xuenai_obr_descriptorsForBF);
            obr->detectAndCompute(xuenai_rect_gray,noArray(),xuenai_rect_ObrKp,xuenai_rect_obr_descriptorsForBF);
            xuenai_obr_descriptorsForBF.convertTo(xuenai_obr_descriptorsForFLANN,CV_32F);//注意这里不能进行原地运算
            xuenai_rect_obr_descriptorsForBF.convertTo(xuenai_rect_obr_descriptorsForFLANN,CV_32F);//注意这里不能进行原地运算

            //进行匹配
            Ptr<BFMatcher>bfm=BFMatcher::create(NORM_HAMMING);
            Ptr<FlannBasedMatcher>flann=FlannBasedMatcher::create();
            bfm->knnMatch(xuenai_rect_obr_descriptorsForBF,xuenai_obr_descriptorsForBF,xuenai_BFMmatch_results,2);
            flann->knnMatch(xuenai_rect_obr_descriptorsForFLANN,xuenai_obr_descriptorsForFLANN,xuenai_FLANNmatch_results,2);

            //比率检验
            vector<DMatch>goodBFMresult,goodFLANNresult;
            for(auto match_result:xuenai_BFMmatch_results){
    
    
                if(match_result.size()>1 && match_result[0].distance<0.7*match_result[1].distance ){
    
    
                    goodBFMresult.push_back(match_result[0]);
                }
            }
            for(auto match_result:xuenai_FLANNmatch_results){
    
    
                if(match_result.size()>1 && match_result[0].distance<0.7*match_result[1].distance ){
    
    
                    goodFLANNresult.push_back(match_result[0]);
                }
            }

            //绘制匹配结果
            if(!goodBFMresult.empty()) {
    
    
                drawMatches(xuenai_rect, xuenai_rect_ObrKp,
                            xuenai_transform, xuenai_ObrKp,
                            goodBFMresult, BFMmatch_result,
                            Scalar::all(-1), Scalar::all(-1), vector<char>(),
                            DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
                imshow("BFMmatch_result",BFMmatch_result);

            }
            if(!goodFLANNresult.empty()) {
    
    
                drawMatches(xuenai_rect, xuenai_rect_ObrKp,
                            xuenai_transform, xuenai_ObrKp,
                            goodFLANNresult, FLANNmatch_result,
                            Scalar::all(-1), Scalar::all(-1), vector<char>(),
                            DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
                imshow("FLANNmatch_result",FLANNmatch_result);
            }
            if (waitKey(0) == 'q')break;
        }

不进行比率筛选

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

进行比率筛选

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

7.单应性矩阵

7.1 概述

使用最小均方误差或者RANSAC方法,计算多个二维点对之间的最优单映射变换矩阵 H(3行x3列),配合perspectivetransform函数,可以实现对图片的矫正
Insertar descripción de la imagen aquí

7.2 API

findHomography

CV_EXPORTS_W Mat findHomography( InputArray srcPoints, InputArray dstPoints,
                                 int method = 0, double ransacReprojThreshold = 3,
                                 OutputArray mask=noArray(), const int maxIters = 2000,
                                 const double confidence = 0.995);

/** @overload */
CV_EXPORTS Mat findHomography( InputArray srcPoints, InputArray dstPoints,
                               OutputArray mask, int method = 0, double ransacReprojThreshold = 3 );


CV_EXPORTS_W Mat findHomography(InputArray srcPoints, InputArray dstPoints, OutputArray mask,
                   const UsacParams &params);
  • 参数如下
参数 含义
srcPoints 源平面中点的坐标矩阵,可以是CV_32FC2类型,也可以是vector<Point2f>类型
dstPoints 目标平面中点的坐标矩阵,可以是CV_32FC2类型,也可以是vector<Point2f>类型
method 计算单应矩阵所使用的方法。不同的方法对应不同的参数,参考如下表格。
ransacReprojThreshold 将点对视为内点的最大允许重投影错误阈值(仅用于RANSAC和RHO方法)。若srcPoints和dstPoints是以vector<Point2f>为单位的,则该参数通常设置在1到10的范围内,建议选择5
mask 可选输出掩码矩阵。通常由鲁棒算法(RANSAC或LMEDS)设置。 请注意,输入掩码矩阵是不需要设置的。
maxIters RANSAC算法的最大迭代次数,默认值为2000
confidence 可信度值,取值范围为0到1
  • method可选值如下
method可选值 含义
0 利用所有点的常规方法
RANSAC 基于RANSAC的鲁棒算法
LMEDS 最小中值鲁棒算法
RHO 基于PROSAC的鲁棒算法

perspectivetransform

perspectivetransform函数与warpPerspective函数的区别在于:

  • perspectivatransformar es transformar el vector <Point2f>
  • warpPerspective es transformar Mat
CV_EXPORTS_W void perspectiveTransform(InputArray src, OutputArray dst, InputArray m );

7.2 Proceso

  1. Detecte puntos clave y calcule descriptores mediante algoritmos de detección de características
  2. Obtenga resultados coincidentes a través del algoritmo de coincidencia de características
  3. Recorra los resultados coincidentes, use la variable miembro DMatch.queryIdx y la variable miembro DMatch.trainIdx para indexar el conjunto de puntos clave de consulta y el conjunto de puntos clave de entrenamiento respectivamente, y use la variable miembro KeyPoint.pt para obtener sus coordenadas, obteniendo así dos vectores <Punto2f>
  4. Llame a la función findHomography para obtener la matriz de homografía
  5. Llame a la función outlooktransform para transformar el vector del conjunto de puntos de coordenadas <Point2f> correspondiente a las cuatro esquinas de la imagen de consulta, y luego obtenemos el vector del conjunto de puntos de coordenadas <Point2f> correspondiente a las cuatro esquinas de la imagen que queremos encontrar en la imagen de entrenamiento.
  6. Utilice la función de polilíneas para trazar los resultados en el gráfico de entrenamiento.

7.3 Efecto

        Mat xuenai = imread("xuenai.jpg");
        Mat xuenai_rect = imread("xuenai_rect.jpg");
        Mat xuenai_rect_gray;cvtColor(xuenai_rect,xuenai_rect_gray,COLOR_BGR2GRAY);
        imshow("xuenai", xuenai);
        namedWindow("panel");
        createTrackbar("threshold","panel", nullptr,255);
        createTrackbar("angle","panel", nullptr,360);
        createTrackbar("width","panel", nullptr,1000);
        createTrackbar("height","panel", nullptr,1000);

        while (true) {
    
    
            int thres = getTrackbarPos("threshold", "panel");
            if(thres==0)thres=100;
            int width = getTrackbarPos("width", "panel");
            if(width==0)width=xuenai.cols;
            int height = getTrackbarPos("height", "panel");
            if(height==0)height=xuenai.rows;
            int angle = getTrackbarPos("angle","panel");

            Mat xuenai_transform=xuenai.clone();

            resize(xuenai_transform,xuenai_transform,Size(width,height));

            Mat M= getRotationMatrix2D(Point2f((float )xuenai_transform.cols/2,(float )xuenai_transform.rows/2),angle,1);
            warpAffine(xuenai_transform,xuenai_transform,M,xuenai_transform.size());

            Mat xuenai_gray(xuenai.size(),xuenai.type());
            cvtColor(xuenai_transform,xuenai_gray,COLOR_BGR2GRAY);

            //准备工作
            Ptr<ORB>obr=ORB::create();
            vector<KeyPoint>xuenai_ObrKp;
            Mat BFMmatch_result;
            vector<KeyPoint>xuenai_rect_ObrKp;
            Mat xuenai_obr_descriptors;Mat xuenai_rect_obr_descriptors;
            vector<vector<DMatch>>xuenai_BFMmatch_results;
            obr->detectAndCompute(xuenai_gray,noArray(),xuenai_ObrKp,xuenai_obr_descriptors);
            obr->detectAndCompute(xuenai_rect_gray,noArray(),xuenai_rect_ObrKp,xuenai_rect_obr_descriptors);

            //进行匹配
            Ptr<BFMatcher>bfm=BFMatcher::create(NORM_HAMMING);
            bfm->knnMatch(xuenai_rect_obr_descriptors,xuenai_obr_descriptors,xuenai_BFMmatch_results,2);

            //比率检验
            vector<DMatch>goodBFMresult;
            for(auto match_result:xuenai_BFMmatch_results){
    
    
                if(match_result.size()>1 && match_result[0].distance<0.8*match_result[1].distance ){
    
    
                    goodBFMresult.push_back(match_result[0]);
                }
            }

            //寻找单应性矩阵并绘制
            vector<Point2f>srcPoints,dstPoints;
            for (auto good_one:goodBFMresult){
    
    
                srcPoints.push_back(xuenai_rect_ObrKp[good_one.queryIdx].pt);
                dstPoints.push_back(xuenai_ObrKp[good_one.trainIdx].pt);
            }
            if (srcPoints.size()>=4){
    
    
                Mat homo = findHomography(srcPoints, dstPoints, RANSAC,5);
                vector<Point2f> origin_pts = {
    
    Point2f(0, 0), Point2f(0, xuenai_rect.rows - 1),
                                              Point2f(xuenai_rect.cols - 1, xuenai_rect.rows - 1),
                                              Point2f(xuenai_rect.cols - 1, 0),};
                vector<Point2f> result_pts;
                vector<Point> result_pts_int;
                perspectiveTransform(origin_pts, result_pts, homo);
                //Point2f转Point
                for (auto point: result_pts) {
    
    
                    result_pts_int.push_back(Point(point));
                }
                polylines(xuenai_transform, result_pts_int, true, Scalar(0, 0, 255));
            }

            //绘制结果
            if(!goodBFMresult.empty()) {
    
    
                drawMatches(xuenai_rect, xuenai_rect_ObrKp,
                            xuenai_transform, xuenai_ObrKp,
                            goodBFMresult, BFMmatch_result,
                            Scalar::all(-1), Scalar::all(-1), vector<char>(),
                            DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
                imshow("FLANNmatch_result",BFMmatch_result);
            }
            
            if (waitKey(0) == 'q')break;
        }

Insertar descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/qq_50791664/article/details/129525331
Recomendado
Clasificación