【OpenCV】OpenCV学习笔记(1)

0.Getting Started with Images

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;
int main()
{
    std::string image_path = "../test.png";
    Mat img = imread(image_path, IMREAD_COLOR);
    if(img.empty())
    {
        std::cout << "Could not read the image: " << image_path << std::endl;
        return 1;
    }
    imshow("Display window", img);
    int k = waitKey(0); // Wait for a keystroke in the window
    if(k == 's')
    {
        imwrite("test.png", img);
    }
    return 0;
}

(1)读取图像

    std::string image_path = "../test.png";
    Mat img = imread(image_path, IMREAD_COLOR);

​ 调用 cv::imread 函数使用指定的文件路径加载图像第一个参数。第二个参数是可选的,指定我们想要的图像格式。

​ 图像格式:

格式名称描述
IMREAD_COLOR以 BGR 8 位格式加载图像
IMREAD_UNCHANGED按原样加载图像(包括 alpha 通道,如果存在)
IMREAD_GRAYSCALE将图像加载为强度图像

(2)检查图像(加载图像成功)

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

(3)显示图像

​ 调用 cv::imshow 函数显示图像。第一个参数是窗口的标题,第二个参数是将显示的 cv::Mat 对象。

​ 因为希望窗口一直显示到用户按下一个键为止(否则程序会很快结束),所以我们使用cv::waitKey函数,该函数的唯一参数是它应该等待用户输入多长时间(以毫秒为单位)。零意味着永远等待。返回值是按下的键。

    imshow("Display window", img);
    int k = waitKey(0); // Wait for a keystroke in the window

(4)

​ 如果按下的键是 “s” 键,则图像会写入文件。为此,调用了cv::imwrite函数,该函数将文件路径和cv::Mat对象作为参数。

    if(k == 's')
    {
        imwrite("test.png", img);
    }
    return 0;

1.The Core Functionality

1.1 Mat - 基本图像容器

(1)Mat

​ Mat基本上是一个包含两个数据部分的类:矩阵头(包含矩阵大小、用于存储的方法、存储矩阵的地址等信息)和一个指向包含像素值的矩阵的指针(根据选择的存储方法取任意维数)。

Mat A, C;                          // creates just the header parts
A = imread(argv[1], IMREAD_COLOR); // here we'll know the method used (allocate matrix)
Mat B(A);                                 // Use the copy constructor
C = A;                                    // Assignment operator
(2)存储方法

​ 存储像素值,可以选择使用的颜色空间和数据类型。

​ 颜色空间是指如何组合颜色分量以编码给定的颜色。最简单的一个是灰色,我们可以使用的颜色是黑色和白色。这些组合使我们能够创建许多灰色阴影。

​ 对于丰富多彩的方式,我们有更多的选择方法。将它们分解成三到四个基本组件,我们可以使用这些组合来创建其他组件。最流行的是RGB,主要是因为这也是我们的眼睛如何建立颜色。其基色为红,绿,蓝。为了编码颜色的透明度有时是第四个元素:添加了α(A)。

然而,还有许多其他颜色系统都有自己的优势:

  • RGB是最常见的,因为我们的眼睛使用类似的东西,但是,OpenCV标准显示系统使用BGR颜色空间(红色和蓝色通道的开关)组成颜色。
  • HSV和HLS将颜色分解为色调,饱和度和值/亮度分量,这是我们描述颜色的更自然的方式。例如,您可能会忽略最后一个组件,使您的算法对输入图像的光线条件不太敏感。
  • YCrCb被流行的JPEG图像格式使用。
  • CIE L * a * b *是感知统一的颜色空间,如果您需要测量给定颜色与其他颜色的距离,则可方便使用。
(3)创建一个Mat对象

​ Mat是一个图像容器,但它也是一个通用的矩阵类。因此,可以创建和操纵多维矩阵。

   	Mat M(2,2, CV_8UC3, Scalar(0,0,255));
   	cout << "M = " << endl << " " << M << endl << endl;

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

CV_[位数][有无符号][类型前缀]C[通道数]

​ 例如,CV_8UC3意味着我们使用8位长的无符号字符类型,每个像素有其中的三个以形成三个通道。cv::Scalar is four element short vector。指定它,可以使用自定义值初始化所有矩阵点。

​ 上面的例子显示了如何创建一个具有两维以上的矩阵。指定其维度,然后传递一个包含每个维的大小的指针,其余的保持不变。

   	M.create(4,4, CV_8UC(2));
    cout << "M = "<< endl << " "  << M << 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;
(4)输出格式

​ 使用默认就够了。

1.2 如何使用OpenCV扫描图像、查找表格和时间测量

1.3 矩阵上的掩码操作

