opencv c++ 入门

1.读入,保存,显示

  image = cv::imread("001.jpeg", cv::IMREAD_COLOR);

  image = cv::imread("001.jpeg", cv::IMREAD_GREYSCALE);
//IMREAD_COLOR-1 -RGB IMREAD_GREY-0-GREY
cv::imshow("Original Image", image);
CV::imwrite("D:\study_opencv\.001new2.jpeg", result);

注意事项:

注意把图片复制到这个文件夹中,而不是里面那个

保存

winodws下永远存在这个目录

2.图像的颜色空间

3.图片的频率表示

4.hsv图像提取

注意在opencv中格式为BGR

5.形态学操作:腐蚀和膨胀

腐蚀:去除小噪点

膨胀:连通白色区域

开,闭运算,保持大小不变

6.图像翻转

    // 正数表示水平 0表示垂直 负数表示水平和垂直
    cv::flip(image, result, 1);
#include <iostream>
// 图像数据结构核心头文件
#include <opencv2/core.hpp>
// 图形接口函数
#include <opencv2/highgui.hpp>
// 使用cv命名空间
// using namespace cv;


//图像翻转
int main(int, char**) {
    std::cout << "Hello, from Image_Reader!\n";
    // 1.创建一个空图像
    cv::Mat image;

    // 这里创建了一个尺寸为0x0的图像,可以通过访问cv:Mat的size属性来验证这一点。
    std::cout << "This image is " << image.rows << " x " << image.cols << std::endl;

    // 2.从文件读取一个图像
    image = cv::imread("001.jpeg");

    // 创建另一个空的图像
    cv::Mat result;
    // 正数表示水平 0表示垂直 负数表示水平和垂直
    cv::flip(image, result, 1);

    // 3.定义窗口(可选)
    // cv::namedWindow("Output Image");
    // 4.显示图像
    cv::imshow("output Image", result);

    // 5. 0 表示永远地等待按键 键入的正整数表示等待的毫秒数
    cv::waitKey(0);

}

7.Mat

以CV_8UC3为例,这个8表示的是每个通道都是8位,这个矩阵的每个元素(像素点)都是3个字节,即24位。

鼠标键盘事件

highgui模块中有大量可用来处理图像的函数,它们可以使用程序对鼠标或键盘事件做出响应,也可以图像上绘制形状或写入文本。

1.在图像上点击

通过编译,你可以让鼠标在置于图像窗口上时运行特定的指令。要实现这个功能,需定义一个合适的回调函数。回调函数不会被显式调用,但是会在响应特定事件的时候被程序调用。为了能被程序识别,回调函数需要具有特定的签名,并且必须注册。对于鼠标事件处理函数,回调函数必须具有这种签名:

void onMouse(int event,int x,int y,int flags,void* param);

第一个参数是整数,表示触发回调函数的鼠标事件的类型。后面两个参数是事件知己难得生时鼠标的位置,用像素坐标表示。参数flags表示事件发生进按下了鼠标的哪个键。最后一个参数是指向任意对象的指针,作为附加的参数知发送给函数。可以使用下面的方法注册回调函数:

cv::setMouseCallback("")

代码:
 


#include <iostream>
// 图像数据结构核心头文件
#include <opencv2/core.hpp>
// 图形接口函数
#include <opencv2/highgui.hpp>
// 使用cv命名空间
// using namespace cv;

// 3.先声明事件函数
void onMouse(int event, int x, int y, int flags, void *param);

// 1.装载、显示和存储图像
int main(int, char **)
{
    std::cout << "Hello, from Image_Reader!\n";
    // 1.创建一个空图像
    cv::Mat image;

    // 这里创建了一个尺寸为0x0的图像,可以通过访问cv:Mat的size属性来验证这一点。
    std::cout << "This image is " << image.rows << " x " << image.cols << std::endl;

    // 2.从文件读取一个图像
    image = cv::imread("../images/puppy.bmp", cv::IMREAD_COLOR);
    // 此代码一定要写在事件注册前,否则事件获取不到窗口
    cv::imshow("Original Image", image);

    // 5.注事鼠标事件
    // Original Image:表示监听鼠标事件的窗口(Original Image 要与 cv::imshow函数值对应,也就是窗口标题要一致)
    // onMouse	表示响应函数,即当鼠标事件触发时调用的函数。请看3的定义
    // 用户传递给回调函数的数据,用来处理轨迹条事件,这里传入imag图像
    cv::setMouseCallback("Original Image", onMouse, reinterpret_cast<void *>(&image));

    // 6. 0 表示永远地等待按键 键入的正整数表示等待的毫秒数
    cv::waitKey(0);
}
/**
 * 4. 定义鼠标事件
 */
