Notas de estudio "Estrategia de programación de visión por computadora OpenCV" (1: Introducción a la programación de imágenes)

Referencias

ilustrar

  • Este libro combina C++ y OpenCV 3.2 para explicar completamente la programación de visión por computadora
  • Todos los códigos se compilan y ejecutan con g ++ en el sistema Ubuntu

0. Instalar la biblioteca OpenCV

  • Instala OpenCV y úsalo en Ubuntu
  • La biblioteca OpenCV se divide en varios módulos , los módulos comunes son los siguientes
    • El módulo opencv_core contiene la funcionalidad principal de la biblioteca.
    • El módulo opencv_imgproc contiene las principales funciones de procesamiento de imágenes
    • El módulo opencv_highgui proporciona funciones para leer y escribir imágenes y videos, así como algunas funciones de interacción con el usuario.

1. Cargar, mostrar y almacenar imágenes

// loadDisplaySave.cpp
#include <iostream>

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

/*** 在图像上点击(让鼠标在置于图像窗口上时运行特定的指令)***/
/*  
    定义一个回调函数 onMouse,回调函数不会被显式地调用,在响应特定事件时被程序调用
        参数一:触发回调函数的鼠标事件的类型
        参数二、三:事件发生时鼠标的位置,用像素坐标表示
        参数四:表示事件发生时按下了鼠标的哪个键
        参数五:指向任意对象的指针,作为附加的参数发送给函数
*/
void onMouse(int event, int x, int y, int flags, void* param) {
    
    
    // reinterpret_cast 用于将任意类型的指针转换为其他类型的指针
    // 将一个 void* 类型的指针转换成了一个 cv::Mat* 类型的指针
    // 并将其赋值给了变量 im,这样就可以通过 im 来访问 cv::Mat 对象
    cv::Mat *im = reinterpret_cast<cv::Mat*>(param);
    
    switch (event) {
    
     // 调度事件
        case cv::EVENT_LBUTTONDOWN: // 鼠标左键按下事件
            // 显示像素值(x, y)
            std::cout << "at (" << x << "," << y << ") value is: "
                      << static_cast<int>(im->at<uchar>(cv::Point(x,y))) << std::endl; 
            break;
    }
}

int main(int argc, char *argv[]) {
    
    
    // cv::Mat 是 OpenCV 定义的用于表示任意维度的稠密数组,OpenCV 使用它来存储和传递图像
    cv::Mat image; // 创建一个尺寸为 0 × 0 空图像
    std::cout << "This image is " << image.rows << " x " << image.cols << std::endl;
    
    // 读入一个图像文件并将其转换为灰度图像
    // 这样生成的图像由无符号字节构成,在 OpenCV 中用常量 CV_8U 表示
    image = cv::imread("puppy.bmp", cv::IMREAD_GRAYSCALE);
    
    /*  读取图像,并将其转换为三通道彩色图像
        这样创建的图像中,每个像素有 3 字节,OpenCV 中用 CV_8UC3 表示
    image = cv::imread("puppy.bmp", cv::IMREAD_COLOR); 
    */

    if (image.empty()) {
    
     // 如果没有分配图像数据,empty 方法将返回 true
        std::cout << "Error reading image..." << std::endl;
        return 0;
    }

    std::cout << "This image is " << image.rows << " x " << image.cols << std::endl;
    // 使用 channels 方法检查图像的通道数
    std::cout << "This image has " << image.channels() << " channel(s)" << std::endl;

    cv::namedWindow("Original Image"); // 定义窗口(可选)
    cv::imshow("Original Image", image); // 显示图像

    // 回调函数注册
    // 函数 onMouse 与 Original Image 图像窗口建立关联,同时把所显示图像的地址作为附加参数传给函数
    cv::setMouseCallback("Original Image", onMouse, reinterpret_cast<void*>(&image));

    cv::Mat result; // 创建另一个空的图像
    cv::flip(image, result, 1); // 正数 1 表示水平翻转,0 表示垂直翻转,负数表示水平和垂直翻转
    // cv::flip(image, image, 1); // 对输入图片就地处理,直接写入原图像
    cv::namedWindow("Output Image");
    cv::imshow("Output Image", result);
    cv::waitKey(0); // 0 表示永远的等待按键,键入的正数表示等待的毫秒数
    cv::imwrite("output.bmp", result); // 保存结果

    // 在图像上绘图(必须包含头文件 imgproc.hpp)
    cv::namedWindow("Drawing on an Image");
    // 目标图像,中心点坐标,半径,颜色,厚度
    cv::circle(image, cv::Point(155, 110), 65, 0, 3);
    // 目标图像,文本,文本位置,字体类型,字体大小,字体颜色,字体厚度
    cv::putText(image, "This is a dog.", cv::Point(40, 200), 
                cv::FONT_HERSHEY_PLAIN, 2.0, 255, 2);
    cv::imshow("Drawing on an Image", image);
    cv::waitKey(0);

    return 0;
}
# 编译并执行
# 若版本为 OpenCV4.x,则最后改为 opencv4
$ g++ loadDisplaySave.cpp -o test1 `pkg-config --cflags --libs opencv` 
$ ./test1
# 控制台输出
This image is 0 x 0
This image is 213 x 320
This image has 1 channel(s)

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

