您现在的位置是:首页 > 正文

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

2024-01-30 20:49:33阅读 0

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 × 256 × 256 256\times 256\times 256 256×256×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

网站文章

  • 模型评估指标

    模型评估指标

    模型评估指标一、回归(Regression)算法指标1. Mean Absolute Error 平均绝对误差2. Mean Squared Error 均方误差3. Root Mean Square...

    2024-01-30 20:48:58
  • 【闲聊杂谈】HTTPS原理详解

    【闲聊杂谈】HTTPS原理详解

    HTTP虽然使用极为广泛, 但是却存在不小的安全缺陷, 主要是其数据的明文传送和消息完整性检测的缺乏, 而这两点恰好是网络支付, 网络交易等新兴应用中安全方面最需要关注的。关于 HTTP的明文数据传输...

    2024-01-30 20:48:49
  • org.hibernate.MappingException: Unknown entity:

    最近学习JEECG框架,使用代码自动生成功能并导入的过程后出现以下问题:页面能正常访问,但是首先前台页面出现NULL,相继后台打印出【org.jeecgframework.core.common.exception.MyExceptionHandler]java.lang.NullPointerException】点击确定后进行增删改查操作,编辑内容新增,提交后前台显示Unknown en

    2024-01-30 20:48:41
  • 管理oracle控制文件

    每一个oracle数据库都有一个控制文件。控制文件是一个小型的二进制文件,可以记录数据库的物理结构,包含以下的内容:数据库名称、相关数据文件和联机重做日志文件的名称和位置、数据库创建的时标、当前日志的序号、检验点信息。 无论何时打开数据库,控制文件必须能够由oracle数据库服务器写入内容。没有控制文件,数据库就不能装载,且很难恢复。oracle数据库控制文件在数据库创建的同时创

    2024-01-30 20:48:32
  • javascript 【2018.11.29】

    &lt;html&gt;&lt;head&gt; &lt;title&gt;&lt;/title&gt;&lt;/head&gt;&lt;body&gt; &lt;script type="text/javascript"&gt; for(i=0;i&lt;10;i++) { if(i==3) break;

    2024-01-30 20:48:04
  • TCP/IP 三次握手

    TCP/IP 三次握手

    TCP/IP 三次握手

    2024-01-30 20:47:58
  • Java中高位转低位溢出的计算过程

    System.out.println((byte) 129);System.out.println((byte) -129);System.out.println("~b2: " + ~10);结果是:-127127~b2: -11计算机中是以补码进行计算正数的反码补码都是原码,如:10原码: 1010反码: 1010补码:1010负数 -10原码 10000000...

    2024-01-30 20:47:51
  • PAT - 乙级 1040 有几个PAT

    1040. 有几个PAT(25)时间限制120 ms内存限制65536 kB代码长度限制8000 B判题程序Standard作者CAO, Peng字符串APPAPT中包含了两个单词“PAT”,其中第一...

    2024-01-30 20:47:22
  • Qt状态机使用

    Qt状态机使用

    Qt状态机是一种被称为QStateMachine的类,它可以用来管理应用程序的状态。状态机有助于将应用程序分解为互相独立、可重复的状态。状态由事件驱动,可以响应输入、发出输出并采取自适应操作。在Qt中...

    2024-01-30 20:47:16
  • Redis最最最通俗易懂的解释!!!

     Redis的官方解释可以百度,这里讲redis缓存为啥速度快???    这么说吧,别人问你什么是“redis”,如果你知道,你可以直接吧啦吧啦一大堆,其实这个时候你的大脑就类似redis缓存,别人问的“redis”就是key,你说出来的结果就是value,而你如果不知道,你就去上网查,然后再告诉别人,这就类似于查询数据库了,你查了再告诉别人当然慢了!    你把脑袋里的东西写进笔记...

    2024-01-30 20:47:08