2、图像处理基础
回顾
Mat
行 列 通道 维度
row col channel dim
在opencv里面的维度一定是大于等于2的,比如上面的一个行向量就是一个1*7的“二维向量”。
在Mat里面除了上面四个比较常用的量以外,还有flag变量,它是在Mat 的头里面,包含了图像的维度,深度(是short类型、char类型还是double类型的);除了flag还有一个data的指针,它是指向内存中数据所在地址的;还有一个refcount指针,引用计数指针,看有多少指针指向了这个地址。
第二讲的内容开始
在Mat中有一个data指针,data指针指向的是数据在内存中的首地址,
M.step[1]为每一行的列元素的大小!!!
由于上面的行和列表达起来有歧义,故其row和col都表示为-1.上面的矩阵为3*4*6的矩阵。
上面的step[0]为一个面的大小,为4*6*(4*16/8)=192bytes
step[1]为6*(4*16/8)=48
step[2]为4*(16/8)=8
例子:
Mat m(400,400,CV_8U,Scalar(0));
定义一个单通道的400*400大小的矩阵,矩阵元素初值为0,即全黑的图像

#include <iostream> #include<highgui.h> #include "opencv2/opencv.hpp" using namespace cv; using namespace std; int main(int argc, char* argv[]) { Mat m(400,400,CV_8U,Scalar(0)); for (int col = 0; col < 400; col++) { for (int row = 195; row < 205; row++) { cout << (int)(*(m.data + m.step[0] * row + m.step[1] * col)) << " ==>"; //获取第[row,col]个像素点的地址并用*符号解析 *(m.data + m.step[0] * row + m.step[1] * col) = 255; cout << (int)(*(m.data + m.step[0] * row + m.step[1] * col)) << endl; } } imshow("canvas",m); cvWaitKey(); getchar(); return 0; }
例子:
Vec3i color定义一个变量,它是Vector类型的,有3个元素,且是int型的;
注意cvWaitKey()可以传参,单位是毫秒;
#include <iostream> #include<highgui.h> #include "opencv2/opencv.hpp" using namespace cv; using namespace std; int main(int argc, char* argv[]) { Mat m = imread("lena.jpg"); int *p_address; Vec3i color; for (int col = 20; col < 40; col++) { for (int row = 2; row < 20; row++) { color[0] = (int)(*(m.data+m.step[0]*row+m.step[1]*col)); color[1] = (int)(*(m.data + m.step[0] * row + m.step[1] * col+m.elemSize1())); color[2] = (int)(*(m.data + m.step[0] * row + m.step[1] * col + m.elemSize1()*2)); //获取第[row,col]个像素点的地址并用*符号解析 cout << color[0] << "," << color[1] << "," << color[2] << " ==>"; color[0] = 255; color[1] = 0; color[2] = 0; *(m.data + m.step[0] * row + m.step[1] * col) = color[0]; *(m.data + m.step[0] * row + m.step[1] * col + m.elemSize1()) = color[1]; *(m.data + m.step[0] * row + m.step[1] * col + m.elemSize1() * 2) = color[2]; cout << (int)(*(m.data + m.step[0] * row + m.step[1] * col)) << (int)(*(m.data + m.step[0] * row + m.step[1] * col + 1)) << (int)(*(m.data + m.step[0]*row + m.step[1] * col + 2)) << endl; } } imshow("canvas",m); cvWaitKey(); getchar(); return 0; }
结果:
上面的数据类型是错误的!!!!!
opencv的模板类Mat_类
例子:

