1. OpenCV 数据结构与基本绘图

1. OpenCV 数据结构与基本绘图

1.1 基础图像容器 Mat

1.1.1 Mat 的拷贝

在 OpenCV 中,Mat 由两个数据部分组成:矩阵头(包含矩阵尺寸、存储方法、存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同,矩阵可以是不同的维数)的指针。
OpenCV 是一个图像处理库,囊括了大量的图像处理函数,为了解决问题通常要使用库中的多个函数,因此在函数中传递图像是常有的事。同时不要忘了我们正在讨论的是计算量很大的图像处理算法,因此,除非万不得已,不应该进行大图像的复制,因为这会降低程序的运行速度。为了解决此问题,OpenCV 使用了引用计数机制。其思路是让每个 Mat 对象有自己的信息头,但共享同一个矩阵。这通过让矩阵指针指向同一地址而实现。而拷贝构造函数则只复制信息头和矩阵指针,而不复制矩阵。以下是测试代码:

#include <opencv2\opencv.hpp>
using namespace cv;

void ModifyImage(Mat E)
{
    for (int i = 0; i < E.rows; ++i)
    {
        for (int j = 0; j < E.cols / 2; ++j)
        {
            E.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
        }
    }
    return;
}

int main()
{
    Mat A, B;
    A = imread("C:/Users/13071/Desktop/pkq.jpg");

    // 直接将 A 等号赋值给 B,操作 B 影响 A
    // B = A;
    // for (int i = 0; i < B.rows; ++i)
    // {
    //     for (int j = 0; j < B.cols / 2; ++j)
    //     {
    //         B.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
    //     }
    // }
    // imwrite("C:/Users/13071/Desktop/pkq_1.jpg", A);

    // 使用拷贝构造函数, 操作 C 影响 A
    // Mat C(A);
    // for (int i = 0; i < C.rows; ++i)
    // {
    //     for (int j = 0; j < C.cols / 2; ++j)
    //     {
    //         C.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
    //     }
    // }
    // imwrite("C:/Users/13071/Desktop/pkq_2.jpg", A);

    // 获取部分图像区域的引用 操作部分区域同样影响 A
    // Mat D(A, Rect(10, 10, 100, 100));
    // for (int i = 0; i < D.rows; ++i)
    // {
    //     for (int j = 0; j < D.cols / 2; ++j)
    //     {
    //         D.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
    //     }
    // }
    // imwrite("C:/Users/13071/Desktop/pkq_3.jpg", A);

    // 函数传参
    ModifyImage(A);
    imwrite("C:/Users/13071/Desktop/pkq_4.jpg", A);
    return 0;
}

结论:对于 Mat 类对象,无论是直接赋值、使用拷贝构造函数还是用于函数传参(不传引用)均复制的是 Mat 类对象的矩阵头和矩阵指针,不会重新分配新的地址空间。多个引用指向同一块内存空间。

Mat 对象的矩阵指针可以通过 mat.data 获得

使用 copyTo 函数和 clone 函数,均会重新开辟新的内存空间,不同引用之间操作不会互相影响。

#include <opencv2\opencv.hpp>
using namespace cv;

int main()
{
    Mat A, B;
    A = imread("C:/Users/13071/Desktop/pkq.jpg");

    // A.copyTo(B);
    B = A.clone();
    for (int i = 0; i < B.rows; ++i)
    {
        for (int j = 0; j < B.cols / 2; ++j)
        {
            B.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
        }
    }
    imwrite("C:/Users/13071/Desktop/pkq_1.jpg", A);
    return 0;
}

1.1.2 Mat 对象的创建

Mat 对象不仅是一个图像容器,还可以用来操作多维矩阵。

