Opencv 学习笔记

    技术2022-07-10  158

    这里写自定义目录标题

    简介模块 图片基本操作显示图片图像腐蚀图像模糊canny边缘检测 读取并播放视频调用摄像头采集图像HighGUI图形界面用户图像的载入、显示与输出滑动条的创建和使用鼠标操作 core组件基础Mat数据解构像素值的存储方法常用数据结构和函数Point点类 Scalar颜色类Size尺寸类Rect矩形类cvtColor()颜色空间转换其他常用的知识点基本图形的绘制DrawEllipse()DrawFilledCircle()DrawPolygon()DrawLine() core组件进阶访问图像中的像素图像在内存之中的存储方式颜色空间缩减LUT函数:Look up table操作计时函数访问图像中像素的三类方法 ROI区域图像叠加&图像混合感兴趣区域:ROI线性混合操作 分离颜色通道、多通道图像混合split()函数 新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入

    简介

    图像处理(Image Processing)是用计算机对图像进行分析,以达到所需结果的技术,又称影像处理。图像处理技术一般包括图像压缩,增强和复原,匹配、描述和识别3个部分。图像处理一般指数字图像处理(Digital Image Processing)。其中,数字图像是指用工业相机、摄像机、扫描仪等设备经过拍摄得到的一个大的二维数组。该数组的元素称为像素,其值称为灰度值。而数字图像处理是通过计算机对图像进行去除噪声、增强、复原、分割、提取特征等处理的方法和技术。

    计算机视觉(Computer Vision)是一门研究如何使机器“看”的科学,具体地说,就是是指用摄影机和电脑代替人眼对目标进行识别、跟踪和测量等机器视觉,并进一步做图形处理,用电脑处理使之成为更适合人眼观察或传送给仪器检测的图像的一门学科。作为一门科学学科,计算机视觉研究相关的理论和技术,试图建立能够从图像或者多维数据中获取“信息”的人工智能系统。因为感知可以看做是从感官信号中提取信息,所以计算机视觉也可以看做是研究如何使人工系统从图像或多维数据中“感知”的科学。

    图像处理和计算机视觉的区别在于:图像处理侧重于“处理”图像——如增强,还原,去噪,分割,等等;而计算机视觉重点在于使用计算机(也许是可移动式的)来模拟人的视觉,因此模拟才是计算机视觉领域的最终目标。

    而OpenCV(Open Source Computer Vision Library),是一个基于开源发行的跨平台计算机视觉库,它实现了图像处理和计算机视觉方面的很多通用算法,已经成为了计算机视觉领域最有力的研究工具之一。

    OpenCV的初衷:

    为基本的视觉应用提供开放且优化的源代码,以促进视觉研究的发展,从而有效地避免“闭门造车”。通过提供一个通用的架构来传播视觉知识,开发者可以在这个架构上继续开展工作,所以代码应该是非常易读且可改写的。OpenCV库采用的协议不要求商业产品继续开放代码,这使得可移植的、性能被优化的代码可以自由获取,可以促进基于视觉的商业应用的发展。

    模块

    【calib3d】——Calibration(校准)和3D这两个词的组合缩写。这个模块主要是相机校准和三维重建相关的内容,包括基本的多视角几何算法、单个立体摄像头标定、物体姿态估计、立体相似性算法、3D信息的重建等。

    【contrib】——Contributed/Experimental Stuf的缩写。该模块包含了一些最近添加的不太稳定的可选功能,不用去多管。新增了新型人脸识别、立体匹配、人工视网膜模型等技术。

    【core】——核心功能模块,包含如下内容:

    OpenCV基本数据结构动态数据结构绘图函数数组操作相关函数辅助功能与系统函数和宏与OpenGL的互操作

    【imgproc】——Image和Process这两个单词的缩写组合,图像处理模块。包含如下内容:

    线性和非线性的图像滤波图像的几何变换其他(Miscellaneous)图像转换直方图相关结构分析和形状描述运动分析和对象跟踪特征检测目标检测等内容

    【features2d】——也就是Features2D,即2D功能框架,包含如下内容:

    特征检测和描述特征检测器(Feature Detectors)通用接口描述符提取器(Descriptor Extractors)通用接口描述符匹配器(Descriptor Matchers)通用接口通用描述符(Generic Descriptor)匹配器通用接口关键点绘制函数和匹配功能绘制函数

    【flann】——Fast Library for Approximate Nearest Neighbors,高维的近似近邻快速搜索算法库,包含以下两个部分:

    快速近似最近邻搜索聚类

    【gpu】——运用GPU加速的计算机视觉模块。

    【highgui】——高层GUI图形用户界面,包含媒体的输入输出、视频捕捉、图像和视频的编码解码、图形交互界面的接口等内容。

    【legacy】——一些已经废弃的代码库,保留下来作为向下兼容,包含如下内容:

    运动分析期望最大化直方图平面细分(C API)特征检测和描述(Feature Detection and Description)描述符提取器(Descriptor Extractors)的通用接口通用描述符(Generic Descriptor Matchers)的常用接口匹配器

    【ml】——Machine Learning,机器学习模块,基本上是统计模型和分类算法,包含如下内容:

    统计模型(Statistical Models)一般贝叶斯分类器(Normal Bayes Classifier)K-近邻(K-Nearest Neighbors)支持向量机(Support Vector Machines)决策树(Decision Trees)提升(Boosting)梯度提高树(Gradient Boosted Trees)随机树(Random Trees)超随机树(Extremely randomized trees)期望最大化(Expectation Maximization)神经网络(Neural Networks)MLData

    【nonfree】——一些具有专利的算法模块,包含特征检测和GPU相关的内容。最好不要商用。

    【objdetect】——目标检测模块,包含Cascade Classification(级联分类)和Latent SVM这两个部分。

    【ocl】——OpenCL-accelerated Computer Vision,运用OpenCL加速的计算机视觉组件模块。

    【photo】——Computational Photography,包含图像修复和图像去噪两部分

    【stitching】——images stitching,图像拼接模块,包含如下部分:

    拼接流水线特点寻找和匹配图像估计旋转自动校准图片歪斜接缝估测曝光补偿图片混合

    【superres】——SuperResolution,超分辨率技术的相关功能模块。

    【ts】——OpenCV测试相关代码,不用去管。

    【video】——视频分析组件,该模块包括运动估计、背景分离、对象跟踪等视频处理相关内容。

    【Videostab】——Video stabilization,视频稳定相关的组件,官方文档中没有多做介绍,不用管它。

    正式版opencv与opencv_contrib之间的区别: 正式版opencv是稳定版本,opencv_contrib是新增但不稳定版本。

    图片基本操作

    显示图片

    #include <opencv2/opencv.hpp> //头文件 using namespace cv; //包含cv命名空间 void main( ) { // 【1】读入一张图片,载入图像 Mat srcImage = imread("1.jpg"); // 【2】显示载入的图片 imshow("【原始图】",srcImage); // 【3】等待任意按键按下,正数为倒数时间,0和负数为无限 waitKey(0); }

    图像腐蚀

    腐蚀,即用图像中的暗色部分“腐蚀”掉图像中的亮色部分。

    #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> using namespace cv; int main() { //载入原图 Mat srcImage = imread("C:/Users/Administrator/Desktop/pic.jpg"); //显示原图 imshow("【原图】腐蚀操作", srcImage); //进行腐蚀操作 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); Mat dstImage; erode(srcImage, dstImage, element); //显示效果图 imshow("【效果图】腐蚀操作", dstImage); waitKey(0); return 0; }

    其中, getStructuringElement()函数值为指定形状和尺寸的结构元素(内核矩阵)。

    图像模糊

    主要使用进行均值滤波操作的blur函数。

    #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" using namespace cv; int main() { //【1】载入原始图 Mat srcImage = imread("C:/Users/Administrator/Desktop/pic.jpg"); //【2】显示原始图 imshow("均值滤波【原图】", srcImage); //【3】进行均值滤波操作 Mat dstImage; blur(srcImage, dstImage, Size(7, 7)); //【4】显示效果图 imshow("均值滤波【效果图】", dstImage); waitKey(0); }

    canny边缘检测

    #include <opencv2/opencv.hpp> #include<opencv2/imgproc/imgproc.hpp> using namespace cv; int main() { //【0】载入原始图 Mat srcImage = imread("C:/Users/Administrator/Desktop/pic.jpg"); imshow("【原始图】Canny边缘检测", srcImage); //显示原始图 Mat dstImage, edge, grayImage; //参数定义 //【1】创建与src同类型和大小的矩阵(dst) dstImage.create(srcImage.size(), srcImage.type()); //【2】将原图像转换为灰度图像 //此句代码的OpenCV2版为: //cvtColor( srcImage, grayImage, CV_BGR2GRAY ); //此句代码的OpenCV3版为: cvtColor(srcImage, grayImage, COLOR_BGR2GRAY); //【3】先用使用 3x3内核来降噪 blur(grayImage, edge, Size(3, 3)); //【4】运行Canny算子 Canny(edge, edge, 3, 9, 3); //【5】显示效果图 imshow("【效果图】Canny边缘检测", edge); waitKey(0); return 0; }

    读取并播放视频

    VedioCapture类。

    #include <opencv2\opencv.hpp> using namespace cv; int main() { //【1】读入视频 VideoCapture capture("C:/Users/Administrator/Desktop/1.avi"); //【2】循环显示每一帧 while (1) { Mat frame;//定义一个Mat变量,用于存储每一帧的图像 capture >> frame; //读取当前帧 imshow("读取视频", frame); //显示当前帧 waitKey(30); //延时30ms } return 0; }

    调用摄像头采集图像

    #include <opencv2\opencv.hpp> using namespace cv; int main() { //【1】从摄像头读入视频 VideoCapture capture(0); //【2】循环显示每一帧 while (1) { Mat frame; //定义一个Mat变量,用于存储每一帧的图像 capture >> frame; //读取当前帧 imshow("读取视频", frame); //显示当前帧 waitKey(30); //延时30ms } return 0; }

    HighGUI图形界面用户

    图像的载入、显示与输出

    Mat 类:图像类型,是用于保存图像以及其他矩阵数据的数据结构; imread():读取图像,返回Mat类,第一个参数为图像名,第二个参数为色彩类型(默认为彩色); imshow():用于在指定窗口显示一幅图像,第一个参数是窗口名,第二个参数是要显示的图像(类型为Mat ); namedWindow():给窗口命名; imwrite():输出(保存)图片,第一个参数为要命名的图片名称,第二个参数为图片名(InputArray类),第三个参数为要保存的格式(一般不写)。

    #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> using namespace cv; int main() { Mat girl = imread("C:/Users/Administrator/Desktop/girl.jpg"); //载入图像到Mat namedWindow("【1】动漫图"); //创建一个名为 "【1】动漫图"的窗口 imshow("【1】动漫图", girl);//显示名为 "【1】动漫图"的窗口 //-----------------------------------【二、初级图像混合】-------------------------------------- // 描述:二、初级图像混合 //-------------------------------------------------------------------------------------------------- //载入图片 Mat image = imread("C:/Users/Administrator/Desktop/dota.jpg", 199); Mat logo = imread("C:/Users/Administrator/Desktop/dota_logo.jpg"); //载入后先显示 namedWindow("【2】原画图"); imshow("【2】原画图", image); namedWindow("【3】logo图"); imshow("【3】logo图", logo); 定义一个Mat类型,用于存放,图像的ROI //Mat imageROI; 方法一 imageROI = image(Rect(800, 350, logo.cols, logo.rows)); 方法二 //imageROI= image(Range(350,350+logo.rows),Range(800,800+logo.cols)); 将logo加到原图上 //addWeighted(imageROI, 0.5, logo, 0.3, 0., imageROI); 显示结果 //namedWindow("【4】原画+logo图"); //imshow("【4】原画+logo图", image); //-----------------------------------【三、图像的输出】-------------------------------------- // 描述:将一个Mat图像输出到图像文件 //----------------------------------------------------------------------------------------------- //输出一张jpg图片到工程目录下 imwrite("C:/Users/Administrator/Desktop/由imwrite生成的图片.jpg", image); waitKey(); return 0; }

    滑动条的创建和使用

    #include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui.hpp> using namespace cv; #define WINDOW_NAME "【滑动条的创建&线性混合示例】" //为窗口标题定义的宏 const int g_nMaxAlphaValue = 100;//Alpha值的最大值 int g_nAlphaValueSlider;//滑动条对应的变量 double g_dAlphaValue; double g_dBetaValue; //声明存储图像的变量 Mat g_srcImage1; Mat g_srcImage2; Mat g_dstImage; //-----------------------------------【on_Trackbar( )函数】-------------------------------- // 描述:响应滑动条的回调函数 //------------------------------------------------------------------------------------------ void on_Trackbar(int, void*) { //求出当前alpha值相对于最大值的比例 g_dAlphaValue = (double)g_nAlphaValueSlider / g_nMaxAlphaValue; //则beta值为1减去alpha值 g_dBetaValue = (1.0 - g_dAlphaValue); //根据alpha和beta值进行线性混合 addWeighted(g_srcImage1, g_dAlphaValue, g_srcImage2, g_dBetaValue, 0.0, g_dstImage); //显示效果图 imshow(WINDOW_NAME, g_dstImage); } //-----------------------------【ShowHelpText( )函数】-------------------------------------- // 描述:输出帮助信息 //------------------------------------------------------------------------------------------------- //-----------------------------------【ShowHelpText( )函数】---------------------------------- // 描述:输出一些帮助信息 //---------------------------------------------------------------------------------------------- void ShowHelpText() { //输出欢迎信息和OpenCV版本 printf("\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n"); printf("\n\n\t\t\t此为本书OpenCV3版的第17个配套示例程序\n"); printf("\n\n\t\t\t 当前使用的OpenCV版本为:" CV_VERSION); printf("\n\n ----------------------------------------------------------------------------\n"); } //--------------------------------------【main( )函数】----------------------------------------- // 描述:控制台应用程序的入口函数,我们的程序从这里开始执行 //----------------------------------------------------------------------------------------------- int main(int argc, char** argv) { //显示帮助信息 ShowHelpText(); //加载图像 (两图像的尺寸需相同) g_srcImage1 = imread("C:/Users/Administrator/Desktop/1.jpg"); g_srcImage2 = imread("C:/Users/Administrator/Desktop/2.jpg"); if (!g_srcImage1.data) { printf("读取第一幅图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return -1; } if (!g_srcImage2.data) { printf("读取第二幅图片错误,请确定目录下是否有imread函数指定图片存在~!\n"); return -1; } //设置滑动条初值为70 g_nAlphaValueSlider = 70; //创建窗体 namedWindow(WINDOW_NAME, 1); //在创建的窗体中创建一个滑动条控件 char TrackbarName[50]; printf(TrackbarName, "透明值 %d", g_nMaxAlphaValue); createTrackbar(TrackbarName, WINDOW_NAME, &g_nAlphaValueSlider, g_nMaxAlphaValue, on_Trackbar); //结果在回调函数中显示 on_Trackbar(g_nAlphaValueSlider, 0); //按任意键退出 waitKey(0); return 0; }

    createTrackbar这个函数我们以后会经常用到,它创建一个可以调整数值的轨迹条,并将轨迹条附加到指定的窗口上,使用起来很方便。首先大家要记住,它往往会和一个回调函数配合起来使用。先看下他的函数原型:

    C++: int createTrackbar(conststring& trackbarname, conststring& winname, int* value, int count, TrackbarCallback onChange=0,void* userdata=0);

    第一个参数,const string&类型的trackbarname,表示轨迹条的名字,用来代表我们创建的轨迹条。 第二个参数,const string&类型的winname,填窗口的名字,表示这个轨迹条会依附到哪个窗口上,即对应namedWindow()创建窗口时填的某一个窗口名。 第三个参数,int* 类型的value,一个指向整型的指针,表示滑块的位置。并且在创建时,滑块的初始位置就是该变量当前的值。 第四个参数,int类型的count,表示滑块可以达到的最大位置的值。PS:滑块最小的位置的值始终为0。 第五个参数,TrackbarCallback类型的onChange,首先注意他有默认值0。这是一个指向回调函数的指针,每次滑块位置改变时,这个函数都会进行回调。并且这个函数的原型必须为void XXXX(int,void*);其中第一个参数是轨迹条的位置,第二个参数是用户数据(看下面的第六个参数)。如果回调是NULL指针,表示没有回调函数的调用,仅第三个参数value有变化。 第六个参数,void*类型的userdata,他也有默认值0。这个参数是用户传给回调函数的数据,用来处理轨迹条事件。如果使用的第三个参数value实参是全局变量的话,完全可以不去管这个userdata参数。

    getTrackbarPos函数, 用于获取当前轨迹条的位置并返回。

    C++: int getTrackbarPos(conststring& trackbarname, conststring& winname);

    鼠标操作

    #include <opencv2/opencv.hpp> using namespace cv; #define WINDOW_NAME "【程序窗口】" //为窗口标题定义的宏 void on_MouseHandle(int event, int x, int y, int flags, void* param); void DrawRectangle(cv::Mat& img, cv::Rect box); void ShowHelpText(); //-----------------------------------【全局变量声明部分】----------------------------------- // 描述:全局变量的声明 //----------------------------------------------------------------------------------------------- Rect g_rectangle; bool g_bDrawingBox = false;//是否进行绘制 RNG g_rng(12345); int main(int argc, char** argv) { //【0】改变console字体颜色 system("color 9F"); //【0】显示欢迎和帮助文字 ShowHelpText(); //【1】准备参数 g_rectangle = Rect(-1, -1, 0, 0); Mat srcImage(600, 800, CV_8UC3), tempImage; srcImage.copyTo(tempImage); g_rectangle = Rect(-1, -1, 0, 0); srcImage = Scalar::all(0); //【2】设置鼠标操作回调函数 namedWindow(WINDOW_NAME); setMouseCallback(WINDOW_NAME, on_MouseHandle, (void*)&srcImage); //【3】程序主循环,当进行绘制的标识符为真时,进行绘制 while (1) { srcImage.copyTo(tempImage);//拷贝源图到临时变量 if (g_bDrawingBox) DrawRectangle(tempImage, g_rectangle);//当进行绘制的标识符为真,则进行绘制 imshow(WINDOW_NAME, tempImage); if (waitKey(10) == 27) break;//按下ESC键,程序退出 } return 0; } //--------------------------------【on_MouseHandle( )函数】----------------------------- // 描述:鼠标回调函数,根据不同的鼠标事件进行不同的操作 //----------------------------------------------------------------------------------------------- void on_MouseHandle(int event, int x, int y, int flags, void* param) { Mat& image = *(cv::Mat*) param; switch (event) { //鼠标移动消息 case EVENT_MOUSEMOVE: { if (g_bDrawingBox)//如果是否进行绘制的标识符为真,则记录下长和宽到RECT型变量中 { g_rectangle.width = x - g_rectangle.x; g_rectangle.height = y - g_rectangle.y; } } break; //左键按下消息 case EVENT_LBUTTONDOWN: { g_bDrawingBox = true; g_rectangle = Rect(x, y, 0, 0);//记录起始点 } break; //左键抬起消息 case EVENT_LBUTTONUP: { g_bDrawingBox = false;//置标识符为false //对宽和高小于0的处理 if (g_rectangle.width < 0) { g_rectangle.x += g_rectangle.width; g_rectangle.width *= -1; } if (g_rectangle.height < 0) { g_rectangle.y += g_rectangle.height; g_rectangle.height *= -1; } //调用函数进行绘制 DrawRectangle(image, g_rectangle); } break; } } //-----------------------------------【DrawRectangle( )函数】------------------------------ // 描述:自定义的矩形绘制函数 //----------------------------------------------------------------------------------------------- void DrawRectangle(cv::Mat& img, cv::Rect box) { cv::rectangle(img, box.tl(), box.br(), cv::Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255)));//随机颜色 } //-----------------------------------【ShowHelpText( )函数】----------------------------- // 描述:输出一些帮助信息 //---------------------------------------------------------------------------------------------- void ShowHelpText() { //输出欢迎信息和OpenCV版本 printf("\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n"); printf("\n\n\t\t\t此为本书OpenCV3版的第18个配套示例程序\n"); printf("\n\n\t\t\t 当前使用的OpenCV版本为:" CV_VERSION); printf("\n\n ----------------------------------------------------------------------------\n"); //输出一些帮助信息 printf("\n\n\n\t欢迎来到【鼠标交互演示】示例程序\n"); printf("\n\n\t请在窗口中点击鼠标左键并拖动以绘制矩形\n"); }

    core组件基础

    Mat数据解构

    Mat类由两个数据部分组成:矩阵头(包含矩阵尺寸、存储方法、存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同,矩阵可以是不同的维数)的指针。

    Mat类使用引用计数功能,复制时只复制数据头,而共用同一个矩阵数据,当最后一个引用成员销毁时,引用计数为0,数据矩阵被销毁。

    Mat A, C; // 仅创建信息头部分 A = imread(1.jpg”, CV_LOAD_IMAGE_COLOR); // 这里为矩阵开辟内存 Mat B(A); //使用拷贝构造函数 C = A; //赋值运算符

    但某些时候你仍会想复制矩阵本身(不只是信息头和矩阵指针),这时可以使用函数clone( )或者copyTo( )。

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

    Mat不但是一个非常有用的图像容器类,同时也是一个通用的矩阵类,我们也可以用它来创建和操作多维矩阵。

    像素值的存储方法

    颜色系统有很多,它们各有优势,具体如下。

    RGB是最常见的,这是因为人眼采用相似的工作机制,它也被显示设备所采用HSV和HLS把颜色分解成色调、饱和度和亮度/明度。这是描述颜色更自然的方式,比如可以通过抛弃最后一个元素,使算法对输入图像的光照条件不敏感YCrCb在JPEG图像格式中广泛使用CIE L* a* b* 是一种在感知上均匀的颜色空间,它适合用来度量两个颜色之间的距离

    常用数据结构和函数

    Point点类

    Point类数据结构表示了二维坐标系下的点,即由其图像坐标x和y指定的2D点。

    Point point; point.x = 10; point.y = 8; Point point = Point(10, 8);

    另外,在OpenCV中有如下定义:

    typedef Point_<int> Point2i; typedef Point2i Point; typedef Point_<float> Point2f;

    所以,Point_<int>、Point2i、Point互相等价,Point_<float>、Point2f互相等价。

    Point类构造函数:

    //包含OpenCV的头文件 //参照github https://github.com/yoyoyo-yo/Gasyori100knock #include <opencv2/opencv.hpp> #include <iostream> using namespace std; //使用OpenCV的命名空间 using namespace cv; //Point 类型操作 类型 int main() { //1.默认构造函数 Point2i p1;//数据为[0,0] Point3f p2;//数据为[0,0,0] cout << p1 << '\n' << p2 << endl; //2.赋值构造函数 Point3f p3(1, 2,3);//数据为[1,2,3] Point3f p4(p3);//数据为[1,2,3] cout << p3 << "\n" << p4 << endl; //3,带参数的构造函数 Point2i p5(1, 2);//数据为[1,2] Point3f p6(1.1, 1.2, 1.3);//数据为[1.1,1.2,1.3] cout << p5 << '\n' << p6 << endl; //4,隐式类型转换,转换为Vec3f Vec3f v = p6;//数据为[1.1,1.2,1.3] cout << v << endl; //5,成员函数访问 cout << p5.x << '\t' << p5.y << endl; cout << p6.x << '\t' << p6.y << '\t' << p6.z << endl; //6.点乘 --可以用来计算两个向量之间的夹角 Point3f p7(2.0f, 3.0f, 4.0f); float value = p6.dot(p7); cout << value << endl; //7.十字相乘 仅仅适用与三维点影像 //结果返回两个向量的垂直向量 Point3f p8(1, 0, 0);//x方向的单位向量 Point3f p9(0, 1, 0);//Y方向的单位向量 Point3f p10 = p8.cross(p9);//计算出来的Z方向的单位向量 cout << p10 << endl; //8.判断点是否在矩阵内 仅仅适用与二维点 Rect2f r(0,0,1,1);//注意,这个构造函数是(x,y,width,height)X坐标系向右为正,Y坐标系向上为正 Point2f p(0.5, 0.5); bool bValue = p.inside(r);//返回为true p.x = 2; bValue = p.inside(r);//返回为false return 0; }

    Scalar颜色类

    Scalar( )表示具有4个元素的数组,在OpenCV中被大量用于传递像素值,如RGB颜色值。而RGB颜色值为三个参数,其实对于Scalar函数来说,如果用不到第四个参数,则不需要写出来;若只写三个参数,OpenCV会认为我们就想表示三个参数。

    Scalar(a, b, c);

    那么定义的RGB颜色值:红色分量为c,绿色分量为b,蓝色分量为a。

    Scalar类的源头为Scalar_类,而Scalar_类是Vec4x的一个变种,我们常用的Scalar其实就是Scalar_<double>。这就解释了为什么很多函数的参数输入可以是Mat,也可以是Scalar。

    Size尺寸类

    原型:

    typedef Size_<int> Size2i; typedef Size2i Size;

    所以,Size_<int>、Size2i、Size这三个类型名等价。

    Size模版类中有很多重载函数,其中我们使用频率最高的是下面这个构造函数:

    Size_(_Tp _width, _Tp _height);

    Rect矩形类

    Rect类的成员变量有x、y、width、height,分别为左上角点的坐标和矩形的宽和高。常用的成员函数有:Size( )返回值为Size;area( )返回矩形的面积;contains(Point)判断点是否在矩形内;inside(Rect)函数判断矩形是否在该矩形内;tl( )返回左上角点坐标;br( )返回右下角点坐标。值得注意的是,如果想求两个矩形的交集和并集,可以用如下格式:

    Rect rect = rect1 & rect2; Rect rect = rect1 | rect2;

    如果想让矩形进行平移操作和缩放操作,甚至可以这样写:

    Rect rectShift = rect + point; Rect rectScale = rect + size;

    cvtColor()颜色空间转换

    cvtColor( )函数是OpenCV里的颜色空间转换函数,可以实现RGB颜色向HSV、HSI等颜色空间的转换,也可以转换为灰度图像。 函数原型:

    void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0);

    第一个参数为输入图像,第二个参数为输出图像,第三个参数为颜色空间转换的标识符,第四个参数为目标图像的通道数,若该参数是0,表示目标图像取源图像的通道数。下面是一个调用示例:

    //此句代码的OpenCV2版为: cvtColor(srcImage,dstImage, CV_GRAY2BGR);//转换原始图为灰度图 //此句代码的OpenCV3版为: cvtColor(srcImage,dstImage, COLOR_GRAY2BGR);//转换原始图为灰度图

    例子:

    #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" using namespace cv; int main( ) { //【1】载入图片 Mat srcImage=imread("1.jpg",1); Mat dstImage; //【2】转换颜色空间 cvtColor(srcImage,dstImage, COLOR_BGR2Lab); //【3】显示效果图 imshow("效果图",dstImage); //【4】保持窗口显示 waitKey( ); return 0; }

    其他常用的知识点

    Matx是个轻量级的Mat,必须在使用前规定好大小,比如一个2* 3的float型的Matx,可以声明为Matx23f。

    Vec是Matx的一个派生类,是一个一维的Matx,跟vector很相似。在OpenCV源码中有如下定义。

    template<typename_Tp, int n> class Vec : public Matx<_Tp, n, 1> {...}; typedef Vec<uchar, 2> Vec2b;

    Range类其实就是为了使OpenCV的使用更像MATLAB而产生的。比如Range::all( )其实就是MATLAB里的符号。而Range(a, b)其实就是MATLAB中的a:b,注意这里的a和b都应为整型。

    OpenCV中防止内存溢出的函数有alignPtr、alignSize、allocate、deallocate、fastMalloc、fastFree等。

    <math.h>里的一些函数使用起来很方便,有计算向量角度的函数fastAtan2、计算立方根的函数cubeRoot、向上取整函数cvCeil、向下取整函数cvFloor、四舍五入函数cvRound等。还有一些类似MATLAB里面的函数,比如cvIsInf判断自变量是否无穷大,cvIsNaN判断自变量是否不是一个数。

    显示文字相关的函数有getTextSize、cvInitFont、putText。

    作图相关的函数有circle、clipLine、ellipse、ellipse2Poly、line、rectangle、polylines、类LineIterator。

    填充相关的函数有fillConvexPoly、fillPoly。

    OpenCV中RNG( )函数的作用为初始化随机数状态的生成器。

    基本图形的绘制

    DrawEllipse()

    画椭圆。

    #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" using namespace cv; #define WINDOW_WIDTH 600//定义窗口大小的宏 //------------------------【DrawEllipse( )函数】------------------------ // 描述:自定义的绘制函数,实现了绘制不同角度、相同尺寸的椭圆 //------------------------------------------------------------------ void DrawEllipse( Mat img, double angle ) { int thickness = 2; int lineType = 8; ellipse( img, Point( WINDOW_WIDTH/2, WINDOW_WIDTH/2 ), Size( WINDOW_WIDTH/4, WINDOW_WIDTH/16 ), angle, 0, 360, Scalar( 255, 129, 0 ), thickness, lineType ); } int main( ) { Mat dstImage=imread("/Users/cdq/Desktop/test.jpg"); DrawEllipse(dstImage,0); imshow("",dstImage); waitKey( ); return 0; }

    DrawFilledCircle()

    画实心圆。

    //---------------------【DrawFilledCircle( )函数】--------------------- // 描述:自定义的绘制函数,实现了实心圆的绘制 //------------------------------------------------------------------ void DrawFilledCircle( Mat img, Point center ) { int thickness = -1; int lineType = 8; circle( img, center, WINDOW_WIDTH/32, Scalar( 0, 0, 255 ), thickness, lineType ); }

    函数DrawFilledCircle( )调用了OpenCV中的circle函数,将圆画到图像img上,圆心由点center定义,圆的半径为WINDOW_WIDTH/32,圆的颜色为Scalar(0,0,255),按BGR的格式为红色,线粗定义为thickness = -1,因此绘制的圆是实心的。

    DrawPolygon()

    void DrawPolygon( Mat img ) { Mat img(500, 500, CV_8U, Scalar(0)); Point root_points[1][4]; root_points[0][0] = Point(215,220); root_points[0][1] = Point(460,225); root_points[0][2] = Point(466,450); root_points[0][3] = Point(235,465); const Point* ppt[1] = {root_points[0]}; int npt[] = {4}; polylines(img, ppt, npt, 1, 1, Scalar(255),1,8,0); imshow("Test", img); waitKey(); fillPoly(img, ppt, npt, 1, Scalar(255)); imshow("Test", img); waitKey(); }

    函数DrawPolygon( )调用了OpenCV中的fillPoly函数,用于将多边形画到图像img上,其中多边形的顶点集为ppt,要绘制的多边形顶点数目为npt,要绘制的多边形数量仅为1,多边形的颜色定义为白色Scalar(255,255,255)。

    DrawLine()

    //-------------------------【DrawLine( )函数】------------------------- // 描述:自定义的绘制函数,实现了线的绘制 //------------------------------------------------------------------ void DrawLine( Mat img, Point start, Point end ) { int thickness = 2; int lineType = 8; line( img, start, end, Scalar( 0, 0, 0 ), thickness, lineType ); }

    DrawLin( )函数调用了OpenCV中的line函数,用于在图像img上画一条从点start到点end的直线段,线的颜色为Scalar(0,0,0)代表的黑色,线的粗细thickness为2,且此线为8联通(lineType = 8)。

    core组件进阶

    访问图像中的像素

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

    图像矩阵的大小取决于所用的颜色模型,确切地说,取决于所用通道数。 例如,灰度模型只有一个通道;RGB模型有R,G,B三个通道。

    OpenCV中子列的通道顺序是反过来的——BGR而不是RGB。很多情况下,因为内存足够大,可实现连续存储,因此,图像中的各行就能一行一行地连接起来,形成一个长行。连续存储有助于提升图像扫描速度,我们可以使用isContinuous( )来判断矩阵是否是连续存储的。

    颜色空间缩减

    我们知道,若矩阵元素存储的是单通道像素,使用C或C++的无符号字符类型,那么像素可有256个不同值。但若是三通道图像,这种存储格式的颜色数就太多了(确切地说,有一千六百多万种)。用如此之多的颜色来进行处理,可能会对我们的算法性能造成严重影响。

    其实,仅用这些颜色中具有代表性的很小的部分,就足以达到同样的效果。

    如此,颜色空间缩减(color space reduction)便可以派上用场了,它在很多应用中可以大大降低运算复杂度。颜色空间缩减的做法是:将现有颜色空间值除以某个输入值,以获得较少的颜色数。也就是“做减法”,比如颜色值0到9可取为新值0,10到19可取为10,以此类推。

    如uchar类型的三通道图像,每个通道取值可以是0~255,于是就有256×256个不同的值。我们可以定义:

    0~9范围的像素值为0;

    10~19范围的像素值为10;

    20~29范围的像素值为20。

    这样的操作将颜色取值降低为26×26×26种情况。这个操作可以用一个简单的公式来实现。因为C++中int类型除法操作会自动截余。例如:

    Iold=14;Inew=(Iold/10)* 10=(14/10)* 10=1* 10=10;

    uchar(无符号字符,即0到255之间取值的数)类型的值除以int值,结果仍是char。因为结果是char类型的,所以求出来小数也要向下取整。利用这一点,刚才提到在uchar定义域中进行的颜色缩减运算就可以表达为下面的形式:

    因为C++中int类型除法操作会自动截余。比如:

    Iold=14; Inew=(Iold/10)*10=(14/10)*10=1*10=10;

    在处理图像像素时,每个像素需要进行一遍上述计算,也需要一定的时间花销。但我们注意到其实只有0~255种像素,即只有256种情况。进一步可以把256种计算好的结果提前存在表中table中,这样每种情况不需计算,直接从table中取结果即可。 批注:如何评判计算和取值的时间。

    int divideWith=10; uchar table[256]; for (int i = 0; i < 256; ++i) table[i] = divideWith*(i/divideWith);

    于是table[i]存放的是值为i的像素减小颜色空间的结果,这样也就可以理解上述方法中的操作:

    p[j] = table[p[j]];

    这样,简单的颜色空间缩减算法就可由下面两步组成: (1)遍历图像矩阵的每一个像素;

    (2)对像素应用上述公式。

    值得注意的是,我们这里用到了除法和乘法运算,而这两种运算又特别费时,所以,应尽可能用代价较低的加、减、赋值等运算来替换它们。此外,还应注意到,上述运算的输入仅能在某个有限范围内取值,如uchar类型可取256个值。

    由此可知,对于较大的图像,有效的方法是预先计算所有可能的值,然后需要这些值的时候,利用查找表直接赋值即可。查找表是一维或多维数组,存储了不同输入值所对应的输出值,其优势在于只需读取、无须计算。 批注:读取速度比计算快?

    LUT函数:Look up table操作

    对于上文提到的Look up table操作,OpenCV官方文档中强烈推荐我们使用一个原型为operationsOnArrays:LUT( )<lut>的函数来进行。它用于批量进行图像元素查找、扫描与操作图像。其使用方法如下:

    //首先我们建立一个mat型用于查表 Mat lookUpTable(1, 256, CV_8U); uchar* p = lookUpTable.data; for( int i = 0; i < 256; ++i) p[i] = table[i]; //然后我们调用函数 (I 是输入 J 是输出): for (int i = 0; i < times; ++i) LUT(I, lookUpTable, J);

    计时函数

    可以利用这两个简便的计时函数——getTickCount()和getTickFrequency()。

    getTickCount( )函数返回CPU自某个事件(如启动电脑)以来走过的时钟周期数。

    getTickFrequency( )函数返回CPU一秒钟所走的时钟周期数。这样,我们就能轻松地以秒为单位对某运算计时。

    double time0 = static_cast<double>(getTickCount( ));//记录起始时间 // 进行图像处理操作…… time0 = ((double)getTickCount( ) – time0)/getTickFrequency( ); cout<<"此方法运行时间为:"<<time0<<"秒"<<endl; //输出运行时间

    访问图像中像素的三类方法

    方法一 指针访问:C操作符[ ];方法二 迭代器iterator;方法三 动态地址计算。

    这三种方法在访问速度上略有差异。debug模式下,这种差异非常明显,不过在release模式下,这种差异就不太明显了。我们通过一组程序来说明这几种方法。程序的目的是减少图像中颜色的数量,比如原来的图像是是256种颜色,我们希望将它变成64种颜色,那只需要将原来的颜色除以4(整除)以后再乘以4就可以了。

    //----------------------【头文件、命名空间包含部分】---------------------- // 描述:包含程序所使用的头文件和命名空间 //------------------------------------------------------------------ #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> using namespace std; using namespace cv; //-------------------------【全局函数声明部分】------------------------- // 描述:全局函数声明 //------------------------------------------------------------------ void colorReduce1(Mat& inputImage, Mat& outputImage, int div); void colorReduce2(Mat& inputImage, Mat& outputImage, int div); void colorReduce3(Mat& inputImage, Mat& outputImage, int div); //---------------------------【main( )函数】--------------------------- // 描述:控制台应用程序的入口函数,我们的程序从这里开始执行 //------------------------------------------------------------------ int main( ) { //【1】创建原始图并显示 Mat srcImage = imread("/Users/cdq/Desktop/test.jpg"); imshow("原始图像",srcImage); //【2】按原始图的参数规格来创建创建效果图 Mat dstImage; dstImage.create(srcImage.rows,srcImage.cols,srcImage.type( ));//效果图的大小、类型与原图片相同 //【3】记录起始时间 double time0 = static_cast<double>(getTickCount( )); //【4】调用颜色空间缩减函数 // colorReduce1(srcImage,dstImage,32); // colorReduce2(srcImage,dstImage,32); colorReduce3(srcImage,dstImage,32); //【5】计算运行时间并输出 time0 = ((double)getTickCount( ) - time0)/getTickFrequency( ); cout<<"此方法运行时间为:"<<time0<<"秒"<<endl; //输出运行时间 //【6】显示效果图 imshow("效果图",dstImage); waitKey(0); } //------------------------【colorReduce( )函数】------------------------ // 描述:使用【指针访问:C操作符[ ]】方法版的颜色空间缩减函数 //------------------------------------------------------------------ void colorReduce1(Mat& inputImage, Mat& outputImage, int div) { //参数准备 outputImage = inputImage.clone( ); //复制实参到临时变量 int rowNumber = outputImage.rows; //行数 int colNumber = outputImage.cols*outputImage.channels( ); //列数×通道数=每一行元素的个数//双重循环,遍历所有的像素值 for(int i = 0;i < rowNumber;i++) //行循环 { uchar* data = outputImage.ptr<uchar>(i); //获取第i行的首地址 for(int j = 0;j < colNumber;j++) //列循环 { // ---------【开始处理每个像素】------------- data[j] = data[j]/div*div + div/2; // ----------【处理结束】--------------------- } //行处理结束 } } //------------------------【colorReduce( )函数】------------------------ // 描述:使用【迭代器】方法版本的颜色空间缩减函数 //------------------------------------------------------------------ void colorReduce2(Mat& inputImage, Mat& outputImage, int div) { //参数准备 outputImage = inputImage.clone( ); //复制实参到临时变量 //获取迭代器 Mat_<Vec3b>::iterator it = outputImage.begin<Vec3b>( ); //初始位置的迭代器 Mat_<Vec3b>::iterator itend = outputImage.end<Vec3b>( ); //终止位置的迭代器 //存取彩色图像像素 for(;it != itend;++it) { // ---------------------【开始处理每个像素】--------------------- (*it)[0] = (*it)[0]/div*div + div/2; (*it)[1] = (*it)[1]/div*div + div/2; (*it)[2] = (*it)[2]/div*div + div/2; // ------------------------【处理结束】------------------------ } } //------------------------【colorReduce( )函数】------------------------ // 描述:使用【动态地址运算配合at】方法版本的颜色空间缩减函数 //------------------------------------------------------------------ void colorReduce3(Mat& inputImage, Mat& outputImage, int div) { //参数准备 outputImage = inputImage.clone( ); //复制实参到临时变量 int rowNumber = outputImage.rows; //行数 int colNumber = outputImage.cols; //列数 //存取彩色图像像素 for(int i = 0;i < rowNumber;i++) { for(int j = 0;j < colNumber;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; //红色通道 // ------------------------【处理结束】------------------------ } // 行处理结束 } } 此方法运行时间为:0.00327002秒 此方法运行时间为:0.00733847秒 此方法运行时间为:0.00644152秒

    ROI区域图像叠加&图像混合

    感兴趣区域:ROI

    在图像处理领域,我们常常需要设置感兴趣区域(ROI,region of interest),来专注或者简化工作过程。也就是从图像中选择的一个图像区域,这个区域是图像分析所关注的重点。我们圈定这个区域,以便进行进一步处理。而且,使用ROI指定想读入的目标,可以减少处理时间,增加精度,给图像处理来带不小的便利。

    定义ROI区域有两种方法:第一种是使用表示矩形区域的Rect。它指定矩形的左上角坐标(构造函数的前两个参数)和矩形的长宽(构造函数的后两个参数)以定义一个矩形区域。

    其中,image为已经载入好的图片。

    //定义一个Mat类型并给其设定ROI区域 Mat imageROI; //方法一 imageROI=image(Rect(500,250,logo.cols,logo.rows));

    另一种定义ROI的方式是指定感兴趣行或列的范围(Range)。Range是指从起始索引到终止索引(不包括终止索引)的一连段连续序列。cRange可以用来定义Range。如果使用Range来定义ROI,那么前例中定义ROI的代码可以重写为:

    //方法二 imageROI=image(Range(250,250+logoImage.rows),Range(200,200+logoImage .cols));

    下面我们来看一个实例,显示如何利用ROI将一幅图加到另一幅图的指定位置。大家如果需要复制以下函数中的代码直接运行,可以自己建一个基于console的程序,然后把函数体中的内容复制到main函数中,然后找两幅大小合适的图片,加入到工程目录下,并和代码中读取的文件名一致即可。

    在下面的代码中,我们通过一个图像掩膜(mask),直接将插入处的像素设置为logo图像的像素值,这样效果会很逼真。

    //----------------------【头文件、命名空间包含部分】---------------------- // 描述:包含程序所使用的头文件和命名空间 //------------------------------------------------------------------ #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> using namespace std; using namespace cv; //---------------------------【main( )函数】--------------------------- // 描述:控制台应用程序的入口函数,我们的程序从这里开始执行 //------------------------------------------------------------------ int main( ) { //【1】读入图像 Mat srcImage1= imread("/Users/cdq/Desktop/test.jpg"); Mat logoImage= imread("/Users/cdq/Desktop/favicon.jpg"); if(!srcImage1.data ) { printf("读取srcImage1错误~! \n"); } if(!logoImage.data ) { printf("读取logoImage错误~! \n"); } //【2】定义一个Mat类型并给其设定ROI区域 Mat imageROI=srcImage1(Rect(200,250,logoImage.cols,logoImage.rows)); //【3】加载掩模(必须是灰度图) Mat mask= imread("/Users/cdq/Desktop/favicon.jpg",0); //【4】将掩膜复制到ROI logoImage.copyTo(imageROI,mask); //【5】显示结果 namedWindow("<1>利用ROI实现图像叠加示例窗口"); imshow("<1>利用ROI实现图像叠加示例窗口",srcImage1); waitKey(0); return 0; }

    这个函数首先是载入了两张jpg图片到srcImage1和logoImage中,然后定义了一个Mat类型的imageROI,并使用Rect设置其感兴趣区域为srcImage1中的一块区域,将imageROI和srcImage1关联起来。接着定义了一个Mat类型的的mask并读入dota_logo.jpg,顺势使用Mat:: copyTo把mask中的内容复制到imageROI中,于是就得到了最终的效果图。namedWindow和imshow配合使用,显示出最终的结果。

    线性混合操作

    线性混合操作是一种典型的二元(两个输入)的像素操作,它的理论公式如下:

    我们通过在范围0到1之间改变alpha值,来对两幅图像(f0(x)和f1(x))或两段视频(同样为(f0(x)和f1(x))产生时间上的画面叠化(cross-dissolve)效果,就像幻灯片放映和电影制作中的那样,也就是在幻灯片翻页时设置的前后页缓慢过渡叠加效果,以及电影情节过渡时经常出现的画面叠加效果。

    实现方面,主要运用了OpenCV中addWeighted()函数,下面来一起全面地了解它。

    这个函数的作用是计算两个数组(图像阵列)的加权和。原型如下:

    void (InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype=-1);

    第一个参数,InputArray类型的src1,表示需要加权的第一个数组,常常填一个Mat; 第二个参数,double类型的alpha,表示第一个数组的权重; 第三个参数,InputArray类型的src2,表示第二个数组,它需要和第一个数组拥有相同的尺寸和通道数; 第四个参数,double类型的beta,表示第二个数组的权重值; 第五个参数,double类型的gamma,一个加到权重总和上的标量值。其含义通过接下来列出的式子自然会理解; 第六个参数,OutputArray类型的dst,输出的数组,它和输入的两个数组拥有相同的尺寸和通道数; 第七个参数,int类型的dtype,输出阵列的可选深度,有默认值-1。当两个输入数组具有相同的深度时,这个参数设置为-1(默认值),即等同于src1.depth( )。

    下面的数学公式表示:用addWeighted函数计算以下两个数组(src1和src2)的加权和,得到结果输出给第四个参数,也就是addWeighted函数的作用的矩阵表达式。

    dst = src1[I]*alpha+ src2[I]*beta + gamma;

    其中I是多维数组元素的索引值。而且,在遇到多通道数组的时候,每个通道都需要独立地进行处理。另外需要注意的是,当输出数组的深度为CV_32S时,这个函数就不适用了,这时候就会内存溢出或者算出的结果压根不对。

    理论和函数的讲解就是上面这些,接着我们来看代码实例,以融会贯通。 批注:未验证。

    //----------------------【头文件、命名空间包含部分】---------------------- // 描述:包含程序所使用的头文件和命名空间 //------------------------------------------------------------------ #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> using namespace std; using namespace cv; //---------------------------【main( )函数】--------------------------- // 描述:控制台应用程序的入口函数,我们的程序从这里开始执行 //------------------------------------------------------------------ int main( ) { //【0】定义一些局部变量 double alphaValue = 0.5; double betaValue; Mat srcImage2, srcImage3, dstImage; //【1】读取图像 ( 两幅图片需为同样的类型和尺寸 ) srcImage2= imread("mogu.jpg"); srcImage3= imread("rain.jpg"); if(!srcImage2.data ) { printf("你妹,读取srcImage2错误~! \n"); return 0; } if(!srcImage3.data ) { printf("你妹,读取srcImage3错误~! \n"); return 0; } //【2】做图像混合加权操作 betaValue= ( 1.0 - alphaValue ); addWeighted(srcImage2, alphaValue, srcImage3, betaValue, 0.0, dstImage); //【3】创建并显示原图窗口 namedWindow("<2>线性混合示例窗口【原图】 by浅墨", 1); imshow("<2>线性混合示例窗口【原图】 by浅墨", srcImage2 ); namedWindow("<3>线性混合示例窗口【效果图】 by浅墨", 1); imshow("<3>线性混合示例窗口【效果图】 by浅墨", dstImage ); waitKey(0); return 0; }

    批注:ROI是感兴趣区域,使用copy_to将覆盖原数据,使用addWeighted将进行图像混合。

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

    而为了更好地观察一些图像材料的特征,有时需要对RGB三个颜色通道的分量进行分别显示和调整。通过OpenCV的split和merge方法可以很方便地达到目的。

    split()函数

    split函数用于将一个多通道数组分离成几个单通道数组。 函数原型:

    void split(const Mat& src, Mat* mvbegin); void split(InputArray m,OutputArrayOfArrays mv);

    第一个参数,InputArray类型的m或者const Mat&类型的src,填我们需要进行分离的多通道数组。 第二个参数,OutputArrayOfArrays类型的mv,填函数的输出数组或者输出的vector容器。

    TODO.

    参考链接: https://book.douban.com/subject/26320896/ https://blog.csdn.net/poem_qianmo/article/details/20911629

    你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

    新的改变

    我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

    全新的界面设计 ,将会带来全新的写作体验;在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;全新的 KaTeX数学公式 语法;增加了支持甘特图的mermaid语法1 功能;增加了 多屏幕编辑 Markdown文章功能;增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;增加了 检查列表 功能。

    功能快捷键

    撤销:Ctrl/Command + Z 重做:Ctrl/Command + Y 加粗:Ctrl/Command + B 斜体:Ctrl/Command + I 标题:Ctrl/Command + Shift + H 无序列表:Ctrl/Command + Shift + U 有序列表:Ctrl/Command + Shift + O 检查列表:Ctrl/Command + Shift + C 插入代码:Ctrl/Command + Shift + K 插入链接:Ctrl/Command + Shift + L 插入图片:Ctrl/Command + Shift + G 查找:Ctrl/Command + F 替换:Ctrl/Command + G

    合理的创建标题,有助于目录的生成

    直接输入1次#,并按下space后,将生成1级标题。 输入2次#,并按下space后,将生成2级标题。 以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

    如何改变文本的样式

    强调文本 强调文本

    加粗文本 加粗文本

    标记文本

    删除文本

    引用文本

    H2O is是液体。

    210 运算结果是 1024.

    插入链接与图片

    链接: link.

    图片:

    带尺寸的图片:

    居中的图片:

    居中并且带尺寸的图片:

    当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

    如何插入一段漂亮的代码片

    去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

    // An highlighted block var foo = 'bar';

    生成一个适合你的列表

    项目 项目 项目 项目1项目2项目3 计划任务 完成任务

    创建一个表格

    一个简单的表格是这么创建的:

    项目Value电脑$1600手机$12导管$1

    设定内容居中、居左、居右

    使用:---------:居中 使用:----------居左 使用----------:居右

    第一列第二列第三列第一列文本居中第二列文本居右第三列文本居左

    SmartyPants

    SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

    TYPEASCIIHTMLSingle backticks'Isn't this fun?'‘Isn’t this fun?’Quotes"Isn't this fun?"“Isn’t this fun?”Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

    创建一个自定义列表

    Markdown Text-to- HTML conversion tool Authors John Luke

    如何创建一个注脚

    一个具有注脚的文本。2

    注释也是必不可少的

    Markdown将文本转换为 HTML。

    KaTeX数学公式

    您可以使用渲染LaTeX数学表达式 KaTeX:

    Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

    Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

    你可以找到更多关于的信息 LaTeX 数学表达式here.

    新的甘特图功能,丰富你的文章

    Mon 06 Mon 13 Mon 20 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid 关于 甘特图 语法,参考 这儿,

    UML 图表

    可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

    张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

    这将产生一个流程图。:

    链接 长方形 圆 圆角长方形 菱形 关于 Mermaid 语法,参考 这儿,

    FLowchart流程图

    我们依旧会支持flowchart的流程图:

    Created with Raphaël 2.2.0 开始 我的操作 确认? 结束 yes no 关于 Flowchart流程图 语法,参考 这儿.

    导出与导入

    导出

    如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

    导入

    如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入, 继续你的创作。


    mermaid语法说明 ↩︎

    注脚的解释 ↩︎

    Processed: 0.014, SQL: 9