【OpenCV C++&Python】(一)图像读取、显示和保存

简介:

OpenCV简介

在这里插入图片描述

OpenCV(开源计算机视觉库)是一个开源库,是基于C/C++开发的:

  • OpenCV 1.x:基于C语言开发,需要手动分配、释放内存。
  • OpenCV 2.x之后:引入C++语言,实现自动化内存管理。

它包含数百种计算机视觉算法,有以下主要模块:

  • 核心功能(Core):定义基本数据结构的模块,包括密集的多维数组和被其他模块使用的基本函数。
  • 图像处理(imgproc):一个图像处理模块,包括线性和非线性图像滤波、几何图像变换(调整大小、仿射和透视扭曲等)、颜色空间转换、直方图等。
  • 视频分析(Video):一个视频分析模块,包括运动估计、背景减除和对象跟踪算法。
  • 摄像机校准和三维重建(calib3d):基本的多视图几何算法、单摄像机和立体摄像机校准、物体姿态估计、立体对应算法和三维重建。
  • 2D特征框架(features2d):显著特征检测器、描述符和描述符匹配器。
  • 对象检测(objdetect):检测预定义类别的实例和对象(例如,人脸、眼睛、杯子、人物、汽车等)。
  • 高级GUI(highgui):一个简单易于使用的用户界面。
  • 视频I/O(videoio):一个易于使用的视频捕获和视频编解码器接口。

所有OpenCV类和函数都放在cv命名空间中。因此,要用代码使用OpenCV的一些功能,需使用cv::来指定命名空间cv

另外,OpenCV提供了Python的 API——OpenCV-Python,它结合了OpenCV C++ API和Python语言的最佳特性。

Mat

OpenCV是一个计算机视觉库,其主要重点是处理和操作图像信息。因此,我们首先需要熟悉OpenCV的基本图像容器——Mat

Mat本质上是由两个数据部分组成的类: 矩阵头(包含信息有矩阵的大小,用于存储的方法,矩阵存储的地址等) 和一个指向包含像素值的矩阵的指针。矩阵头的大小是恒定的。然而,矩阵本身的大小因图像的不同而不同,通常比较大。

OpenCV包含大量图像处理的函数。因此,在使用OpenCV时常常要将图像传递给函数;而图像处理算法的计算量往往很大。如果每次传递都是复制传递整个图像数据,那么将影响到程序的速度。

为了解决这个问题,OpenCV使用了一个引用计数系统。其思想是,每个Mat对象都有自己的头,但通过让两个Mat对象的矩阵指针指向同一地址,可以使得两个Mat对象共享一个矩阵。此外,复制操作符只会复制头和矩阵的指针,而不会复制数据本身。

        Mat A, C;         //仅创建了头部
        A = imread(argv[1], CV_LOAD_IMAGE_COLOR);  // 为A分配矩阵
        Mat B(A);             //使用拷贝构造函数
        C = A;             //赋值运算符

最后,上述所有对象都指向同一个矩阵,使用其中任何一个进行修改也会影响其他所有对象。实际上,不同的对象只是为相同的底层数据提供了不同的访问方法。然而,它们的头部是不同的。另外,你还可以创建只引用部分数据(图像的一小块)的头。例如,要在图像中创建感兴趣区域(ROI),只需创建一个带有指定边界的新头:

Mat D (A, Rect(10, 10, 100, 100) ); // 用矩形指定边界
Mat E = A(Range:all(), Range(1,3)); // 用行和列来指定边界

问题:如果矩阵本身可能属于多个Mat对象,当不再需要时,谁负责清理它?

回答:最后一个使用它的对象。

这是通过使用引用计数机制来处理的。每当有人复制Mat对象的头部 时,矩阵的计数器就会增加。每清除一个头时,该计数器就会减小。当计数器为零时,矩阵被释放。

有时你也会想复制矩阵本身,所以OpenCV提供了cv::Mat::clone()cv::Mat::copyTo()函数。

Mat F = A.clone();
Mat G;
A.copyTo(G);

图像存储方式

我们可以选择颜色空间和使用的数据类型来存储图像。颜色空间指的是我们如何组合颜色组件来对给定的颜色进行编码。最简单的一种是灰度,我们可以使用黑色和白色结合创造出许多灰色的阴影。