可以使用 Mat 的运算符 << 输出 Mat 的内容(只对二维图像有效)

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main()
{
    // 直接构造函数创建
    // 图像类型 CV_[位数][是否带符号][类型前缀]C[通道数]
    Mat A(2, 2, CV_8UC3, Scalar(0, 0, 255));
    // cout << A << endl;

    // 使用构造函数创建高维 Mat 对象
    int sz[3] = {10, 10, 10};
    Mat B(3, sz, CV_8UC3, Scalar(0, 255, 0));

    // 使用 Create 函数创建
    Mat C;
    C.create(4, 4, CV_8UC3);

    // 使用 Matlab 的方式创建,使用 Mat 对象的成员函数
    Mat D, E, F;
    D = Mat::eye(4, 4, CV_8UC3);
    cout << "D:" << endl << D << endl;

    E = Mat::zeros(4, 4, CV_8UC3);
    cout << "E:" << endl << E << endl;

    F = Mat::ones(4, 4, CV_8UC3);
    cout << "F:" << endl << F << endl;
/* ------------------------------------------------------------------
D:
[  1,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0;
   0,   0,   0,   1,   0,   0,   0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0,   1,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0,   0,   0,   0,   1,   0,   0]
E:
[  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0]
F:
[  1,   0,   0,   1,   0,   0,   1,   0,   0,   1,   0,   0;
   1,   0,   0,   1,   0,   0,   1,   0,   0,   1,   0,   0;
   1,   0,   0,   1,   0,   0,   1,   0,   0,   1,   0,   0;
   1,   0,   0,   1,   0,   0,   1,   0,   0,   1,   0,   0]
------------------------------------------------------------------ */
    return 0;
}

1.2 常用数据结构和函数

1.2.1 Point 类

表示了二维坐标系下的点,其中Point_<int>、Point2i、Point 相互等价,Point_<float>、point2f 相互等价。可以直接使用 cout << 对 Point 类进行输出。

类似的三维坐标空间对应存在 Point3i、Point3d 等

1.2.2 Scalar 类

常被用于表示像素值

Scalar(a, b, c);

abc 依次表示 蓝色分量、绿色分量和红色分量

1.2.3 Size 类

Size(width, height);

用于定义宽高尺寸

1.2.4 Rect 类

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main()
{
    // 定义一个矩形,分别表示左上角坐标,宽和高
    Rect rect1(1, 1, 10, 10);
    Rect rect2(5, 5, 10, 10);

    // 区域求交
    Rect rect3 = rect1 & rect2;
    // 区域求并
    Rect rect4 = rect1 | rect2;
    cout << "rect3" << endl << rect3 << endl;
    cout << "rect4" << endl << rect4 << endl;

    // 平移
    Point point(1, 1);
    Rect rect5 = rect1 + point;
    cout << "rect5" << endl << rect5 << endl;

    // 放大【在原先长宽的基础上加上新增的长宽】
    Size size(20, 20);
    Rect rect6 = rect1 + size;
    cout << "rect6" << endl << rect6 << endl;

    return 0;
}

1.2.5 cvtColor() 函数

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main()
{
    Mat srcImage, dstImage;
    srcImage = imread("C:/Users/13071/Desktop/pkq.jpg");

    cvtColor(srcImage, dstImage, COLOR_BGR2GRAY);
    imwrite("C:/Users/13071/Desktop/pkq_1.jpg", dstImage);
    return 0;
}

2. core 组件进阶

2.1 访问图像中的像素

2.1.1 图像在内存中的存储方式

2.1.2 颜色空间缩减

将现有颜色空间(颜色数)映射到一个更小的颜色空间。

2.1.3 LUT 函数的使用

用于批量进行图像元素查找、扫描与操作图像。

2.1.4 计时函数

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main()
{
    double time0 = static_cast<double>(getTickCount());
    for (int i = 0; i < 1e8; ++i) {}
    double time1 = static_cast<double>(getTickCount());
    cout << (time1 - time0) / getTickFrequency() << "s" << endl;
    return 0;
}

以秒为单位进行计时

2.1.5 访问图像中像素的三种方式

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

// 使用 c 语言操作符 [] 进行访问元素
// void ColorReduce(const Mat& inputImage, Mat& outputImage, int div)
// {
//     outputImage = inputImage.clone();
//     int rows = outputImage.rows;
//     int cols = outputImage.cols * outputImage.channels();

//     for (int i = 0; i < rows; ++i)
//     {
//         // ptr 用于获取指定行的首地址
//         uchar* p = outputImage.ptr<uchar>(0);
//         for (int j = 0; j < cols; ++j)
//         {
//             p[j] = p[j] / div * div + div / 2;
//         }
//     }
//     return;
// }