#include <iostream> #include<highgui.h> #include "opencv2/opencv.hpp" using namespace cv; using namespace std; int main(int argc, char* argv[]) { Mat m = imread("lena.jpg"); Mat_<Vec3b> m2 = m; //for()循环画一个红色的实心圆 for (int y = 21; y < 42; y++) { for (int x = 2; x < 21; x++) { if (pow(double(x - 11), 2) + pow(double(y - 31), 2) - 64.0 < 0.00000000001) { //Mat_模板类实现了对()的重载,可以定位到一个像素 m2(x, y) = Vec3b(0,0,255); } } }imshow("canvas",m); cvWaitKey(); getchar(); return 0; }
结果如下:
注意pow函数的含义:pow(src,double p,dst);//如果p是整数dst(I)=src(I)^p;其他|src(I)|^p
像素值的读写6
上面是进行图像的量化,把原来的像素点(之前是0-255),现在不需要那么高的像素级,把它降低10个等级,原来的像素级除以10再乘以10;
查找表,在上面先定义了一个uchar型的数组,uchar table[256];然后再进行像素量化降级,可以对比用二值图像来理解,然后再建立一个1*256的一个数组lookuptable。LUT(源图像输入,查找表,输出图像)。它只有在有映射关系的时候才能用到,是一个特例。
在2.4以前,有这两种数据类型IplImage和CvMat,我们现在都用Mat,然后有一些例程,它的形式是IplImage *p,这种图像指针类型的,我们可以用&iplimg这种引用的方式转换为Mat类型。
同时也支持这两种方式互转,
关于图像的读取
Mat imread(const string &filename,int flags=1)
imread()函数,返回值是一个Mat类型,它读取失败里面的*data是指向NULL的,建议不要使用指针来判断是否为NULL来说明是否读取成功;它自己有一个成员函数来判断的isempty();
flags只要大于0就代表返回3通道图像,灰度图像就会强制转化为3通道,flag为0,强制转化为单通道;flag小于0,那么是什么就读什么,不进行转换;
有一种图像格式叫做PNM,PPM,PGM,可移植像素格式,它是纯文本的格式,常见的是PGM的格式;
imwrite()是存储文件
注意并不是所有的Mat都可以存储成图像,目前只支持8U的单通道或者3通道格式,如果想把16U的转换成功的话,就只能用PNG格式或者JPEG2000,注意一下。
如果这个文件夹下有这个文件,它是直接覆盖的,不会有任何提醒;
首先来看看imwrite()函数的具体用法。
bool imwrite(const string& filename, InputArray img, const vector<int>& params=vector<int>() )
该函数是把程序中的Mat类型的矩阵保存为图像到指定位置。
参数filename为所需保存图像的文件目录和文件名。这里的文件名需要带有图像格式后缀的,目前OpenCV该函数只支持JPEG,PNG,PPM,PGM,PBM,TIFF等。并不是所有Mat类型都支持。
img参数为图像数据来源,其类型为Mat。注意也不是所有格式的Mat型数据都能被使用保存为图片,目前OpenCV主要只支持单通道和3通道的图像,并且此时要求其深度为8bit和16bit无符号(即CV_16U)。所以其他一些数据类型是不支持的,比如说float型等。如果Mat类型数据的深度和通道数不满足上面的要求,则需要使用convertTo()函数和cvtColor()函数来进行转换。convertTo()函数负责转换数据类型不同的Mat,即可以将类似float型的Mat转换到imwrite()函数能够接受的类型。而cvtColor()函数是负责转换不同通道的Mat,因为该函数的第4个参数就可以设置目的Mat数据的通道数(只是我们一般没有用到它,一般情况下这个函数是用来进行色彩空间转换的)。另外也可以不用imwrite()函数来存图片数据,可以直接用通用的XML IO接口函数将数据存在XML或者YXML中。
参数params是用来设置对应图片格式的参数的,因为一般情况下这些图片格式都是经过了压缩的,这里就是设置这些压缩参数来控制图片的质量。该参数是一个vector<int>类型,里面分别存入paramId_1, paramValue_1, paramId_2, paramValue_2, ... 也就是说存入一对属性值。如果不设置该参数的话,则程序会自动根据所保存的图像格式采用一个默认的参数。
介绍一下,打开外设,或者读取视频的一个例子
VideoCapture这样一个类,有一个构造函数直接指定设备的编号cap(0);注意在工程的时候通过hub连接两个或者更多的时候,其ID可能就会是同一个,出现打不开的情况。

#include<iostream> #include"opencv2/opencv.hpp" #include<stdio.h> using namespace std; using namespace cv; int main(int argc, char *argv[]) { //打开摄像头 VideoCapture cap(0); //打开视频文件 //VideoCapture cap("video.short.raw.avi"); //检查是否成功打开 if (!cap.isOpened()) { cerr << "can not open a camera or file."<<endl; return -1; } Mat edges; //创建窗口 namedWindow("edges",1); for (;;) { Mat frame; //从cap中读一帧,存到frame cap >> frame; //如果未读到图像 if (frame.empty()) break; //将读到的图像转为灰度图 cvtColor(frame,edges,CV_BGR2GRAY); //进行边缘提取操作 Canny(edges,edges,0,30,3); //显示结果 imshow("edges",edges); //等待30s,如果按键则退出循环 if (waitKey(30) >= 0) break; } //退出时会自动释放 cap 中占用资源 return 0; }
上面的例子是读,下面的例子是写