对于彩色的图像,我们有更多的方式可供选择,且每种都有各自的优点:

  • RGB是最常见的,它也是我们的眼睛形成颜色的方式。为了对颜色的透明度进行编码,有时会添加第四个元素alpha(RGBA)。但是注意,OpenCV的图像空间使用的是BGR颜色空间。
  • HSVHLS将颜色分解为色调、饱和度和值/亮度分量,这是我们描述颜色更自然的方式。你可以忽略最后一个分量,使算法对输入图像的光照条件不那么敏感。
  • YCrCb是流行的JPEG图像格式。
  • CIE L*a*b*是一种均匀颜色空间,如果你需要测量一种颜色到另一种颜色的距离,它很有用。

颜色空间的每个构件都有自己的有效值域。这由使用的数据类型决定。对于数据类型,最小的是char:一个字节或8位。它可以是无符号的(因此可以存储0到255的值)或有符号的(从-127到+127的值)。虽然在三个组件(如RGB)的情况下,它可以表示1600多万($256\times 256\times 256$)种颜色,但我们可以通过为每个组件使用浮点(4字节=32位)或双精度(8字节=64位)数据类型来获得更精细的表示。然而,要记住到,增加精度也会增加整个图片的占用内存。

显式创建Mat对象

  • cv::Mat::Mat 构造函数
    Mat M(2,2, CV_8UC3, Scalar(0,0,255));
    cout << "M = " << endl << " " << M << endl << endl;

在这里插入图片描述

对于二通道和多通道图像,我们首先定义它们的大小:行和列(2,2)。然后我们需要指定用于存储元素的数据类型以及每个矩阵点的通道数。为此,我们定下以下约定定义图像:

CV_[每一项的bit数][有符号或无符号][类型前缀]C[通道数]

例如,CV_8UC3意味着我们使用8位无符号字符类型,每个像素由三个通道表示。

cv::Scalar为四元素的短向量(BGRA)。通过它可以用自定义值初始化所有矩阵点各个通道的值。

如果需要更多维度的矩阵,可以使用宏CV_8UC,并在括号中设置通道数CV_8UC(1),如下所示:

  • 使用C/C++数组并通过构造函数初始化
    int sz[3] = {2,2,2};
    Mat L(3,sz, CV_8UC(1), Scalar::all(0));

上面的示例显示了如何创建具有两个以上维度的矩阵。指定其维度3,然后传递一个包含每个维度大小的指针sz,其他保持不变。Scalar::all(0)就是给每个通道都赋值0

  • cv::Mat::create 函数:
    Mat N;
    N.create(4, 4, CV_8UC(3));
    cout << "N = " << endl << " " << N << endl << endl;

在这里插入图片描述

这种构造方式无法初始化矩阵值。

  • MATLAB 风格的初始化: cv::Mat::zeros , cv::Mat::ones , cv::Mat::eye . 指定要使用的大小和数据类型:
    Mat E = Mat::eye(4, 4, CV_64F);
    cout << "E = " << endl << " " << E << endl << endl;
    Mat O = Mat::ones(2, 2, CV_32F);
    cout << "O = " << endl << " " << O << endl << endl;
    Mat Z = Mat::zeros(3,3, CV_8UC1);
    cout << "Z = " << endl << " " << Z << endl << endl;
E O Z
在这里插入图片描述 在这里插入图片描述 在这里插入图片描述
  • 对于小矩阵,可以使用逗号分隔的初始值设定项或初始值设定项列表:
    Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
    cout << "C = " << endl << " " << C << endl << endl;

在这里插入图片描述

    C = (Mat_<double>({0, -1, 0, -1, 5, -1, 0, -1, 0})).reshape(3);
    cout << "C = " << endl << " " << C << endl << endl;

在这里插入图片描述
说明:这一方式需要C++11支持)

  • 为现有Mat对象创建一个新头,并用cv::Mat::clonecv::Mat::copyTo将其添加到该头中。
    Mat RowClone = C.row(1).clone();
    cout << "RowClone = " << endl << " " << RowClone << endl << endl;

在这里插入图片描述

注意:你可以使用cv::randu()函数用随机值填充矩阵。你需要给出随机值的下限和上限:

    Mat R = Mat(3, 2, CV_8UC3);
    randu(R, Scalar::all(0), Scalar::all(255));

输出格式

OpenCV允许你格式化矩阵输出:

  • 默认
    cout << "R (default) = " << endl <<        R           << endl << endl;