void onMouse(int event, int x, int y, int flags, void *param)
{
    cv::Mat *im = reinterpret_cast<cv::Mat *>(param);
    switch (event)
    {
    case cv::EVENT_LBUTTONDOWN:
        // 显示像素值(x,y)
        // 这里调用cv::Mat对象的at方法来获取(x,y)的像素值。
        // 鼠标的回调事件可能收到的事件还有cv::EVENT_MOUSEMOVE(鼠标移动)、
        // cv::EVENT_LBUTTONUP(鼠标左键抬起)、
        // cv::EVENT_RUBUTTONDOWN(鼠标右键按下)和CV::EVENT_RUBUTTONUP(鼠标右键抬起)
        std::cout << "at(" << x << "," << y << ") values is:" << static_cast<int>(im->at<uchar>(cv::Point(x, y))) << std::endl;
        break;

    default:
        break;
    }
}

说明:

注意窗口显示与注册事件的先后顺序,以及窗口标题与注册事件第一个参数要一致。

运行结果:

image-20231015230538774

2.在图像上绘图

必须引入头文件:

// circle  cv::putText 等图形函数使用
#include <opencv2/imgproc.hpp>
OpenCV还提供了几个用于在图像上绘制形状和写入文本的函数。

基本函数有circle、ellipse、line和rectangle。这是一个使用cirecle函数的例子:

#include <iostream>
// 图像数据结构核心头文件
#include <opencv2/core.hpp>
// 图形接口函数
#include <opencv2/highgui.hpp>
// circle函数使用
#include <opencv2/imgproc.hpp>
// 使用cv命名空间
// using namespace cv;

// 1.装载、显示和存储图像
int main(int, char **)
{
    // 1.创建一个空图像
    cv::Mat image;
    image = cv::imread("../images/puppy.bmp", cv::IMREAD_COLOR);
    // 2.绘制圆形:需引入头文件:opencv2/imgproc.hpp
    cv::circle(image,               // 目标图像
               cv::Point(155, 110), // 中心点坐标 cv::Point()表示像素的坐标
               65,                  // 半径
               0,                   // 颜色(0表示黑色)
               3                    // 厚度
    );

    // 绘制文本
    cv::putText(image,                     // 目标图像
                "This is a dog",           // 文本 
                cv::Point(40, 200),        // 文本位置
                cv::FONT_HERSHEY_PLAIN,    // 字体类型
                2.0,                       // 字体大小
                255,                       // 字体颜色(蓝色),因为这个图为,3通道彩色图,BGR,所在255为蓝色
                2);                        // 文本厚度

    // 显示窗口
    cv::imshow("Original Image", image);

    // 6. 0 表示永远地等待按键 键入的正整数表示等待的毫秒数
    cv::waitKey(0);
}

说明:

显示为蓝色字

image-20231015232156839

如果要显示白色:

    // 绘制文本
    cv::putText(image,                     // 目标图像
                "This is a dog",           // 文本 
                cv::Point(40, 200),        // 文本位置
                cv::FONT_HERSHEY_PLAIN,    // 字体类型
                2.0,                       // 字体大小
                cv::Scalar(255, 255, 255), // 字体颜色(白色) Scalar
                2);                        // 文本厚度

image-20231015232716021

、深入了解cv::Mat数据结构

cv::Mat数据结构,它是程序库中的关键部件,用来操作图像和矩阵(从计算机和数学的角度看,图像其实就是矩阵)。

1.测试cv::Mat数据结构的不同属性

以下代码:

编写时每个cv::waitKey(0); 为 一段,写完一个测试一个看下效果,请多次编译多次运行。

#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, char **)
{
   	// 1.创建一个240行x320列的新图像 CV_8U灰度图(U 无符号 S有符号 对于3通道CV_8U3C)
    // 100为颜色
	cv::Mat image1(240, 320, CV_8U, 100);
    // 显示图像
	cv::imshow("Image", image1); 
    // 等待按键 
	cv::waitKey(0); 

	// 2.重新分配一个新图像
	image1.create(200, 200, CV_8U);
    // 颜色
	image1 = 200;
    // 显示图像
	cv::imshow("Imgae", image1); 
    // 等待按键
	cv::waitKey(0); 

	// 3.创建一个红色的图像
	//  通道次序为BGR
	cv::Mat image2(240, 320, CV_8UC3, cv::Scalar(0, 0, 255));
	
	//或者
	//cv::Mat image2(cv::Size(320, 240), CV_8UC3);
	//image2 = cv::Scalar(0, 0, 255);
    //显示图像
	cv::imshow("Imgae", image2); 
    //等待按键
	cv::waitKey(0);  

	// 4.读入一幅图像
	cv::Mat image3 = cv::imread("../images/puppy.bmp");

	// 所有这些图像都指向同一个数据块
	cv::Mat image4(image3);
    // 浅复制,只是改变了图像头的引用,性能好,但原图像会被改变
	image1 = image3;  

	// 5.这些图像是源图像的副本图像
    // copyto做深复制,目标图像会调用create方法
	image3.copyTo(image2);        
    // clone创建一个完全相同的新图像   
	cv::Mat image5 = image1.clone();  

	// 6.转换图像进行测试,水平与垂直反转
	cv::flip(image3, image3, -1);

	// 检查哪些图像在处理过程中受到了影响
	cv::imshow("Imgae3", image3); //显示图像
	cv::imshow("Imgae1", image1); //显示图像
	cv::imshow("Imgae2", image2); //显示图像
	cv::imshow("Imgae4", image4); //显示图像
	cv::imshow("Imgae5", image5); //显示图像
    //等待按键
	cv::waitKey(0);  

	// 7.从函数中读取一个灰度图像,调用在main方法定义的函数
	cv::Mat gray = function();
	//当局部变量ima超出作用范围后,ima会被释放。但从相关引用计数器可以看到,
    // 另一个实例(即变量gray)引用了ima内部的图像数据,因此ima的内存块不会被释放
	cv::imshow("Imgae", gray);  //显示图像
	cv::waitKey(0);             //等待按键

    // 8.作为灰度图像输入,注意:
    image1 = cv::imread("../images/puppy.bmp", cv::IMREAD_GRAYSCALE);
    // 如果你需要把一幅图像复制到另一幅图像中,且两者的数据类型不一定相同,
    //  那就要使用convertTo方法了(这两幅图像的通道数量必须相同)
    // 参数:
    // m      目标矩阵。如果m在运算前没有合适的尺寸或类型,将被重新分配。
    // rtype  目标矩阵的类型。因为目标矩阵的通道数与源矩阵一样,所以rtype也可以看做是目标矩阵的位深度。如果rtype为负值,目标矩阵和源矩阵将使用同样的类型。
    // alpha  尺度变换因子(可选)。
    // beta  附加到尺度变换后的值上的偏移量(可选)。
    image1.convertTo(image2, CV_32F, 1 / 255.0, 0.0);

    cv::imshow("Imgae", image2); // 显示图像
    cv::waitKey(0);              // 等待按键
    return 0;
}

说明:

cv::Mat有两个必不可少的级成部分:一个头部和一个数据块。

头部包含了矩阵的所有相关信息(大小、通道、数据类型等)。

数据块包含了图像中所有像素的值。头部有一个指向数据块的指针,即data属性。cv::Mat有一个很重要的属性,即只有在明确要求时,内存块才会被复制。实际上,大多数据作仅仅复制了cv::Mat的头部,因此多个对象会指向同一个数据块。这种内存管理模式可以提高应用程序的运行效率,避免内存泄露,但是我们必须了解它带来的的后果。