#include<iostream> #include"opencv2/opencv.hpp" #include<stdio.h> using namespace std; using namespace cv; int main(int argc, char *argv[]) { //定义视频的宽度和高度 Size s(320, 240); //创建writer,并制定FOURCC及FPS等参数 VideoWriter writer = VideoWriter("myvedio.avi",CV_FOURCC('M','J','P','G'),25,s); //检查是否成功创建 if (!writer.isOpened()) { cerr << "Can not create video file.\n"<<endl; return -1; } //视频帧 Mat frame(s,CV_8UC3); for (int i = 0; i < 100; i++) { //将图像置为黑色 frame = Scalar::all(0); //将整数i转为i字符串类型 char text[128]; sprintf_s(text,"%d",i); //将数字绘到画面上 putText(frame,text,Point(s.width/3,s.height/3),FONT_HERSHEY_SCRIPT_SIMPLEX,3,Scalar(0,0,255),3,8); //将图像写入视频 writer << frame; } VideoCapture cap("myvedio.avi"); for (int i = 0; i < 100; i++) { Mat frame; //从 cap 中读一帧,存到 frame cap >> frame; //如果未读到图像 if (frame.empty()) break; //显示结果 imshow("frame", frame); waitKey(1000); } //退出程序时自动关闭视频文件 waitKey(0); return 0; }
本课程涉及到深度学习与机器学习相关的内容,所以要学习一下python
python环境的安装,建议Anaconda+Ipython+(PyScripter)(版本2.7比较流行或者3.5都可以)
print函数在2.7没有括号,在3.5有括号
在2.7版本
x=3,x/2=1,是整型
在3.5版本
x=3,x/2=1.5是浮点型
机器学习
机器学习:要随机得观测数据,要随机得对观测数据进行采样,要学习数据的性质,其实也叫做特征feature,以及属性,属性通常叫做label,他是属于那一个类别,再来预测新的未知的数据的属性。在机器学习当中,我们通常要把数据分成两部分,一部分叫做training(训练集用来学习数据特征),另一部分叫做test(测试集用来测试算法是否准确)。机器学习的问题主要分为
监督学习和非监督学习
监督学习除了数据本身以外,还有额外的属性部分,也就是说会有一些feature,然后再额外的会有一些label,这样的问题分为分类和回归两部分;分类问题可以理解为输出是离散数据;回归问题输出的就是连续的,比如说根据年龄、体重,来预测身高。监督学习主要是分类,尽量把问题转化为分类问题。
如果输入没有额外的label等,那么就叫做非监督学习,主要分为聚类(kmeans)和核密度估计。
Knn-Grassroots Democracy
当有一个未知物体的时候,在我们做出推荐之前会考虑一下它周围的数据的label是属于那个类别,从而觉得这个数据是属于那个类别的。
KNN算法应用得背景是字符识别,最常见的数据库在网站http://yann.lecun.com/exdb/mnist
knn作为一种机器学习算法也分为两部分,训练算法识别特征的类别,然后在训练集上测试;KNN的训练部分就是把训练集和标签全部存储,比如说,给出5这样一个训练的图像
,
它的label就是5。会有一个其他图片比如说是0,它的label就是0,
KNN就会把这两个属性全都存储下来,它就学习完了,只要把数据全部喂给算法,它就已经完成识别了,什么操作都没有,接下来就是测试部分,只要提供测试集,没有标签,在指定K的大小之后,让算法来猜它是什么数据。它是没有训练的,平时不学习,等到测试的时候问题就来了,它就把所有的训练集跟测试数据进行一一比对,比对就涉及到一个距离问题,测试两个向量的距离distance。
下面将一下聚类算法
上面图在轮廓上是比较相似的,但是在语义上是不同的。
讲到聚类算法必须提到K-means算法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】