在这里插入图片描述

  • Python
    cout << "R (python)  = " << endl << format(R, Formatter::FMT_PYTHON) << endl << endl;

在这里插入图片描述

  • 逗号分隔值(CSV)
    cout << "R (csv)     = " << endl << format(R, Formatter::FMT_CSV   ) << endl << endl;

在这里插入图片描述

  • Numpy
    cout << "R (numpy)   = " << endl << format(R, Formatter::FMT_NUMPY ) << endl << endl;

在这里插入图片描述

  • C
    cout << "R (c)       = " << endl << format(R, Formatter::FMT_C     ) << endl << endl;

在这里插入图片描述

Mat更多功能: https://docs.opencv.org/4.5.5/d3/d63/classcv_1_1Mat.html

图像读取、显示和保存

了解了Mat,下面介绍图像的读取、显示和保存

C++

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;
int main()
{
    std::string image_path = samples::findFile("xing.jpg");
    Mat img = imread(image_path, IMREAD_COLOR);
    if (img.empty())
    {
        std::cout << "Could not read the image: " << image_path << std::endl;
        return 1;
    }
    namedWindow("窗口1", WINDOW_AUTOSIZE);   
    imshow("窗口1", img);  //在“窗口1”这个窗口输出图片。
    int k = waitKey(0);
    if (k == 's')
    {
        imwrite("image_save.jpg", img);
    }
    return 0;
}

在这里插入图片描述

代码解释:

(1)导入头文件

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;

core :定义了OpenCV库的基本组成部分

imgcodecs:提供读写功能

highgui:包含在窗口中显示图像的功能

通过使用using namespace cv;在下面的示例中,我们就可以直接访问库函数,而无需显式说明命名空间。

(2)读取图像

调用samples::findFile获取图像路径,调用 cv::imread 读取图像:

    std::string image_path = samples::findFile("xing.jpg");
    Mat img = imread(image_path, IMREAD_COLOR);

读取的图像数据将存储在cv::Mat对象中。

imread()第一个参数是图像的路径。第二个参数是可选的,用于指定图像的格式。它可以是:

  • IMREAD_COLOR:默认参数,以BGR 8bit格式读取图像,忽略alpha通道(透明度),可用1替代。
  • IMREAD_GRAYSCALE:读入灰度图像,可用0替代。
  • IMREAD_UNCHANGED:读入图像,包括alpha通道(透明度),可用-1替代。

检查图像是否加载成功,失败则退出程序:

    if(img.empty())
    {
        std::cout << "Could not read the image: " << image_path << std::endl;
        return 1;
    }

(3)在OpenCV窗口展示图像:

    namedWindow("窗口1", WINDOW_AUTOSIZE);   
    imshow("窗口1", img);  //在“窗口1”这个窗口输出图片。
    int k = waitKey(0); 
  • WINDOW_AUTOSIZE 窗口大小自动适应图片大小,并且不可手动更改。
  • WINDOW_NORMAL 用户可以改变这个窗口大小
  • WINDOW_OPENGL 窗口创建的时候会支持OpenGL

因为我们希望在用户按下任意键之前显示窗口(否则程序结束得太快),所以我们使用waitKey(),它唯一的参数是它等待用户输入的时间(以毫秒为单位)。0意味着永远等待。返回值k是按下的键。

(4)保存图像:
如果按下的键是“s”键,图像将被保存。

    if (k == 's')
    {
        imwrite("image_save.jpg", img);
    }

如果是32F的图像,需要转换为8U类型。例如:

#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
    Mat image1 = imread("image.jpg");
    namedWindow("image1", WINDOW_NORMAL);
    imshow("image1", image1);
    waitKey(0);
    Mat image2;
    image1.convertTo(image2, CV_32F);  // 将图像类型从8UC1更改为32FC1
    namedWindow("image2", WINDOW_AUTOSIZE);
    imshow("image2", image2);
    waitKey(0);
    return 0;
}
在这里插入图片描述 在这里插入图片描述

Python

import cv2 as cv
import sys

img = cv.imread(cv.samples.findFile("image.jpg"))
if img is None:
    sys.exit("Could not read the image.")

cv.namedWindow("window", cv.WINDOW_AUTOSIZE)
cv.imshow("window", img)
k = cv.waitKey(0)
if k == ord("s"):
    cv.imwrite("image_save.jpg", img)

