"OpenCV Computer Vision Programming Strategy" study notes (1: Introduction to image programming)

References

illustrate

  • This book combines C++ and OpenCV 3.2 to fully explain computer vision programming
  • All codes are compiled and executed with g++ in Ubuntu system

0. Install the OpenCV library

  • Install OpenCV and use it on Ubuntu
  • The OpenCV library is divided into multiple modules , the common modules are as follows
    • The opencv_core module contains the core functionality of the library
    • The opencv_imgproc module contains the main image processing functions
    • The opencv_highgui module provides functions for reading and writing images and videos as well as some user interaction functions

1. Loading, displaying and storing images

// 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)

insert image description here

insert image description here

insert image description here

2. Deep understanding of cv::Mat

  • cv::Mat has two essential components: a header and a data block
    • The header contains all relevant information about the matrix (size, number of channels, data type, etc.)
    • The data block contains the values ​​of all pixels in the image
    • The header has a pointer to the data block, the data attribute
  • cv::Mat has a very important property: the memory block will only be copied when explicitly requested , in fact, most operations only copy the head of cv::Mat, so multiple objects will point to the same data block
// 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]
  • Don't return the class attribute of the image when using the class
    • If a function calls the method of this class, a shallow copy of the image property will be performed. Once the copy is modified, the class attribute will also be "secretly" modified, which will affect the subsequent behavior of this class
    class Test {
          
          
        // 图像属性
        cv::Mat ima;
    public:
        // 在构造函数中创建一幅灰度图像
        Test() : ima(240, 320, CV_8U, cv::Scalar(100)) {
          
          }
        // 用这种方法返回一个类属性,这是一种不好的做法
        cv::Mat method() {
          
          
            return ima;
        }
    };
    

3. Define the region of interest

  • Sometimes it is necessary to make a processing function only work on a certain part of the image . OpenCV embeds an elegant and concise mechanism that can define a sub-region of an image and operate on this sub-region as a normal image
  • Suppose you want to copy a small image to a large image, in order to achieve this function
    • Define a region of interest (Region Of Interest, ROI) , where the copy operation is performed, and the position of the ROI will determine the insertion position of the logo
      • One way to define ROI is to use the cv::Rect instance : by specifying the position of the upper left corner (the first two parameters of the constructor) and the size of the rectangle (the last two parameters represent width and height), a rectangular area is described, The entire ROI must be inside the parent image
      • ROI can also be described by the range of rows and columns: the range is a continuous sequence from the start index to the end index (excluding the start value and end value), which can be represented by the cv::Range structure

    Since the image and ROI share the same image data, any transformation of the ROI will affect the relevant area of ​​the original image

  • Functions or methods in OpenCV usually operate on all pixels in the image, and the scope of these functions or methods can be limited by defining a mask
    • The mask is an 8-bit image. If the value of a certain position in the mask is not 0, the operation at this position will work; if the value of some pixel positions in the mask is 0, then the corresponding Operations on location will not work
    • For example, you can use a mask when calling the copyTo method, you can use the mask to copy only the white part of the logo, because the background of the logo is black (so the value is 0), so it is easy to be copied image and mask at the same time to use
// 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

insert image description here

insert image description here

Guess you like

Origin blog.csdn.net/qq_42994487/article/details/131317634