1.4 图像操作

(1)输入/输出

​ 从文件加载图像:

Mat img = imread(filename);

​ 如果读取jpg文件,则默认情况下会创建3通道映像。如果需要灰度图像,则使用:

Mat img = imread(filename, IMREAD_GRAYSCALE);

​ 将图像保存到文件中:

imwrite(filename, img);
(2)基本操作

​ 1 访问像素强度值

​ 为了获得像素强度值,必须知道图像的类型和通道数。以下是单通道灰度图像(类型8UC1)和像素坐标x和y的示例:

Scalar intensity = img.at<uchar>(y, x);

​ 现在考虑使用BGR颜色排序的3通道图像(由imread返回的默认格式):

Vec3b intensity = img.at < Vec3b >(y,x);
uchar blue = intensity.val [0];
uchar green = intensity.val [1];
uchar red = intensity.val [2];
Vec3b intensity = img.at < Vec3b > (y, x);
float blue = intensity.val [0];
float green = intensity.val [1];
float red = intensity.val [2];

​ 2 内存管理和引用计数

​ 3 可视化图像

1.5 使用OpenCV添加(混合)两个图像

(1)混合理论

g ( x ) = ( 1 − α ) f 0 ( x ) + α f 1 ( x ) g(x) = (1- \alpha)f_0(x) + \alpha f_1(x) g(x)=(1α)f0(x)+αf1(x)

​ 其中, g ( x ) g(x) g(x) 表示融合图片中的像素点, f 0 ( x ) f_0(x) f0(x) f 1 ( x ) f_1(x) f1(x) 分别表示背景和前景中的像素点, α \alpha α 表示两种图片的融合比例。

(2)代码
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/imgproc/imgproc.hpp>   
#include <iostream>  
#include <iostream>
using namespace cv;
// we're NOT "using namespace std;" here, to avoid collisions between the beta variable and std::beta in c++17
using std::cin;
using std::cout;
using std::endl;
int main(void)
{
    double alpha = 0.5; 
    double beta; 
    double input;
    Mat src1, src2, dst;
    cout << " Simple Linear Blender " << endl;
    cout << "-----------------------" << endl;
    cout << "* Enter alpha [0.0-1.0]: ";
    cin >> input;
    // We use the alpha provided by the user if it is between 0 and 1
    if( input >= 0 && input <= 1 )
    {
        alpha = input; 
    }
    src1 = imread("../Linux_logo.jpg");
    src2 = imread("../Windows-logo.jpg");
    if( src1.empty() ) 
    { 
        cout << "Error loading src1" << endl;
        return EXIT_FAILURE; 
    }
    if( src2.empty() ) 
    { 
        cout << "Error loading src2" << endl; 
        return EXIT_FAILURE;
    }
    resize(src2, src2, Size(src1.cols, src1.rows)); 
    
    beta = ( 1.0 - alpha );
    addWeighted( src1, alpha, src2, beta, 0.0, dst);
    imshow( "Linear Blend", dst );
    waitKey(0);
    return 0;
}

1.6 改变图像的对比度和亮度

(1)理论

g ( x ) = α f ( x ) + β g(x) = \alpha f(x) + \beta g(x)=αf(x)+β

​ 其中,通常 α > 0 \alpha>0 α>0 ,且 α \alpha α 被称为增益(gain), β \beta β 被称为偏置(bias)。

(2)注意点

​ 1 新图像:初始像素值等于零,与原始图像相同的大小和类型

Mat new_image = Mat::zeros( image.size(), image.type() );

​ 2 BGR图像

​ 每像素(B,G和R)有三个值,因此将分别访问它们。

​ 3 saturate_cast() 函数

​ 在图像处理方面,无论是加是减,乘除,都会超出一个像素灰度值的范围(0~255),saturate_cast函数的作用即是:当运算完之后,结果为负,则转为0,结果超出255,则为255。

#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace std;
using namespace cv;
int main( int argc, char** argv )
{
    double alpha = 1.0; /*< Simple contrast control */
    int beta = 0;       /*< Simple brightness control */
    String imageName("../ubuntu.png"); // by default
    if (argc > 1)
    {
        imageName = argv[1];
    }
    Mat image = imread( imageName );
    Mat new_image = Mat::zeros( image.size(), image.type() );
    cout << " Basic Linear Transforms " << endl;
    cout << "-------------------------" << endl;
    cout << "* Enter the alpha value [1.0-3.0]: "; cin >> alpha;
    cout << "* Enter the beta value [0-100]: ";    cin >> beta;
    for( int y = 0; y < image.rows; y++ ) {
        for( int x = 0; x < image.cols; x++ ) {
            for( int c = 0; c < 3; c++ ) {
                new_image.at<Vec3b>(y,x)[c] =
                  saturate_cast<uchar>( alpha*( image.at<Vec3b>(y,x)[c] ) + beta );
            }
        }
    }
    namedWindow("Original Image", WINDOW_AUTOSIZE);
    namedWindow("New Image", WINDOW_AUTOSIZE);
    imshow("Original Image", image);
    imshow("New Image", new_image);
    waitKey();
    return 0;
}