代码解释:

(1) 导入库

import cv2 
import sys

sys用于退出程序

(2)读取图像

img = cv.imread(cv.samples.findFile("image.jpg"),0)

检查图像是否加载成功,失败则退出程序:

if img is None:
    sys.exit("Could not read the image.")

(3)在OpenCV窗口展示图像:

cv.namedWindow("window", cv.WINDOW_AUTOSIZE)
cv.imshow("window", img)
k = cv.waitKey(0)

(4)保存图像:

if k == ord("s"):
    cv.imwrite("image_save.jpg", img)

更改图像数据类型:

dst = src.astype(np.float32)  # 将图像类型从8UC1更改为32FC1

代码:

https://gitee.com/BinaryAI/open-cv-c--and-python/tree/master

参考:
[1] https://docs.opencv.org/4.5.5/index.html

相关文章
|
2月前
|
计算机视觉
Opencv学习笔记(三):图像二值化函数cv2.threshold函数详解
这篇文章详细介绍了OpenCV库中的图像二值化函数`cv2.threshold`,包括二值化的概念、常见的阈值类型、函数的参数说明以及通过代码实例展示了如何应用该函数进行图像二值化处理,并展示了运行结果。
542 0
Opencv学习笔记(三):图像二值化函数cv2.threshold函数详解
|
3月前
|
算法 计算机视觉
opencv图像形态学
图像形态学是一种基于数学形态学的图像处理技术,它主要用于分析和修改图像的形状和结构。
55 4
|
3月前
|
存储 计算机视觉
Opencv的基本操作(一)图像的读取显示存储及几何图形的绘制
本文介绍了使用OpenCV进行图像读取、显示和存储的基本操作,以及如何绘制直线、圆形、矩形和文本等几何图形的方法。
Opencv的基本操作(一)图像的读取显示存储及几何图形的绘制
|
2月前
|
Ubuntu Linux 编译器
Linux/Ubuntu下使用VS Code配置C/C++项目环境调用OpenCV
通过以上步骤,您已经成功在Ubuntu系统下的VS Code中配置了C/C++项目环境,并能够调用OpenCV库进行开发。请确保每一步都按照您的系统实际情况进行适当调整。
519 3
|
2月前
|
计算机视觉 Python
python利用pyqt5和opencv打开电脑摄像头并进行拍照
本项目使用Python的PyQt5和OpenCV库实现了一个简单的摄像头应用。用户可以通过界面按钮打开或关闭摄像头,并实时预览视频流。点击“拍照”按钮可以捕捉当前画面并保存为图片文件。该应用适用于简单的图像采集和处理任务。
156 0
python利用pyqt5和opencv打开电脑摄像头并进行拍照
|
2月前
|
C++ Python
探索Python与C/C++混合编程的艺术
探索Python与C/C++混合编程的艺术
54 1
|
2月前
|
机器学习/深度学习 算法 计算机视觉
【Python篇】Python + OpenCV 全面实战:解锁图像处理与视觉智能的核心技能
【Python篇】Python + OpenCV 全面实战:解锁图像处理与视觉智能的核心技能
106 2
|
3月前
|
机器学习/深度学习 计算机视觉 Python
opencv环境搭建-python
本文介绍了如何在Python环境中安装OpenCV库及其相关扩展库,包括numpy和matplotlib,并提供了基础的图像读取和显示代码示例,同时强调了使用Python虚拟环境的重要性和基本操作。
|
3月前
|
存储 计算机视觉 C++
在C++中实现Armadillo库与OpenCV库之间的数据格式转换
在C++中实现Armadillo库与OpenCV库之间的数据格式转换是一项常见且实用的技能。上述步骤提供了一种标准的方法来进行这种转换,可以帮助开发者在两个库之间高效地转移和处理数据。虽然转换过程相对直接,但开发者应留意数据类型匹配和性能优化等关键细节。
69 11
|
3月前
|
存储 计算机视觉 C++
在C++中实现Armadillo库与OpenCV库之间的数据格式转换
在C++中实现Armadillo库与OpenCV库之间的数据格式转换是一项常见且实用的技能。上述步骤提供了一种标准的方法来进行这种转换,可以帮助开发者在两个库之间高效地转移和处理数据。虽然转换过程相对直接,但开发者应留意数据类型匹配和性能优化等关键细节。
32 3