《OpenCV 计算机视觉编程攻略》学习笔记(一:图像编程入门)

news2024/11/23 15:43:02

1、参考引用

  • OpenCV 计算机视觉编程攻略(第3版)
  • 本书结合 C++ 和 OpenCV 3.2 全面讲解计算机视觉编程
  • 所有代码均在 Ubuntu 系统中用 g++ 编译执行

0. 安装 OpenCV 库

  • 在Ubuntu上安装OpenCV及使用
  • 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 有两个必不可少的组成部分:一个头部和一个数据块
    • 头部包含了矩阵的所有相关信息(大小、通道数量、数据类型等)
    • 数据块包含了图像中所有像素的值
    • 头部有一个指向数据块的指针,即 data 属性
  • 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]
  • 在使用类的时候不要返回图像的类属性
    • 如果某个函数调用了这个类的 method ,就会对图像属性进行一次浅复制。副本一旦被修改,class 属性也会被 “偷偷地” 修改,这会影响这个类的后续行为
    class Test {
        // 图像属性
        cv::Mat ima;
    public:
        // 在构造函数中创建一幅灰度图像
        Test() : ima(240, 320, CV_8U, cv::Scalar(100)) {}
        // 用这种方法返回一个类属性,这是一种不好的做法
        cv::Mat method() {
            return ima;
        }
    };
    

3. 定义感兴趣区域

  • 有时需要让一个处理函数只在图像的某个部分起作用。OpenCV 内嵌了一个精致又简洁的机制,可以定义图像的子区域,并把这个子区域当作普通图像进行操作
  • 假设要把一个小图像复制到一个大图像上,为实现这个功能
    • 定义一个感兴趣区域(Region Of Interest,ROI),在此处进行复制操作,ROI 的位置将决定标志的插入位置
      • 定义 ROI 的一种方法是使用 cv::Rect 实例:通过指明左上角的位置(构造函数的前两个参数)和矩形的尺寸(后两个参数表示宽度和高度),描述了一个矩形区域,整个 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

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/668860.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python数据可视化 - 使用Python dash搭建交互式地图可视化看板

1.前言 前几年刚接触Dash库的时候&#xff0c;Dash生态还不太成熟&#xff0c;做些简单的web还行&#xff0c;复杂的、系统性还是得用flask或django来实现。随着这两年dash的不断迭代更新&#xff0c;以及dash大佬feffery相继开发了feffery_antd_components、feffery_leaflet_…

编译原理笔记7:语法分析(1)语法分析器的任务、语法错误的处理

目录 语法分析器是编译器前端的核心语法错误的处理语法错误的处理目标语法错误的基本恢复策略 语法分析器是编译器前端的核心 语法分析器的两项主要任务&#xff0c;分别&#xff1a; 是根据词法分析器提供的记号流&#xff0c;为语法正确的输入构造分析树&#xff08;或语法树…

uboot下UCLASS框架详解---结合项目工作中spi master和flash驱动开发

文章目录 一、综述二、UCLASS架构解析2.1 uclass2.2 udevice2.3 uclass driver2.4 driver2.4.1 spi master driver 三、uboot代码解析3.1 DM的初始化3.2 spi norflash设备识别3.3 设备树内容3.4 .config配置3.5 spi读写测试 四、其他相关链接1、SPI协议详细总结附实例图文讲解通…

IDEA合并分支和.gitignore可能遇到的问题

将本地的 v1 分支合并到 master 分支上 1.确认你在 master 分支上&#xff0c;在命令行执行以下命令&#xff0c;这将切换到 master 分支。 git checkout master 2.拉取最新代码 在合并分支之前&#xff0c;请确保您的代码库是最新的。在命令行执行以下命令&#xff0c;这将从…

一文解决C/C++中所有指针相关知识点

本篇会对C/C中【常见指针相关知识】一直进行总结迭代&#xff0c;记得收藏吃灰不迷路&#xff0c;一起学习分享喔 请大家批评指正&#xff0c;一起学习呀~ 一、指针基本知识1.1 指针的定义1.2 &#xff08;*&#xff09; 和&#xff08; &&#xff09; 运算符1.3 如何声明…

使用omp并行技术加速bfs广度优先算法

基本思想 从初始状态S开始&#xff0c;利用规则&#xff0c;生成所有可能的状态。构成树的下一层节点&#xff0c;检查是否出现目标状态G&#xff0c;若未出现&#xff0c;就对该层所有状态节点&#xff0c;分别顺序利用规则。生成再下一层的所有状态节点&#xff0c;对这一层的…

chatgpt赋能python:Python在SEO中的排名

Python在SEO中的排名 Python作为一门高级编程语言&#xff0c;已经有近三十年的历史&#xff0c;被广泛用于各个领域的开发&#xff0c;包括 Web开发、数据分析、机器学习等。在 SEO 中&#xff0c;Python 也具有很高的应用价值。本文将会介绍 Python 在 SEO 中的应用以及其排…

C语言进阶教程(一个可执行文件生成的具体步骤)

文章目录 前言一、预处理二、编译三、汇编四、链接总结 前言 本篇文章来讲解一个.c文件生成一个可执行文件的完整过程&#xff0c;我们学习了那么久&#xff0c;只知道在编译器中按下编译运行就可以将一个.c文件运行起来了&#xff0c;但是我们并不了解其中的具体步骤&#xf…