// 使用迭代器访问元素
// void ColorReduce(const Mat& inputImage, Mat& outputImage, int div)
// {
//     outputImage = inputImage.clone();
//     Mat_<Vec3b>::iterator it = outputImage.begin<Vec3b>();
//     Mat_<Vec3b>::iterator itend = outputImage.end<Vec3b>();
//
//     while (it != itend)
//     {
//         (*it)[0] = (*it)[0] / div * div + div / 2;
//         (*it)[1] = (*it)[1] / div * div + div / 2;
//         (*it)[2] = (*it)[2] / div * div + div / 2;
//         ++it;
//     }
//     return;
// }

// 动态计算地址
void ColorReduce(const Mat& inputImage, Mat& outputImage, int div)
{
    outputImage = inputImage.clone();
    int rows = outputImage.rows;
    int cols = outputImage.cols;

    for (int i = 0; i < rows; ++i)
    {
        for (int j = 0; j < cols; ++j)
        {
            // at 方法获取图像中 (i, j) 位置的元素
            outputImage.at<Vec3b>(i, j)[0] = outputImage.at<Vec3b>(i, j)[0] / div * div + div / 2;
            outputImage.at<Vec3b>(i, j)[1] = outputImage.at<Vec3b>(i, j)[1] / div * div + div / 2;
            outputImage.at<Vec3b>(i, j)[2] = outputImage.at<Vec3b>(i, j)[2] / div * div + div / 2;
        }
    }

    return;
}
int main()
{
    Mat image = imread("C:/Users/13071/Desktop/pkq.jpg");
    Mat dstImage;

    double time0 = static_cast<double>(getTickCount());

    ColorReduce(image, dstImage, 32);

    double time1 = static_cast<double>(getTickCount());
    cout << (time1 - time0) / getTickFrequency() << "s" << endl;

    imwrite("C:/Users/13071/Desktop/dst.jpg", dstImage);
    return 0;
}

2.2 ROI区域图像叠加和图像混合

2.2.1 感兴趣区域ROI

选取原图像中的一块感兴趣区域,然后将子图像拷贝过去。

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main()
{
    Mat srcImage = imread("C:/Users/13071/Desktop/shida.jpg");
    Mat logoImage = imread("C:/Users/13071/Desktop/pkq.jpg");

    Mat imageRoi = srcImage(Rect(1000, 600, logoImage.cols, logoImage.rows));
    Mat mask = imread("C:/Users/13071/Desktop/pkq.jpg", 0);

    logoImage.copyTo(imageRoi, mask);

    imwrite("C:/Users/13071/Desktop/shida_1.jpg", srcImage);

    return 0;
}

2.2.2 计算数组加权和:addWeighted() 函数使用

设置两个图像在新图像中所占的比重,然后两个图像按比重混合

用于混合的两个图像需要尺寸和通道数相等

2.3 分离颜色通道、多通道图像混合

2.3.1 通道分离:split() 函数

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main()
{
    Mat srcImage = imread("C:/Users/13071/Desktop/shida.jpg");
    
    vector<Mat> channels;
    split(srcImage, channels);
    imwrite("C:/Users/13071/Desktop/shida_1.jpg", channels[0]);
    imwrite("C:/Users/13071/Desktop/shida_2.jpg", channels[1]);
    imwrite("C:/Users/13071/Desktop/shida_3.jpg", channels[2]);
    return 0;
}

2.3.2 通道合并:merge() 函数

2.4 图像对比度,亮度值调整

2.4.1 理论依据

调整图像对比度和亮度通常可以使用以下公式,其中 f(i, j) 为原图像中的像素,g(i, j) 为输出图像的像素,参数 a 用于调节对比度,参数 b 用于调节亮度。
g ( i , j ) = a ∗ f ( i , j ) + b g(i, j)=a^{*} f(i, j)+b g(i,j)=af(i,j)+b

2.5 离散傅里叶变换

注:本文主要是自己看书时的一些记录。by — 《OpenCV3编程入门》

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值