2. Comprensión profunda de cv::Mat

  • cv::Mat tiene dos componentes esenciales: un encabezado y un bloque de datos
    • El encabezado contiene toda la información relevante sobre la matriz (tamaño, número de canales, tipo de datos, etc.)
    • El bloque de datos contiene los valores de todos los píxeles de la imagen.
    • El encabezado tiene un puntero al bloque de datos, el atributo de datos
  • cv::Mat tiene una propiedad muy importante: el bloque de memoria solo se copiará cuando se solicite explícitamente ; de ​​hecho, la mayoría de las operaciones solo copian el encabezado de cv::Mat, por lo que varios objetos apuntarán al mismo bloque de datos
// mat.cpp
#include <iostream>

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

// 测试函数,它创建一幅图像
cv::Mat function() {
    
    
    cv::Mat ima(500, 500, CV_8U, 50); // 创建图像
    return ima; // 返回图像
}

int main(int argc, char *argv[]) {
    
    
    // 创建一个 240 行 × 320 列的新图像
    // CV_8U 表示每个像素对应 1 字节(灰度图像),U 表示无符号
    // cv::Size 结构包含了矩阵高度和宽度,同样可以提供图像的尺寸信息
    cv::Mat image1(240, 320, CV_8U, 100);
    // cv::Mat image1(cv::Size(240, 320), CV_8UC3);
    cv::imshow("Image", image1);
    cv::waitKey(0);
    
    // 随时可以用 create 方法分配或重新分配图像的数据块
    // 如果新的尺寸和类型与原来的相同,就不会重新分配内存
    image1.create(200, 200, CV_8U);
    image1 = 200;
    cv::imshow("Image", image1);
    cv::waitKey(0);

    // 创建一个红色的图像 (通道次序为 BGR)
    // 数据结构 cv::Scalar 用于在调用函数时传递像素值
    cv::Mat image2(240, 320, CV_8UC3, cv::Scalar(0, 0, 255));
    cv::imshow("Image", image2);
    cv::waitKey(0);

    // 读入一幅图像
    cv::Mat image3 = cv::imread("puppy.bmp");
    // 所有这些图像都指向同一个数据块,通过 cv::Mat 实现计数引用和浅复制
    // 只有当图像的所有引用都将释放或赋值给另一幅图像时,内存才会被释放
    cv::Mat image4(image3);
    image1 = image3;

    // 这些图像是源图像的副本图像
    // 1. 如果要对图像内容做一个深复制,可以使用 copyTo 方法
    image3.copyTo(image2);
    // 2. 另一个生成图像副本的方法是 clone
    cv::Mat image5 = image3.clone();

    // 转换图像进行测试
    cv::flip(image3, image3, 1);
    // 检查哪些图像在处理过程中受到了影响
    cv::imshow("Image 3", image3);
    cv::imshow("Image 1", image1);
    cv::imshow("Image 2", image2);
    cv::imshow("Image 4", image4);
    cv::imshow("Image 5", image5);
    cv::waitKey(0);

    // 从函数中获取一个灰度图像
    // 运行这条语句后,就可用变量 gray 操作由 function 函数创建的图像,而不需要额外分配内存
    cv::Mat gray = function();
    cv::imshow("Image", gray);
    cv::waitKey(0);

    // 作为灰度图像读入
    image1 = cv::imread("puppy.bmp", CV_LOAD_IMAGE_GRAYSCALE);
    // 如果要把一幅图像复制到另一幅图像中,且两者的数据类型不相同,那就使用 convertTo 方法
    // 转换成浮点型图像 [0,1],这两幅图像的通道数量必须相同
    image1.convertTo(image2, CV_32F, 1/255.0, 0.0); // 两个可选参数:缩放比例、偏移量
    cv::imshow("Image", image2);

    // 3×3 双精度型矩阵
    cv::Matx33d matrix(3.0, 2.0, 1.0, 
                       2.0, 1.0, 3.0,
                       1.0, 2.0, 3.0);
    // 3×1 矩阵(即向量)
    cv::Matx31d vector(5.0, 1.0, 3.0);
    cv::Matx31d result = matrix * vector;
    std::cout << result;
    cv::waitKey(0);

    return 0;
}
# 编译并执行
# 若版本为 OpenCV4.x,则最后改为 opencv4
$ g++ mat.cpp -o test2 `pkg-config --cflags --libs opencv` 
$ ./test2
# 控制台输出
[20;
 20;
 16]
  • No devuelva el atributo de clase de la imagen cuando use la clase
    • Si una función llama al método de esta clase, se realizará una copia superficial de la propiedad de la imagen. Una vez que se modifica la copia, el atributo de clase también se modificará "en secreto", lo que afectará el comportamiento posterior de esta clase.
    class Test {
          
          
        // 图像属性
        cv::Mat ima;
    public:
        // 在构造函数中创建一幅灰度图像
        Test() : ima(240, 320, CV_8U, cv::Scalar(100)) {
          
          }
        // 用这种方法返回一个类属性,这是一种不好的做法
        cv::Mat method() {
          
          
            return ima;
        }
    };
    