【openGauss简单数据库管理---快速入门】

【openGauss简单数据库管理---快速入门】 &#x1f53b; 一、openGauss数据库管理&#x1f530; 1.1 连接openGauss数据库&#x1f530; 1.2 创建数据库&#x1f530; 1.3 查看数据库和切换数据库&#x1f530; 1.4 修改数据库&#x1f530; 1.5 删除数据库&#x1f530; 1.6 启…

高等代数复习(二)

本篇复习内容有 求解标准正交基 证明标准正交基 证明正交变换 利用共轭变换证明 求解与给定矩阵的相似矩阵--对角矩阵 1.求标准正交基 在求标准正交基时&#xff0c;通常要先正交化&#xff0c;然后单位化&#xff0c;即可求出标准正交基。 2.证明标准正交基 标准正交基单位…

chatgpt赋能python:Python提取指定位置字符

Python 提取指定位置字符 Python 是一种高级程序语言&#xff0c;其易读性、简单易学性和易维护性使其成为最受欢迎的编程语言之一。它可以用于各种数据分析和科学计算&#xff0c;包括搜索引擎优化&#xff08;SEO&#xff09;。 在SEO中&#xff0c;提取和处理数据是一个重…

C++——string容器模拟实现

目录 1. 基本成员变量 2. 默认成员函数 2.1 构造函数 2.2 析构函数 2.3 拷贝构造函数 2.4 赋值运算符重载 3. 容量与大小相关函数 3.1 size 3.2 capacity 4. 字符串访问相关函数 4.1 operator[ ]重载 4.2 迭代器 5. 增加函数接口 5.1 reserve 5.2 resize 5.3 …

【二叉树part01】| 二叉树的递归遍历、二叉树的迭代遍历

目录 ✿二叉树的递归遍历❀ ☞LeetCode144.前序遍历 ☞LeetCode145.二叉树的后序遍历 ☞LeetCode94.二叉树的中序遍历 ✿二叉树的迭代遍历❀ ☞LeetCode144.前序遍历 ☞LeetCode145.二叉树的后序遍历 ☞LeetCode94.二叉树的中序遍历 ✿二叉树的递归遍历❀ ☞LeetCode…

docker-compose启动opengauss数据库——华为“自研”数据库

文章目录 1. 启动数据库2. 登录2.1 本地登录2.2 远程登录 1. 启动数据库 yml文件 创建opengauss目录&#xff0c;里边创建docker-compose.yml文件内容如下&#xff1a; 华为开源数据库&#xff0c;默认5432端口&#xff0c;是不是很熟悉&#xff0c;疑似又是个套壳子的事件。果…

Cortext-M3系列:调试系统架构(8)

1、调试特性概述 单片机的调试功能在程序开发中有着十分重要的地位&#xff0c;好的调试工具&#xff0c;能让程序开发大大加快。笔者在刚开始学单片机相关知识时&#xff0c;使用的是pintf打印相关参数&#xff0c;进行调试&#xff08;虽然现在很多时候也这样&#xff09;&am…

MyBatis 的使用方法

观前提示:本篇博客演示使用的 IDEA 版本为2021.3.3版本,使用的是Java8(又名jdk1.8) 前端使用 VSCode(Visual Studio Code1.78.2) 电脑使用的操作系统版本为 Windows 10 目录 Mybatis是什么? Mybatis 有什么用? Mybatis 框架交流 Mybatis 项目环境搭建 1. 添加 Mybatis…

设计模式之生成器(建造者)模式笔记

设计模式之建造者模式笔记 说明Builder(生成器)目录UML生成器(建造者)模式示例类图自行车类建造者抽象类摩拜单车对象类小黄车单车对象类指挥者类测试类优缺点 模式扩展手机类测试类 说明 记录下学习设计模式-生成器(也叫建造者)模式的写法。 Builder(生成器) 意图:将一个复…

监控中的计算机科学

文章目录 一、前言二、监控分类2.1 模拟摄像头2.1 数字摄像头 三、监控系统四、后端产品4.1 硬盘录像机4.2 视频矩阵4.3 控制设备4.4 显示设备 五、传输端5.1 光纤视频线5.1.2 单模光纤5.1.3 多模光纤5.1.4 光端机 5.1 双绞线 六、畅享 一、前言 布林肯访华&#xff0c;黑我们…

Redis6之事务与锁

事务 Redis事务是一个单独的隔离操作&#xff1a;事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中&#xff0c;不会被其他客户端发送来的命令请求所打断。 Redis事务的主要作用就是串联多个命令防止别的命令插队。 命令 1、开启事务&#xff1a;multi 2、执行事…

STM32 RGB屏幕

使用ST的HAL库进行开发&#xff0c;RGB屏幕是480*272的4.3寸LCD&#xff0c;由于驱动RGB屏幕需要较多的内存&#xff0c; 所以使用了外部SDRAM&#xff0c;内存是32M字节&#xff0c;关于SDRAM的驱动本文不进行讨论。 RGB屏幕常用的像素格式有&#xff1a;ARGB8888、RGB888、…