1.7 离散傅里叶变换

(1)载入原始图像
(2)将图像扩大到合适的尺寸

​ DFT 的性能取决于图像大小。 对于数字二、三和五的倍数的图像大小,它往往是最快的。 因此,为了获得最大的性能,通常将边界值填充到图像以获得具有此类特征的大小是一个好主意。 getOptimalDFTSize() 返回这个最佳尺寸,可以使用 copyMakeBorder() 函数来扩展图像的边框(附加像素初始化为零):

	int m = getOptimalDFTSize(I.rows);
    int n = getOptimalDFTSize( I.cols ); // on the border add zero values
    copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
(3)傅里叶变换结果分配存储空间

​ 变换结果为复数,每个原图像值结果会有两个图像值

    Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
    Mat complexI;
    merge(planes, 2, complexI);         // Add to the expanded another plane with zeros
(4)傅里叶变换
    dft(complexI, complexI);            // this way the result may fit in the source matrix
(5)复数转化为幅值

​ 变换结果是复数,对应幅值表示为M:
M = R e ( D F T ( I ) ) 2 + I m ( D F T ( I ) 2 ) M = \sqrt{Re(DFT(I))^2 + Im(DFT(I)^2)} M=Re(DFT(I))2+Im(DFT(I)2)

    //将多通道 complexI 分离成几个单通道数组
	split(complexI, planes);                   // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
    magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude
    Mat magI = planes[0];
(6)进行对数尺度缩放

M 1 = l n ( 1 + M ) M_1 = ln(1+M) M1=ln(1+M)

    magI += Scalar::all(1);                    // switch to logarithmic scale
    log(magI, magI);
(7)剪切和重分布幅度图像象限

​ 第2步扩展了图像,现在需要将新添加的像素剔除。可以重新分布图像象限位置。

	magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));
    // rearrange the quadrants of Fourier image  so that the origin is at the image center
    int cx = magI.cols/2;
    int cy = magI.rows/2;
    Mat q0(magI, Rect(0, 0, cx, cy));   // Top-Left - Create a ROI per quadrant
    Mat q1(magI, Rect(cx, 0, cx, cy));  // Top-Right
    Mat q2(magI, Rect(0, cy, cx, cy));  // Bottom-Left
    Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right
	
    Mat tmp;                           // swap quadrants (Top-Left with Bottom-Right)
    q0.copyTo(tmp);
    q3.copyTo(q0);
    tmp.copyTo(q3);

    q1.copyTo(tmp);                    // swap quadrant (Top-Right with Bottom-Left)
    q2.copyTo(q1);
    tmp.copyTo(q2);
(8)归一化
	normalize(magI, magI, 0, 1, NORM_MINMAX); // Transform the matrix with float values into a
(9)结论

​ 从频谱图上可以看出,当将频谱移频到原点以后,图像中心比较亮。在频谱图中,一个点的亮暗主要与这个频率中点的数目和点的灰度值有关,也就是说在空间域中包含这种频率的点越多。而经过频移后,频率为0的部分,也就是傅里叶变换所得到的常量分量在图像中心,往外扩散,点所代表的频率越来越高。也说明了图像中的“能量”主要集中在低频部分。

1.5 视觉slam十四讲中涉及的

(1)基本使用

#include <iostream>
#include <chrono>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