3. Definir la región de interés

  • A veces es necesario hacer que una función de procesamiento solo funcione en una parte determinada de la imagen . OpenCV incorpora un mecanismo elegante y conciso que puede definir una subregión de una imagen y operar en esta subregión como una imagen normal
  • Supongamos que desea copiar una imagen pequeña a una imagen grande, para lograr esta función
    • Defina una región de interés (Region Of Interest, ROI) , donde se realiza la operación de copia, y la posición del ROI determinará la posición de inserción del logotipo.
      • Una forma de definir el ROI es usar la instancia cv::Rect : especificando la posición de la esquina superior izquierda (los dos primeros parámetros del constructor) y el tamaño del rectángulo (los dos últimos parámetros representan el ancho y el alto), se describe un área rectangular, todo el ROI debe estar dentro de la imagen principal
      • El ROI también se puede describir por el rango de filas y columnas: el rango es una secuencia continua desde el índice inicial hasta el índice final (excluyendo el valor inicial y el valor final), que se puede representar mediante la estructura cv::Range

    Dado que la imagen y el ROI comparten los mismos datos de imagen, cualquier transformación del ROI afectará el área relevante de la imagen original

  • Las funciones o métodos en OpenCV generalmente operan en todos los píxeles de la imagen, y el alcance de estas funciones o métodos se puede limitar definiendo una máscara
    • La máscara es una imagen de 8 bits. Si el valor de cierta posición en la máscara no es 0, la operación en esta posición funcionará; si el valor de algunas posiciones de píxeles en la máscara es 0, entonces las operaciones correspondientes en la ubicación no trabajará
    • Por ejemplo, puede usar una máscara cuando llama al método copyTo, puede usar la máscara para copiar solo la parte blanca del logotipo, porque el fondo del logotipo es negro (por lo que el valor es 0), por lo que es fácil ser copiado imagen y máscara al mismo tiempo para usar
// logo.cpp
#include <iostream>

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

int main(int argc, char *argv[]) {
    
    
    cv::namedWindow("Image");
    cv::Mat image = cv::imread("puppy.bmp");
    cv::Mat logo = cv::imread("smalllogo.png");

    // 在图像的右下角定义一个 ROI
    // image 是目标图像, logo 是标志图像
    cv::Mat imageROI(image, cv::Rect(image.cols - logo.cols, // ROI 坐标
                                     image.rows - logo.rows,
                                     logo.cols, logo.rows)); // ROI 大小
    /*imageROI = image(cv::Range(image.rows-logo.rows,image.rows),
                       cv::Range(image.cols-logo.cols,image.cols));*/
    logo.copyTo(imageROI); // 插入标志
    cv::imshow("Image", image);
    cv::waitKey(0);

    image = cv::imread("puppy.bmp");
    // 在图像的右下角定义一个 ROI
    imageROI = image(cv::Rect(image.cols - logo.cols,
                              image.rows - logo.rows,
                              logo.cols, logo.rows));
    // 把标志作为掩码(必须是灰度图像)
    cv::Mat mask(logo);
    logo.copyTo(imageROI, mask); // 插入标志,只复制掩码不为 0 的位置
    cv::imshow("Image", image);
    cv::waitKey(0);

    return 0;
}
# 编译并执行
# 若版本为 OpenCV4.x,则最后改为 opencv4
$ g++ logo.cpp -o test3 `pkg-config --cflags --libs opencv` 
$ ./test3

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/qq_42994487/article/details/131317634
Recomendado
Clasificación