运行结果:

①灰度图

image-20231016002310511

②重新分配一个新图像

image-20231016002339615

③创建一个红色的图像

image-20231016002412341

④图像复制

浅复制:image1 = image3

深复制:

	// 5.这些图像是源图像的副本图像
    // copyto做深复制,目标图像会调用create方法
	image3.copyTo(image2);        
    // clone创建一个完全相同的新图像   
	cv::Mat image5 = image1.clone(); 

浅复制:将原始对象的指针值复制到副本中,即指针复制,原始对象和副本共享引用的数据;相当于创建了一个文件的快捷方式。

深复制:复制原始对象指针所引用的数据,并将其赋给副本对象,即内容复制,相当于创建了一份新的文件。

例如,当我们为一个类的属性添加copy关键字时,那么对这个属性赋值时(即:调用setter方法),就会执行深拷贝操作,同时还需要改类遵守NSCopying协议。当我们把属性关键字改为strong或者weak时,那么对这个属性赋值时,就会执行浅拷贝(只拷贝指针地址)。

对image3做了修改,其它图像也包含了这幅图像,有的图像共用了同一个图像数据,有的图像则有图像数据的独立副本。查看显示的图像,可以看出:image1 image3 image4进行了改变,也就是浅复制。

image-20231016002449561

⑤从函数中读取一个灰度图像

image-20231016002532225

⑥作为灰度图像输入

注意:

