「OpenCV コンピュータビジョンプログラミング戦略」学習メモ(1:画像プログラミング入門)

参考文献

説明する

  • この本は、C++ と OpenCV 3.2 を組み合わせて、コンピューター ビジョン プログラミングを完全に説明します。
  • すべてのコードは、Ubuntu システムの g++ でコンパイルおよび実行されます。

0. OpenCVライブラリをインストールする

  • OpenCVをインストールしてUbuntuで使用する
  • OpenCV ライブラリは複数のモジュールに分かれており、共通のモジュールは次のとおりです。
    • opencv_core モジュールには、ライブラリのコア機能が含まれています
    • opencv_imgproc モジュールには、主要な画像処理関数が含まれています
    • opencv_highgui モジュールは、画像やビデオの読み取りおよび書き込み機能と、いくつかのユーザー対話機能を提供します。

1. 画像の読み込み、表示、保存

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

ここに画像の説明を挿入

ここに画像の説明を挿入

ここに画像の説明を挿入

2. cv::Mat の深い理解

  • cv::Mat には、ヘッダーとデータ ブロックという 2 つの重要なコンポーネントがあります。
    • ヘッダーには、マトリックスに関するすべての関連情報 (サイズ、チャネル数、データ型など) が含まれています。
    • データ ブロックには画像内のすべてのピクセルの値が含まれます
    • ヘッダーにはデータ ブロックへのポインター、データ属性が含まれます。
  • cv::Mat には非常に重要なプロパティがあります。メモリ ブロックは明示的に要求された場合にのみコピーされます。実際、ほとんどの操作は cv::Mat の先頭をコピーするだけなので、複数のオブジェクトが同じデータ ブロックを指すことになります。
// 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]
  • クラスを使用する場合、画像のクラス属性を返さない
    • 関数がこのクラスのメソッドを呼び出すと、画像プロパティの浅いコピーが実行されます。コピーが変更されると、クラス属性も「密かに」変更され、このクラスのその後の動作に影響します。
    class Test {
          
          
        // 图像属性
        cv::Mat ima;
    public:
        // 在构造函数中创建一幅灰度图像
        Test() : ima(240, 320, CV_8U, cv::Scalar(100)) {
          
          }
        // 用这种方法返回一个类属性,这是一种不好的做法
        cv::Mat method() {
          
          
            return ima;
        }
    };
    

3. 関心領域を定義する

  • 場合によっては、処理関数を画像の特定の部分にのみ機能させることが必要になることがありますOpenCV には、画像のサブ領域を定義し、このサブ領域を通常の画像として操作できるエレガントで簡潔なメカニズムが組み込まれています。
  • この機能を実現するために、小さな画像を大きな画像にコピーするとします。
    • コピー操作が実行される関心領域 (関心領域、ROI) を定義します。ROI の位置によってロゴの挿入位置が決まります。
      • ROI を定義する 1 つの方法は、cv::Rect インスタンスを使用することです。左上隅の位置 (コンストラクターの最初の 2 つのパラメーター) と四角形のサイズ (最後の 2 つのパラメーターは幅と高さを表します) を指定します。長方形の領域が記述されています。ROI全体が親画像内にある必要があります
      • ROI は、行と列の範囲によって記述することもできます。範囲は、開始インデックスから終了インデックス (開始値と終了値を除く) までの連続したシーケンスであり、 cv::Range 構造体で表すことができます。

    画像と ROI は同じ画像データを共有するため、ROI を変換すると元の画像の関連領域に影響します。

  • OpenCV の関数またはメソッドは通常、画像内のすべてのピクセルに対して動作します。これらの関数またはメソッドの範囲は、マスクを定義することで制限できます。
    • マスクは 8 ビット イメージです。マスク内の特定の位置の値が 0 でない場合は、この位置での演算が機能します。マスク内の一部のピクセル位置の値が 0 の場合は、その位置での対応する演算が実行されます。動作しないでしょう
    • たとえば、copyTo メソッドを呼び出すときにマスクを使用できます。ロゴの背景が黒であるため (値が 0 であるため)、マスクを使用してロゴの白い部分だけをコピーできます。画像とマスクを同時にコピーして使用できます
// 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

ここに画像の説明を挿入

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/qq_42994487/article/details/131317634