int main(int argc, char *argv[])
{
    std::string img_path = "../ubuntu.png";
    cv::Mat img;
    img = cv::imread(img_path);
    if (img.data == nullptr)
    {
        std::cerr << "文件不存在!" << std::endl;
    }

    std::cout << "图像的宽:" << img.cols << "\n"
              << "图像的高:" << img.rows << "\n"
              << "通道数:" << img.channels() << std::endl; 

    cv::imshow("image", img);
    cv::waitKey(0);

    if (img.type() != CV_8UC1 && img.type() != CV_8UC3)
    {
        std::cout << "请输入一张彩色图或灰度图!!!" << std::endl;
        return 0;
    }

    //std::chrono 计时
    std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
    for (size_t y = 0; y < img.rows; y++)
    {
        unsigned char* row_ptr = img.ptr<unsigned char>(y);
        for (size_t x = 0; x < img.cols; x++)
        {
            unsigned char* data_ptr = &row_ptr[x * img.channels()];
            for (int c = 0; c != img.channels(); c++)
            {
                unsigned char data = data_ptr[c];   //data: I(x, y)第c个通道的值
            }
        }
    }
    std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
    std::chrono::duration<double> time_used = std::chrono::duration_cast<std::chrono::duration<double>> (t2 - t1);
    std::cout << "遍历图像用时:" << time_used.count() << " s" << std::endl;

    //cv::Mat
    cv::Mat img_another = img;
    img_another(cv::Rect(0, 0, 100, 100)).setTo(100);
    cv::imshow("img_another", img);
    cv::waitKey(0);


    cv::Mat image = img.clone();
    image(cv::Rect(0, 0, 100, 100)).setTo(255);
    cv::imshow("image_clone", image);
    cv::imshow("img", img);
    cv::waitKey(0);

    //cv::destroyAllWindows();

    return 0;
}

(2)图像去畸变

​ 重看确实有新的体会,为什么第一遍的时候没想到 _

​ 输入图像有畸变,即需要将畸变图像的像素坐标转化到归一化坐标,根据公式计算去畸变后的归一化坐标,然后再投影到像素平面得到新的去畸变后的图像。

#include <iostream>
#include <string>
#include <opencv2/opencv.hpp>

std::string image_file = "../jibian.jpg";

int main(int argc, char *argv[])
{
    //畸变参数
    double k1 = -0.28340811;
    double k2 = 0.07395907;
    double p1 = 0.00019359;
    double p2 = 1.76187114e-05;

    //内参
    double fx = 458.654;
    double fy = 457.296;
    double cx = 367.215;
    double cy = 248.375;

    cv::Mat image = cv::imread(image_file, 0);
    int rows = image.rows;
    int cols = image.cols;
    cv::Mat image_undistort = cv::Mat(rows, cols, 0);

    for (int v = 0; v < image.rows; v++)
    {
        for (int u = 0; u < image.cols; u++)
        {
            double x;
            double y;
            x = (u - cx) / fx;
            y = (v - cy) / fy;
            double r;
            double r2;
            double r4;
            r = sqrt(x * x + y * y);
            r2 = r * r;
            r4 = r2 * r2;

            double x_distorted;
            double y_distorted;
            x_distorted = x * (1 + k1 * r2 + k2 * r4) + 2 * p1 * x * y + p2 * (r2 + 2 * x * x);
            y_distorted = y * (1 + k1 * r2 + k2 * r4) + 2 * p2 * x * y + p1 * (r2 + 2 * y * y);

            double u_dis = fx * x_distorted + cx;
            double v_dis = fy * y_distorted + cy;
            if (u_dis >= 0 && v_dis >= 0 && u_dis < cols && v_dis < rows)
            {
                image_undistort.at<uchar>(v, u) = image.at<uchar> ((int) v_dis, (int) u_dis);
            }
            else
            {
                image_undistort.at<uchar> (v, u) = 0;
            }
        }
    }

    cv::imshow("未去畸变图", image);
    cv::imshow("去畸变后的图", image_undistort);

    cv::waitKey(0);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,OpenCV是一个开源计算机视觉和机器学习软件库,用于开发图像和视频处理应用程序。通过使用OpenCV,您可以读取摄像头并显示实时图像,打开视频文件或摄像头文件,并获取视频的相关信息,例如帧宽度、帧高度、帧率和总帧数。 对于学习OpenCV,你可以按照以下步骤进行: 1. 安装OpenCV库:在开始学习OpenCV之前,您需要从OpenCV官方网站下载和安装OpenCV库。根据您的操作系统和编程语言选择合适的版本。 2. 学习基本概念:熟悉OpenCV的基本概念和术语,例如图像和视频的加载、显示、保存以及常用的图像处理操作,如滤波、边缘检测和特征提取等。 3. 掌握OpenCV函数和类:深入了解OpenCV提供的函数和类,例如cv::Mat用于图像和矩阵操作,cv::VideoCapture用于读取和处理视频,以及cv::imshow和cv::waitKey等用于显示图像的函数。 4. 实践项目:通过完成一些实践项目来应用您所学到的知识。例如,利用OpenCV实现人脸检测、目标追踪、图像识别等。 5. 学习资料和资源:查找和阅读OpenCV的官方文档、教程和示例代码,参与开源社区讨论和交流,加入相关的论坛和邮件列表等。 总结起来,学习OpenCV包括安装OpenCV库、学习基本概念、掌握OpenCV函数和类、实践项目以及查找和阅读相关资料和资源。通过不断实践和学习,您将能够更好地理解和应用OpenCV库来开发图像和视频处理应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值