将对应OpenCV 3.x版本的旧标志替换为OpenCV 4.x`版本的新标志即可

   CV_LOAD_IMAGE_UNCHANGED  = -1 ( = cv::IMREAD_UNCHANGED),
   CV_LOAD_IMAGE_GRAYSCALE  = 0  ( = cv::IMREAD_GRAYSCALE),
   CV_LOAD_IMAGE_COLOR      = 1  ( = cv::IMREAD_COLOR),
   CV_LOAD_IMAGE_ANYDEPTH   = 2  ( = cv::IMREAD_ANYDEPTH),
   CV_LOAD_IMAGE_ANYCOLOR   = 4

image-20231016002601401

本例中的原始图像被复制进了一幅浮点型图像。这一方法包含两个可选参数:缩放比例和偏 移量。需要注意的是,这两幅图像的通道数量必须相同。 cv::Mat 对象的分配模型还能让程序员安全地编写返回一幅图像的函数(或类方法): cv::Mat function() { // 创建图像 cv::Mat ima(240,320,CV_8U,cv::Scalar(100)); // 返回图像 return ima; } 我们还可以从 main 函数中调用这个函数: // 得到一个灰度图像 cv::Mat gray= function(); 运行这条语句后,就可以用变量 gray 操作这个由 function 函数创建的图像,而不需要额 外分配内存了。正如前面解释的,从 cv::Mat 实例到灰度图像实际上只是进行了一次浅复制。 当局部变量 ima 超出作用范围后,ima 会被释放。但是从相关引用计数器可以看出,另一个实例 (即变量 gray)引用了 ima 内部的图像数据,因此 ima 的内存块不会被释放。 请注意,在使用类的时候要特别小心,不要返回图像的类属性。下面的实现方法很容易 引发错误:

class Test { // 图像属性 cv::Mat ima; 
public:
 // 在构造函数中创建一幅灰度图像 Test() : ima(240,320,CV_8U,cv::Scalar(100)) {} 
// 用这种方法回送一个类属性,这是一种不好的做法
 cv::Mat method() { return ima; }
 };

如果某个函数调用了这个类的 method,就会对图像属性进行一次浅复制。副本一旦被修改, class 属性也会被“偷偷地”修改,这会影响这个类的后续行为(反之亦然)。这违反了面向对 象编程中重要的封装性原理。为了避免这种类型的错误,你需要将其改成返回属性的一个副本。

2.输入输出数组

cv::InputArray类型作为输入参数

cv::OutputArray用来指定某方法或函数的返回数组。

3.处理小矩阵

模板类:cv::Matx和它的子类。

定义一个3x3的双精度浮点矩阵和一个3元素的向量,然后使用两者相乘:

#include <iostream>
// 图像数据结构核心头文件
#include <opencv2/core.hpp>
// 图形接口函数
#include <opencv2/highgui.hpp>

int main(int, char **)
{
    // 3X3双精度型矩阵
    cv::Matx33d matrix(3.0, 2.0, 1.0,
                       2.0, 1.0, 3.0,
                       1.0, 2.0, 3.0);

    // 3x1矩阵(即向量)
    cv::Matx31d vector(5.0, 1.0, 3.0);

    // 相乘
    cv::Matx31d result=matrix*vector;

    std::cout<<result<<std::endl;
    return 0;
}

定义感兴趣区域

有时需要让一个处理函数只在图像的某个部分起作用。OpenCV内嵌了一个精致又简洁的机制,可以定义图像的子区域,并把这个子区域当作普通的图像进行操作。

假设我们要把一个小图像复制到一个大图像上。例如要把下面的标志插入到测试图你中。

为了这病这个功能可以定义一个感兴趣的区域(Region Interest, ROI),在此处进行复制操作,这个ROI的位置将瘊定标志的插入位置。

#include <iostream>
// 图像数据结构核心头文件
#include <opencv2/core.hpp>
// 图形接口函数
#include <opencv2/highgui.hpp>

int main(int, char **)
{
    // 1.读取图像
    cv::Mat image = cv::imread("../images/puppy.bmp");
    // 2.读取另一张图
    cv::Mat logo = cv::imread("../images/logo.png");    
    // 3.在第一张图image上找一块区域
    cv::Mat imageROI(image, cv::Rect(image.cols - logo.cols, // ROI坐标
                                     image.rows - logo.rows,
                                     logo.cols, logo.rows   // ROI大小
                                     ));

    // 插入标志
    logo.copyTo(imageROI);

    // 显示图像
    cv::imshow("ROI", image);

    cv::waitKey(0);
    return 0;
}

image-20231017083804362

使用图像掩码

OpenCV中的有些操作可以用来定义掩码。函数或方法通常对图像中所有通过定义掩码可以限制这些函数或方法的作用范围。掩码是一个8位图像,如果掩码中某个位置的值不为0,在这个位置上的操作就会起作用;如果掩码中某些像素位置的值为0,那么对图像中相应位置的操作将不起作用。例如,在调用copyTo方法时就可以使用掩码,我们可以利用掩码只复制标志中白色的部分,如下所示

#include <iostream>
// 图像数据结构核心头文件
#include <opencv2/core.hpp>
// 图形接口函数
#include <opencv2/highgui.hpp>
// cvtColor函数,头文件
#include <opencv2\imgproc\imgproc.hpp>

using namespace std;
using namespace cv;

int main(int, char **)
{
    // 1.读取一张图像
    Mat image = imread("../images/puppy.bmp");
    // 2.读取另一张图
    cv::Mat logo = cv::imread("../images/smalllogo.png");
    // 在图像的右下角定义一个ROI
    Mat imageROI(image, 
		          cv::Rect(image.cols-logo.cols, //ROI坐标
                           image.rows-logo.rows,
		                   logo.cols,logo.rows));//ROI 大小
 
    // 把标志作为掩码(必须是灰度图像)
    Mat mask(logo);

    // 插入标志,只复制掩码不为0的位置,纯黑不显示
    logo.copyTo(imageROI, logo);
    // logo.copyTo(imageROI);

    imshow("Image", image);

    waitKey(0);

    return 0;
}

说明:

logo图最好使用二值化的图,或对彩色图像进行二值化处理。

二值化:

二值图像(Binary Image)是指将图像上的每一个像素只有两种可能的取值或灰度等级状态,人们经常用黑白、B&W、单色图像表示二值图像。

二值图像是指在图像中,灰度等级只有两种,也就是说,图像中的任何像素点的灰度值均为0或者255,分别代表黑色和白色。

运行结果:

image-20231017222751901

因为标志的背景是黑色的(因此值为0),所以很容易同时作为被复制图像和掩码来使。 当然,我们也可以在程序中自己决定如何定义掩码。OpenCV中大多数基于像素的操作都可以使用掩码。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值