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

news/2024/12/2 15:49:46/

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 方法将返回 truestd::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. 另一个生成图像副本的方法是 clonecv::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");// 在图像的右下角定义一个 ROIimageROI = 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.ppmy.cn/news/493958.html

相关文章

DIY自己的深度学习电脑

以下是参考而来的丐中丐中丐版 CPU&#xff1a;AMD 3500X 主板&#xff1a;微星 B450m 迫击炮 铂金版 显卡&#xff1a;索泰 RTX 2060 super Xgaming OC V2 内存&#xff1a;阿斯加特 洛极 51C 灰 3200频 8G*2 硬盘&#xff1a;海康威视C2000 Pro 1TB 电源&#xff1a;鑫…

2016中国计算机内存条,国产连续推出32G超大内存条,打造内存条市场上的复仇者IP...

阿斯加特(Asgard)是国内高端电脑存储产品品牌&#xff0c;致力于为消费者提供更高性能的电脑存储产品(SSD、内存条、移动硬盘)和更优质的产品服务。 阿斯加特(Asgard)名字来源于北欧神话&#xff0c;是众神的国度。像我们熟知的超级英雄雷神、洛基都是来自于阿斯加特。据说因为…

阿斯加特AN3+全球首发,忆芯主控SSD家族再添新星

全球存储市场持续稳定增长&#xff0c;SSD大规模替代机械硬盘大潮涌现&#xff0c;中国势力不断崛起。2019年到2022年闪存应用方向以SSD和移动应用为主&#xff0c;SSD市场的增速持续上扬。SSD出货量又以消费级SSD为主&#xff0c;同时消费级SSD和企业级SSD均呈现良好的增长势头…

高端内存的由来(为什么需要高端内存)

作者&#xff1a;文松 链接&#xff1a;https://www.zhihu.com/question/280526042/answer/1615449221 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 一、高端内存的由来&#xff08;为什么需要高端内存&#xff…

openGauss存储技术(二)——列存储引擎和内存引擎

上一篇内容我们介绍了openGauss存储技术&#xff08;一&#xff09;——行存储引擎&#xff0c;本文重点介绍openGauss列存储引擎和内存引擎。 openGauss列存储引擎 传统行存储数据压缩率低&#xff0c;必须按行读取&#xff0c;即使读取一列也必须读取整行。在分析性的作业以…

其实电脑装机很简单

PS&#xff1a;又是好久没写过帖子了&#xff0c;十月份亲身经历一系列悲惨事故&#xff0c;好在卑微小李生命力顽强&#xff0c;成功的挺了下来。 今天分享一下装机的故事 这次的经历源于一批项目上的新进机器(主要是因为新服务器太贵&#xff0c;性能强一点的PC机照样可以&…

Geocomputation (2)Attribute data operations

Geocomputation &#xff08;2&#xff09;Attribute data operations 属性数据操作 来源&#xff1a;https://github.com/geocompx/geocompy 1.准备 #| echo: false import pandas as pd import matplotlib.pyplot as plt pd.options.display.max_rows 6 pd.options.disp…