OpenCV学习笔记——《OpenCV3编程入门》读书笔记
2016-12-05 21:00
429 查看
这篇读书笔记,主要记录我阅读《OpenCV3编程入门》的读书笔记,作者毛星云写的这本书十分适合接触OpenCV的初学者,虽然理论性的内容是简要概括,但还是可以大致的理解。。此书最好的就是提供了大量的完整例子的代码,对初学者入门相当有帮助,建议各位OpenCV的初学者可以认真阅读一下这本书,会对OpenCV的编程有个一个整体的初步认识。
OpenCV学习笔记
chap1 邂逅OpenCV
1.using namespace cv; //包含cv命名空间
2.Mat srcImage = ("1.jpg"); //载入图像
3.imshow("[原始图]",srcImage);//显示图像
4.waitKey(0); //等待任意按键按下
5.waitKey(6000); //等待6000ms后窗口自动关闭
6.blur(srcImage,dstImage,Size(7,7)); //均值滤波操作
7. cvtColor(srcImage, grayImage, CV_BGR2GRAY ); //将原图像转换为灰度图像
8.读入视频
法一:VideoCapturecapture("1.avi");
法二:VideoCapturecapture; capture.open("1.avi");
9. 调用摄像头读入视频
VideoCapture capture(0);
chap2. 启程前的认知准备
chap3. HighGUI图形用户界面初步
1.OpenCV中的C++类和函数都是定义在命名空间cv之内的,所以在代码开头 的位置加上using namespacecv; 这句代码。
2.Mat类是用于保存图像以及其他矩阵数据的数据结构,默认尺寸为0
3. 图像的载入:Matimread(const string& filename, int flags=1);第一个参数conststring& filename指的是载入图片的路径名;第二个参数int flags是载入标识符,指定一个夹在图像的类型,默认为1,flags>0返回3通道彩色图像,flags=0返回灰度图像,flags<0返回Alpha通道的加载图像。
4.图像的显示:imshow(conststring& winname, InputArray mat);第一个参数指的是要显示窗口标识名称;第二个参数填要显示的图像。
5. 创建窗口namedWindow
namedWindow(cosnt string& winname, int flags=WINDOW_AUTOSIZE);
第一个参数填写被用作窗口标识符的窗口名称;第二个参数窗口的标识,可以填如下几种值:WINDOW_NORMAL用户可以改变窗口的大小,WINDOW_AUTOSIZE设置这个值(为默认值),窗口大小会自动调整以适应所显示的图像,且用户没法手动改变窗口大小。
6. 输出图像到文件imwrite()函数
imwrite(cosntstring& filename, InputArray img, const vector<int>¶ms=vecror<int>() );
第一个参数,填需要的写入的文件名,要带上后缀,如“123.jpg”;第二个参数,一般填一个Mat类型的图像数据;第三个参数,表示特定格式保存的参数编码,默认值vecror<int>()
7.创建滑动条:creatTrackbar()函数
creatTrackbar( conststring& trackbarname, conststring& winname, int* value, intcount, TrackbarCallback onChange=0, void* userdata=0)
第一个参数,trackbarname,轨迹条的名字;
第二个参数,winname,窗口的名字,表示轨迹条依附到的窗口;
第三个参数,value,指向整形的指针,表示滑块的位置,创建时滑块的初始位置就是该变量当前的值;
第四个参数,count,表示滑块可以达到的最大位置的值;
第五个参数,onChange,指向回调函数的指针,滑块位置改变,这个函数都进行回调,且这个函数的原型必须为voidXXXX(int, void*),第一个参数是轨迹条的位置,第二个参数是用户数据。若回调是NULL指针,则表示没有回调函数的作用,仅第三个参数value有变化;
第六个参数,userdata,默认值0。
chap4. OpenCV数据结构与基本绘图
1.使用函数clone()或者copyTo() 来复制一幅图像的矩阵
Mat F= A.clone(); Mat G; A.copyTo(G)
2. 矩形的表示:Rect类
Rect类的成员有x,y,width,height,分别为左上角点的坐标和矩形的宽、高。
contains(Point)判断点是否在矩形内;
inside(Rect)判断矩形是否在该矩形内;
tl()返回左上角点坐标; br()返回右下角点坐标
3.颜色空间转换:cvtColor()函数
cvtColor(InputArraysrc, OutputArray dst, int code, int dstCn=0)
第一个参数,输入图像;
第二个参数,输出图像;
第三个参数,颜色空间转换的标识符;
第四个参数,目标图像的通道数,若为0,则表示其取源图像的通道数。
4. 创建空白的Mat图像
MatatomImage=Mat::zeros( 长, 宽, CV_8UC3 );
5.OpenCV默认的图片通道存储顺序是BGR,即蓝绿红,而不是RGB
chap5. core组件进阶
1. 按原始图的参数规格来创建创建效果图
MatsrcImage = imread("1.jpg");
Mat dstImage;
dstImage.create(srcImage.rows,srcImage.cols,srcImage.type());//效果图的大小、类型与原图片相同
2.计时函数 getTickCount()和getTickFrequency()
//记录起始时间
doubletime0 = static_cast<double>(getTickCount());
//计算运行时间并输出
time0= ((double)getTickCount() - time0)/getTickFrequency();
cout<<"\t此方法运行时间为:"<<time0<<"秒"<<endl; //输出运行时间
3.Mat类的公有成员变量cols和rows给出了图像的宽和高
srcImage.rows
srcImage.cols
4.Mat类的成员函数channels()用于返回图像的通道数;
灰度图的通道数为1,彩色图的通道数为3.
5. Mat类提供ptr函数得到图像任意行的首地址
uchar*data = outputImage.ptr<uchar>(i); //获取第i行的首地址
6.访问图像中的像素P100-114
7. 感兴趣区域(ROI,regionof interest)
两种方法定义ROI:
第一种,用表示矩形区域的Rect,它指定矩形的左上角坐标和矩形的长宽
MatimageROI;
imageROI=image(Rect(500,250,logo.cols,logo.rows));
第二种,指定感兴趣行或者列的范围(Range),Range是指从起始索引到终止索引(不包括终止索引)的一连段连续序列。
imageROI=image(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols))
8. MatimageROI=image(Rect(500,250,logo.cols,logo.rows));
Matmask=imread("logo.jpg",0);//加载掩膜,必须为灰度图
logoImage.copyTo(imageROI,mask);//将掩膜复制到ROI
9. 计算数组加权和:addWeighted()函数 (图像线性混合)
voidaddWeighted(InputArray src1, double alpha, InputArray src2, double beta, doublegamma, OutputArray dst, int dtype=-1);
第一个参数,src1,表示要加权的第一个数组;
第二个参数,alpha,表示第一个数组的权重;
第三个参数,src2,第二个数组,需要和第一个数组有相同的尺寸和通道数;
第四个参数,beta,第二个数组的权重;
第五个参数,gamma,一个加到权重和上的标量值;
第六个参数,dst,输出的数组,和输入的两个数组有相同的尺寸和通道数;
第七个参数,dtype,输出阵列的可选深度,默认值-1.
数学公式表达:
dst =src1[I]*alpha + src2[I]*beta + gamma;
10. 通道分离:split()函数
将一个多通道数组分离成几个单通道数组
voidsplit(const Mat&src, Mat*mvbegin);
或者 voidsplit(InputArray m, OutputArrayOfArrays mv);
第一个参数,m或者src,填需要进行分离的多通道数组;
第二个参数,mv,填输出数组或者输出的vector容器
eg.
vector<Mat>channels;
MatimageBlueChannel;
MatimageGreenChannel;
MatimageRedChannel;
srcImage4=imread("dota.jpg");
split(srcImage4,channels);//分离彩色通道,将3通道图像转成3个单通道图像;
imageBlueChannel=channels.at(0);
imageGreenChannel=channels.at(1);
imageRedChannel=channels.at(2);
11. 通道合并:merge()函数,是split()函数的逆向操作
将多个数组合并成一个多通道的数组
voidmerge(InputArrayOfArrays mv,OuoputArray dst);
eg.接着上面
merge(channels,mergeImage);
12. 创建0矩阵的图像
g_srcImage= imread( "1.jpg");
g_dstImage= Mat::zeros( g_srcImage.size(), g_srcImage.type() );
13. 一般的图像处理算子都是一个函数,它接受一个或多个输入图像,并产生输出图像
14.g(i,j)=a*f(i,j)+b;
a称为增益(gain),对比度
b称为偏置(bias),亮度
15. 访问图像的每一个像素,使用这样的语法image.at<Vec3b>(y,x)[c]
y是像素所在的行,x是像素坐在的列,c是B,G,R(对应0,1,2)其中之一;
eg.
// 三个for循环,执行运算 g_dstImage(i,j) = a*g_srcImage(i,j)+ b
for(inty = 0; y < g_srcImage.rows; y++ )
{
for(intx = 0; x < g_srcImage.cols; x++ )
{
for(intc = 0; c < 3; c++ )
{
g_dstImage.at<Vec3b>(y,x)[c] = saturate_cast<uchar>((g_nContrastValue*0.01)*( g_srcImage.at<Vec3b>(y,x)[c])
+ g_nBrightValue );
}
}
}
★运算结果可能会超出像素取值范围(溢出),还可能是非整数(如果是浮点数的话),所以用saturate_cast对结果进行转换,确保它为有效值。
16. //输出一些帮助信息
cout<<endl<<"\t运行成功,请调整滚动条观察图像效果\n\n"
<<"\t按下“q”键时,程序退出\n";
//按下“q”键时,程序退出
while(char(waitKey(1))!=
'q') {}
17. Mat srcImage = imread("1.jpg",0);
//【1】以灰度模式读取原始图像并显示
18. 离散傅立叶变换DFT P135
19.getOptimalDFTSize 返回给定向量尺寸的傅立叶最优尺寸大小
20.copyMakeBorder 扩充图像边界
21.magnitude 计算二维矢量的幅值
22.log 计算每个数组元素绝对值的自然对数
23.normalize 进行矩阵归一化
chap6. 图像处理
1. 平滑处理(smoothing)也称模糊处理(bluring)
用途:减少图像上的噪点、失真、降低图像分辨率。
2. 图像滤波,尽量保留图像细节特征的条件下对目标图像的噪声进行抑制。
3. 信号或者图像,能量大多集中在幅度谱的低频和中频段,在高频段,有用的信息经常被噪声淹没,所以,一个能降低高频成分幅度的滤波器就能够减弱噪声的影响。
4. 平滑滤波是低频增强的空间域滤波技术,目的:一,模糊;二,消除噪音。
5. 线性滤波
方框滤波,均值滤波,高斯滤波
6. 非线性滤波
中值滤波,双边滤波
7.常见的线性滤波
低通滤波器:允许低频率通过;
高通滤波器:允许高频率通过;
带通滤波器:允许一定范围频率通过;
带阻滤波器:阻止一定范围频率通过并允许其他频率通过;
全通滤波器:允许所有频率通过,仅改变相位关系;
陷波滤波器:阻止一个狭窄频率范围通过,一种特殊的带阻滤波器。
8. 滤波:
是将信号中特定波段频率滤除的操作,是抑制和防止干扰的一项重要措施。
9. 滤波可分为低通滤波和高通滤波:
低通就是模糊;
高通就是锐化。
10. 高斯滤波:指用高斯函数作为滤波函数的滤波操作;
11. 高斯模糊:就是高斯低通滤波。
12. 方框滤波BoxFilter() P157
13. 均值滤波是方框滤波归一化(normalized)后的特殊情况
14. 均值滤波的缺陷:不能很好的保护细节,在去噪的同时也破坏了图像的细节部分,图像变的模糊,不能很好地去除噪声点。
15. 均值滤波:blur函数
16. 高斯滤波:GaussianBlur函数
作用:模糊一张图片
17. 中值滤波(Medianfilter),非线性滤波,用点邻域灰度值的中指来代替该像素点的灰度值,在去除脉冲噪声、椒盐噪声的同时又可以保留图像的边缘细节。
函数medianBlur()
18. 双边滤波(Bilateralfilter),非线性滤波,结合图像的空间邻近度和像素值相似度的一种这种处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的,具有简单、非迭代、局部的特点。
函数bilateralFilter()
19. 膨胀(dilate) 腐蚀(erode)
功能:消除噪声;分割独立的图像元素,在图像中连接相邻的元素;寻找图像中的明显的极大值区域或极小值区域;求出图像的梯度。
20. 腐蚀和膨胀是对白色部分(高亮部分)而言的,不是黑色部分。
膨胀,对图像中的高亮部分进行膨胀,效果图拥有比原图更大的高亮区域;
腐蚀,原图中的高亮部分被腐蚀,效果图拥有比原图更小的高亮区域。
21.膨胀,就是求局部最大值的操作;
复试,就是求局部最小值的操作。
22. 函数getStructuringElement返回指定形状和尺寸的结构元素(内核矩阵)
第一个参数,表示内核的形状,三种形状可以选择,
矩形:MORPH_RECT;
交叉形:MORPH_CROSS;
椭圆形:MORPH_ELLIPSE。
第二个参数,内核的尺寸,eg: Size(15,15)
第三个参数,锚点的位置
23. 利用morphologyEx函数进行膨胀
Matelement=getStructuringElement(MORPH_RECT,Size(15,15));
morphologyEx(srcimage,dstimage, MORPH_DILATE, element);
24. 利用morphologyEx函数进行腐蚀
morphologyEx(srcimage,dstimage, MORPH_ERODE, element);
25. 开运算,就是先腐蚀后膨胀的过程
数学表达式:dst=open(src,element)=dilate(erode(src,element))
函数:morphologyEx(src,dst,MORPH_OPEN,element);
它可以用来消除小物体,在纤细点分离物体,且在平滑较大物体的边界的同时不明显改变其面积。
26. 闭运算,先膨胀后腐蚀的过程
数学表达式:dst=clese(src,element)=erode(dilate(src,element))
函数:morphologyEx(image,image, MORPH_CLOSE, element);
它可以排除小型黑洞(黑色区域)。
27. 形态学梯度,是膨胀图与腐蚀图之差
数学表达式:dst=morph-grad(src,element)=dilate(src,element)-erode(src,element)
函数morphologyEx(image,image, MORPH_GRADIENT, element);
28. 顶帽运算,原图像与开运算的结果图之差
数学表达式:dst=tophat(src,element)=src-open(src,element)
函数 morphologyEx(image, image, MORPH_TOPHAT, element);
它得到的效果图突出了比原图轮廓周围更明亮的区域
29. 黑帽运算,闭运算的结果与原图像之差
数学表达式:dst=blacehat(src,element)=close(src,element)-src
函数 morphologyEx(image, image, MORPH_BLACKHAT, element);
它突出了比原图轮廓周围的区域更暗的区域,可以用来分离比邻近点暗一些的斑块,效果图有着非常完美的轮廓。
30. 漫水填充 floodFill
选中和种子点相连的区域,将该区域替换成指定的颜色。
31. 图像金字塔
是图像中多尺度表达的一种,最主要应用于图像的分割,是一种以多分辨率来解释图像的有效但概念简单的结构。
32. 一幅图像的金字塔是一系列以金字塔形状排列,分辨率逐步降低,且来源于同一张原始图的图像集合。通过梯次向下采样获得,直到达到某个终止条件时停止。
33. 金字塔的底部是待处理图像的高分辨率表示,顶部是低分辨率的近似。
34. 层级越高,图像越小,分辨率越低。
35. 两种常见的图像金字塔
★高斯金字塔(Gaussianpyramid),用来向下采样。
★拉普拉斯金字塔(Laplacianpyramid),从金字塔低层图像重建上层未采样图像,预测残差,可以对图像进行最大程度还原,配合高斯金字塔一起使用。
二者区别:
★高斯金字塔用来向下采样图像;
★拉普拉斯金字塔从金字塔底层图像中向上采样,重建一个图像。
36. 尺寸调整:resize()函数
37. 向上采样:pyrUp()函数
作用,向上采样并模糊一张图片,简言之,放大一张图片。
38. 采样:pyrDown()函数
作用,向下采样并模糊一张图片,简言之,缩小一张图片。
39. 固定阈值操作:Threshold()函数 p238
40. 自适应阈值操作:adaptiveThreshold()函数
chap7. 图像变换
1. Mat srcImage1=srcImage.clone();
//使用函数clone()
复制一幅图像的矩阵
2. dstImage.create( srcImage1.size(),srcImage1.type() ); //
【1】创建与src同类型和大小的矩阵(dst)
3. cvtColor( srcImage1, grayImage,
CV_BGR2GRAY ); // 【2】将原图像转换为灰度图像
4. dstImage = Scalar::all(0);//【5】将g_dstImage内的所有元素设置为0
5.Canny边缘检测:Canny()函数 P250
6.sobel算子 P253
7.Laplacian算子 P256
8.convertScaleAbs()函数 用线性变换转换输入数组元素成8位无符号整型
9.scharr滤波器
主要是陪着Sobel算子的运算而存在
10. 霍夫变换(HoughTransform)
它是图像处理中的一种特征提取技术,该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换的结果。
优点:
分割结果Robustness,对数据的不完全或者噪声不是非常敏感。
11. 霍夫变换运用两个坐标空间之间的变换将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化为统计峰值问题。
12. 在使用霍夫变换之前,首先要对图像进行边缘检测处理,即霍夫变换的直接输入只能是边缘的二值图像。
23.OpenCV支持三种不同的霍夫线变换:
标准霍夫变换(StandardHough Transform, SHT);
多尺度霍夫变换(Multi-ScaleHough Transform, MSHT);
累计概率霍夫变换(ProgressiveProbabilistic Hough Transform, PPHT).
24.HoughLines函数用来调用标准霍夫变换(SHT)和多尺度霍夫变换(MSHT)。
25. HoughLinesP函数调用累计概率霍夫变换(PPHT)。PPHT执行效率很高,相比于HoughLines,更倾向于HoughLinesP函数。
26. 霍夫线变换的原理 P268
(1)一条直线在图像二维空间,可由两个变量表示
笛卡尔坐标系:用斜率和截距
极坐标系:用极径和极角
对于霍夫变换采用极坐标系来表示直线。
(2)对于一个点(x,y),每一对的极坐标系的参数,极径和极角,代表一条通过(x,y)的直线。
(3)对于一个定点(x,y),在极坐标下,对极径极角绘制出所有通过(x,y)的直线,得到的是一条正弦曲线。
(4)如果两个不同的点进行上述操作,得到的曲线在平面θ-r相交,则意味着它们通过同一条直线。
(5)一条直线可以通过在平面θ-r寻找交于一点的曲线数量来检测;越多的曲线交于一点,意味着这个交点表示的直线由更多的点组成。一般,可以设置直线上的点的阈值来定义多少条曲线交于一点,这样才认为检测到了一条直线。
(6)霍夫变换要做的就是追踪图像中每个点对应曲线间的角点,若交于一点的曲线数量超过了阈值,则可认为这个角点所代表的参数对(θ,rθ)为原图像中的一条直线。
27. 霍夫圆变换HoughCircles()函数
28. 重映射 remap() 函数
29. 放射变换:wrapAffine()函数
30. 求得仿射变换:
warpMat=getAffineTransform(srcTriangle,dstTriangle);
31. 计算二维旋转变换矩阵:getRotationMatrix2D()函数
32. 直方图均衡化:equalizeHist()函数
chap8. 图像轮廓与图像分割修复
1. 寻找轮廓:findContours()函数
2. 绘制轮廓drawContours()函数
3. 寻找凸包:convexHull()函数
4. 使用多边形将轮廓包围
返回外部矩形边界:boundingRect()函数
寻找最小包围矩形:minAreaRect()函数
寻找最小包围圆形:minEnclosingCircle()函数
用椭圆拟合二维点集:fitEllipse()函数
逼近多边形曲线:approxPolyDP()函数
5. 图像的矩
矩的计算:moments()函数
计算轮廓面积:contourArea()函数
计算轮廓长度:arcLength()函数
6. 分水岭算法:watershed()函数
7. 图像修补:inpaint()函数
chap9. 直方图与匹配
1. 计算直方图:calcHist()函数
2. 找寻最值:minMaxLoc()函数 p348
3. 对比直方图:compareHist()函数
4.MatND类是用于存储直方图的一种数据结构。
5. 计算反向投影:calBackProject()函数
6. 通道复制:mixChannels()函数
7. 模板匹配:matchTemplate()函数
模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术。
模板匹配不是基于直方图的,而是通过在输入图像上滑动图像快,对实际的图像快和输入图像进行匹配的一种匹配方法。
chap10. 角点检测
1. 角点,通常定义为两条边的角点;
更严格的说,角点的局部邻域应该具有两个不同区域的不同方向的边界;
在实际中,大多数角点检测方法检测的是拥有特定特征的图像点,而不仅仅是“角点”;
这些特征点在图像中有具体的坐标,并具有某些数学特征,如,局部最大、最小灰度、某些梯度特征等。
2. 兴趣点(interestpoints),也称为关键点(keypoints)、特征点(featurepoints)
3. 图像特征类型可分为三种:边缘、角点(感兴趣关键点)、斑点(Blobs)(感兴趣区域);
4. 角点:
如果,某一点在任意方向的一个微笑变动都会引起灰度很大的变化,则将其称之为角点!
5. 角点的具体描述:
a.一阶导数(即灰度的梯度)的局部最大所对应的像素点;
b.两条及两条以上边缘的交点;
c.图像中梯度值和梯度方向的变化速率都很高的点;
d.角点处的一阶导数最大,二阶导数为0,它只是了物体边缘变化的不连续的方向。
6. 当前图像处理领域,角点检测算法:
a.基于灰度图像的;
b.基于二值图像的;
c.基于轮廓图像的。
7. 基于灰度图像的角点检测,可分为三种:
a.基于梯度;
b.基于模板;
c.基于模板梯度。
8. 基于模板的方法,主要考虑像素领域点的灰度变化,即图像亮度的变化,将与邻点亮度对比足够大的点定义为角点。
9. 常见的基于模板的角点检测算法有:
a.Kitchen-Rosenfeld角点检测算法
b.Harris角点检测算法
c.KLT角点检测算法
d.SUSAN角点检测算法
10.Harris角点检测是一种直接基于灰度图像的角点提取算法,稳定性高,尤其对L型角点检测精度高。
但,由于采用了高斯滤波,运算速度相对较慢,角点信息有丢失和位置偏移的现象,且角点提取有聚簇现象。
11.Shi-Tomasi算子是Harriss算法的改进。
12. 寻找亚像素角点:cornerSubPix()函数
chap11. 特征检测与匹配
1.SUFT, SpeededUp Robust Features,加速版的具有鲁棒性的特征。
2.SUFT是SIFT(尺度不变特征变换算法)的加速版;一般来说,标准的SUFT算子比SIFT算子快好几倍;且,在多幅图片下具有更好的稳定性。
3. 绘制关键点:drawKeypoints()函数 p401
4.KeyPoint类 p402
5. //【2】使用SURF算子检测关键点
intminHessian = 700;//SURF算法中的hessian阈值
SurfFeatureDetector detector( minHessian );//定义一个SurfFeatureDetector(SURF)特征检测类对象
std::vector<KeyPoint>keyPoint1, keyPoints2;//vector模板类,存放任意类型的动态数组
//【3】调用detect函数检测出SURF特征关键点,保存在vector容器中
detector.detect(srcImage1, keyPoint1 );
detector.detect(srcImage2, keyPoints2 );
//【4】计算描述符(特征向量)
SurfDescriptorExtractor extractor;
Mat descriptors1, descriptors2;
extractor.compute(srcImage1, keyPoint1, descriptors1 );
extractor.compute( srcImage2,keyPoints2, descriptors2 );
6. //【5】使用BruteForce进行匹配
//实例化一个匹配器
BruteForceMatcher<
L2<float>> matcher;
std::vector<
DMatch >matches;
//匹配两幅图中的描述子(descriptors)
matcher.match( descriptors1,descriptors2, matches );
7.
//【6】绘制从两个图像中匹配出的关键点
Mat imgMatches;
drawMatches( srcImage1, keyPoint1,srcImage2, keyPoints2, matches, imgMatches );//进行绘制
8. 绘制匹配点:drawMatches()函数 p405
9.BruteForceMatcher类
用于进行暴力匹配。
10. //【6】存下符合条件的匹配结果(即其距离小于2*min_dist的),使用radiusMatch同样可行
std::vector<
DMatch >good_matches;
for(inti = 0; i < descriptors_1.rows; i++ )
{
if(matches[i].distance < 2*min_dist )
{good_matches.push_back( matches[i]); }
}
11.ORB特征提取 p425
OpenCV学习笔记
chap1 邂逅OpenCV
1.using namespace cv; //包含cv命名空间
2.Mat srcImage = ("1.jpg"); //载入图像
3.imshow("[原始图]",srcImage);//显示图像
4.waitKey(0); //等待任意按键按下
5.waitKey(6000); //等待6000ms后窗口自动关闭
6.blur(srcImage,dstImage,Size(7,7)); //均值滤波操作
7. cvtColor(srcImage, grayImage, CV_BGR2GRAY ); //将原图像转换为灰度图像
8.读入视频
法一:VideoCapturecapture("1.avi");
法二:VideoCapturecapture; capture.open("1.avi");
9. 调用摄像头读入视频
VideoCapture capture(0);
chap2. 启程前的认知准备
chap3. HighGUI图形用户界面初步
1.OpenCV中的C++类和函数都是定义在命名空间cv之内的,所以在代码开头 的位置加上using namespacecv; 这句代码。
2.Mat类是用于保存图像以及其他矩阵数据的数据结构,默认尺寸为0
3. 图像的载入:Matimread(const string& filename, int flags=1);第一个参数conststring& filename指的是载入图片的路径名;第二个参数int flags是载入标识符,指定一个夹在图像的类型,默认为1,flags>0返回3通道彩色图像,flags=0返回灰度图像,flags<0返回Alpha通道的加载图像。
4.图像的显示:imshow(conststring& winname, InputArray mat);第一个参数指的是要显示窗口标识名称;第二个参数填要显示的图像。
5. 创建窗口namedWindow
namedWindow(cosnt string& winname, int flags=WINDOW_AUTOSIZE);
第一个参数填写被用作窗口标识符的窗口名称;第二个参数窗口的标识,可以填如下几种值:WINDOW_NORMAL用户可以改变窗口的大小,WINDOW_AUTOSIZE设置这个值(为默认值),窗口大小会自动调整以适应所显示的图像,且用户没法手动改变窗口大小。
6. 输出图像到文件imwrite()函数
imwrite(cosntstring& filename, InputArray img, const vector<int>¶ms=vecror<int>() );
第一个参数,填需要的写入的文件名,要带上后缀,如“123.jpg”;第二个参数,一般填一个Mat类型的图像数据;第三个参数,表示特定格式保存的参数编码,默认值vecror<int>()
7.创建滑动条:creatTrackbar()函数
creatTrackbar( conststring& trackbarname, conststring& winname, int* value, intcount, TrackbarCallback onChange=0, void* userdata=0)
第一个参数,trackbarname,轨迹条的名字;
第二个参数,winname,窗口的名字,表示轨迹条依附到的窗口;
第三个参数,value,指向整形的指针,表示滑块的位置,创建时滑块的初始位置就是该变量当前的值;
第四个参数,count,表示滑块可以达到的最大位置的值;
第五个参数,onChange,指向回调函数的指针,滑块位置改变,这个函数都进行回调,且这个函数的原型必须为voidXXXX(int, void*),第一个参数是轨迹条的位置,第二个参数是用户数据。若回调是NULL指针,则表示没有回调函数的作用,仅第三个参数value有变化;
第六个参数,userdata,默认值0。
chap4. OpenCV数据结构与基本绘图
1.使用函数clone()或者copyTo() 来复制一幅图像的矩阵
Mat F= A.clone(); Mat G; A.copyTo(G)
2. 矩形的表示:Rect类
Rect类的成员有x,y,width,height,分别为左上角点的坐标和矩形的宽、高。
contains(Point)判断点是否在矩形内;
inside(Rect)判断矩形是否在该矩形内;
tl()返回左上角点坐标; br()返回右下角点坐标
3.颜色空间转换:cvtColor()函数
cvtColor(InputArraysrc, OutputArray dst, int code, int dstCn=0)
第一个参数,输入图像;
第二个参数,输出图像;
第三个参数,颜色空间转换的标识符;
第四个参数,目标图像的通道数,若为0,则表示其取源图像的通道数。
4. 创建空白的Mat图像
MatatomImage=Mat::zeros( 长, 宽, CV_8UC3 );
5.OpenCV默认的图片通道存储顺序是BGR,即蓝绿红,而不是RGB
chap5. core组件进阶
1. 按原始图的参数规格来创建创建效果图
MatsrcImage = imread("1.jpg");
Mat dstImage;
dstImage.create(srcImage.rows,srcImage.cols,srcImage.type());//效果图的大小、类型与原图片相同
2.计时函数 getTickCount()和getTickFrequency()
//记录起始时间
doubletime0 = static_cast<double>(getTickCount());
//计算运行时间并输出
time0= ((double)getTickCount() - time0)/getTickFrequency();
cout<<"\t此方法运行时间为:"<<time0<<"秒"<<endl; //输出运行时间
3.Mat类的公有成员变量cols和rows给出了图像的宽和高
srcImage.rows
srcImage.cols
4.Mat类的成员函数channels()用于返回图像的通道数;
灰度图的通道数为1,彩色图的通道数为3.
5. Mat类提供ptr函数得到图像任意行的首地址
uchar*data = outputImage.ptr<uchar>(i); //获取第i行的首地址
6.访问图像中的像素P100-114
7. 感兴趣区域(ROI,regionof interest)
两种方法定义ROI:
第一种,用表示矩形区域的Rect,它指定矩形的左上角坐标和矩形的长宽
MatimageROI;
imageROI=image(Rect(500,250,logo.cols,logo.rows));
第二种,指定感兴趣行或者列的范围(Range),Range是指从起始索引到终止索引(不包括终止索引)的一连段连续序列。
imageROI=image(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols))
8. MatimageROI=image(Rect(500,250,logo.cols,logo.rows));
Matmask=imread("logo.jpg",0);//加载掩膜,必须为灰度图
logoImage.copyTo(imageROI,mask);//将掩膜复制到ROI
9. 计算数组加权和:addWeighted()函数 (图像线性混合)
voidaddWeighted(InputArray src1, double alpha, InputArray src2, double beta, doublegamma, OutputArray dst, int dtype=-1);
第一个参数,src1,表示要加权的第一个数组;
第二个参数,alpha,表示第一个数组的权重;
第三个参数,src2,第二个数组,需要和第一个数组有相同的尺寸和通道数;
第四个参数,beta,第二个数组的权重;
第五个参数,gamma,一个加到权重和上的标量值;
第六个参数,dst,输出的数组,和输入的两个数组有相同的尺寸和通道数;
第七个参数,dtype,输出阵列的可选深度,默认值-1.
数学公式表达:
dst =src1[I]*alpha + src2[I]*beta + gamma;
10. 通道分离:split()函数
将一个多通道数组分离成几个单通道数组
voidsplit(const Mat&src, Mat*mvbegin);
或者 voidsplit(InputArray m, OutputArrayOfArrays mv);
第一个参数,m或者src,填需要进行分离的多通道数组;
第二个参数,mv,填输出数组或者输出的vector容器
eg.
vector<Mat>channels;
MatimageBlueChannel;
MatimageGreenChannel;
MatimageRedChannel;
srcImage4=imread("dota.jpg");
split(srcImage4,channels);//分离彩色通道,将3通道图像转成3个单通道图像;
imageBlueChannel=channels.at(0);
imageGreenChannel=channels.at(1);
imageRedChannel=channels.at(2);
11. 通道合并:merge()函数,是split()函数的逆向操作
将多个数组合并成一个多通道的数组
voidmerge(InputArrayOfArrays mv,OuoputArray dst);
eg.接着上面
merge(channels,mergeImage);
12. 创建0矩阵的图像
g_srcImage= imread( "1.jpg");
g_dstImage= Mat::zeros( g_srcImage.size(), g_srcImage.type() );
13. 一般的图像处理算子都是一个函数,它接受一个或多个输入图像,并产生输出图像
14.g(i,j)=a*f(i,j)+b;
a称为增益(gain),对比度
b称为偏置(bias),亮度
15. 访问图像的每一个像素,使用这样的语法image.at<Vec3b>(y,x)[c]
y是像素所在的行,x是像素坐在的列,c是B,G,R(对应0,1,2)其中之一;
eg.
// 三个for循环,执行运算 g_dstImage(i,j) = a*g_srcImage(i,j)+ b
for(inty = 0; y < g_srcImage.rows; y++ )
{
for(intx = 0; x < g_srcImage.cols; x++ )
{
for(intc = 0; c < 3; c++ )
{
g_dstImage.at<Vec3b>(y,x)[c] = saturate_cast<uchar>((g_nContrastValue*0.01)*( g_srcImage.at<Vec3b>(y,x)[c])
+ g_nBrightValue );
}
}
}
★运算结果可能会超出像素取值范围(溢出),还可能是非整数(如果是浮点数的话),所以用saturate_cast对结果进行转换,确保它为有效值。
16. //输出一些帮助信息
cout<<endl<<"\t运行成功,请调整滚动条观察图像效果\n\n"
<<"\t按下“q”键时,程序退出\n";
//按下“q”键时,程序退出
while(char(waitKey(1))!=
'q') {}
17. Mat srcImage = imread("1.jpg",0);
//【1】以灰度模式读取原始图像并显示
18. 离散傅立叶变换DFT P135
19.getOptimalDFTSize 返回给定向量尺寸的傅立叶最优尺寸大小
20.copyMakeBorder 扩充图像边界
21.magnitude 计算二维矢量的幅值
22.log 计算每个数组元素绝对值的自然对数
23.normalize 进行矩阵归一化
chap6. 图像处理
1. 平滑处理(smoothing)也称模糊处理(bluring)
用途:减少图像上的噪点、失真、降低图像分辨率。
2. 图像滤波,尽量保留图像细节特征的条件下对目标图像的噪声进行抑制。
3. 信号或者图像,能量大多集中在幅度谱的低频和中频段,在高频段,有用的信息经常被噪声淹没,所以,一个能降低高频成分幅度的滤波器就能够减弱噪声的影响。
4. 平滑滤波是低频增强的空间域滤波技术,目的:一,模糊;二,消除噪音。
5. 线性滤波
方框滤波,均值滤波,高斯滤波
6. 非线性滤波
中值滤波,双边滤波
7.常见的线性滤波
低通滤波器:允许低频率通过;
高通滤波器:允许高频率通过;
带通滤波器:允许一定范围频率通过;
带阻滤波器:阻止一定范围频率通过并允许其他频率通过;
全通滤波器:允许所有频率通过,仅改变相位关系;
陷波滤波器:阻止一个狭窄频率范围通过,一种特殊的带阻滤波器。
8. 滤波:
是将信号中特定波段频率滤除的操作,是抑制和防止干扰的一项重要措施。
9. 滤波可分为低通滤波和高通滤波:
低通就是模糊;
高通就是锐化。
10. 高斯滤波:指用高斯函数作为滤波函数的滤波操作;
11. 高斯模糊:就是高斯低通滤波。
12. 方框滤波BoxFilter() P157
13. 均值滤波是方框滤波归一化(normalized)后的特殊情况
14. 均值滤波的缺陷:不能很好的保护细节,在去噪的同时也破坏了图像的细节部分,图像变的模糊,不能很好地去除噪声点。
15. 均值滤波:blur函数
16. 高斯滤波:GaussianBlur函数
作用:模糊一张图片
17. 中值滤波(Medianfilter),非线性滤波,用点邻域灰度值的中指来代替该像素点的灰度值,在去除脉冲噪声、椒盐噪声的同时又可以保留图像的边缘细节。
函数medianBlur()
18. 双边滤波(Bilateralfilter),非线性滤波,结合图像的空间邻近度和像素值相似度的一种这种处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的,具有简单、非迭代、局部的特点。
函数bilateralFilter()
19. 膨胀(dilate) 腐蚀(erode)
功能:消除噪声;分割独立的图像元素,在图像中连接相邻的元素;寻找图像中的明显的极大值区域或极小值区域;求出图像的梯度。
20. 腐蚀和膨胀是对白色部分(高亮部分)而言的,不是黑色部分。
膨胀,对图像中的高亮部分进行膨胀,效果图拥有比原图更大的高亮区域;
腐蚀,原图中的高亮部分被腐蚀,效果图拥有比原图更小的高亮区域。
21.膨胀,就是求局部最大值的操作;
复试,就是求局部最小值的操作。
22. 函数getStructuringElement返回指定形状和尺寸的结构元素(内核矩阵)
第一个参数,表示内核的形状,三种形状可以选择,
矩形:MORPH_RECT;
交叉形:MORPH_CROSS;
椭圆形:MORPH_ELLIPSE。
第二个参数,内核的尺寸,eg: Size(15,15)
第三个参数,锚点的位置
23. 利用morphologyEx函数进行膨胀
Matelement=getStructuringElement(MORPH_RECT,Size(15,15));
morphologyEx(srcimage,dstimage, MORPH_DILATE, element);
24. 利用morphologyEx函数进行腐蚀
morphologyEx(srcimage,dstimage, MORPH_ERODE, element);
25. 开运算,就是先腐蚀后膨胀的过程
数学表达式:dst=open(src,element)=dilate(erode(src,element))
函数:morphologyEx(src,dst,MORPH_OPEN,element);
它可以用来消除小物体,在纤细点分离物体,且在平滑较大物体的边界的同时不明显改变其面积。
26. 闭运算,先膨胀后腐蚀的过程
数学表达式:dst=clese(src,element)=erode(dilate(src,element))
函数:morphologyEx(image,image, MORPH_CLOSE, element);
它可以排除小型黑洞(黑色区域)。
27. 形态学梯度,是膨胀图与腐蚀图之差
数学表达式:dst=morph-grad(src,element)=dilate(src,element)-erode(src,element)
函数morphologyEx(image,image, MORPH_GRADIENT, element);
28. 顶帽运算,原图像与开运算的结果图之差
数学表达式:dst=tophat(src,element)=src-open(src,element)
函数 morphologyEx(image, image, MORPH_TOPHAT, element);
它得到的效果图突出了比原图轮廓周围更明亮的区域
29. 黑帽运算,闭运算的结果与原图像之差
数学表达式:dst=blacehat(src,element)=close(src,element)-src
函数 morphologyEx(image, image, MORPH_BLACKHAT, element);
它突出了比原图轮廓周围的区域更暗的区域,可以用来分离比邻近点暗一些的斑块,效果图有着非常完美的轮廓。
30. 漫水填充 floodFill
选中和种子点相连的区域,将该区域替换成指定的颜色。
31. 图像金字塔
是图像中多尺度表达的一种,最主要应用于图像的分割,是一种以多分辨率来解释图像的有效但概念简单的结构。
32. 一幅图像的金字塔是一系列以金字塔形状排列,分辨率逐步降低,且来源于同一张原始图的图像集合。通过梯次向下采样获得,直到达到某个终止条件时停止。
33. 金字塔的底部是待处理图像的高分辨率表示,顶部是低分辨率的近似。
34. 层级越高,图像越小,分辨率越低。
35. 两种常见的图像金字塔
★高斯金字塔(Gaussianpyramid),用来向下采样。
★拉普拉斯金字塔(Laplacianpyramid),从金字塔低层图像重建上层未采样图像,预测残差,可以对图像进行最大程度还原,配合高斯金字塔一起使用。
二者区别:
★高斯金字塔用来向下采样图像;
★拉普拉斯金字塔从金字塔底层图像中向上采样,重建一个图像。
36. 尺寸调整:resize()函数
37. 向上采样:pyrUp()函数
作用,向上采样并模糊一张图片,简言之,放大一张图片。
38. 采样:pyrDown()函数
作用,向下采样并模糊一张图片,简言之,缩小一张图片。
39. 固定阈值操作:Threshold()函数 p238
40. 自适应阈值操作:adaptiveThreshold()函数
chap7. 图像变换
1. Mat srcImage1=srcImage.clone();
//使用函数clone()
复制一幅图像的矩阵
2. dstImage.create( srcImage1.size(),srcImage1.type() ); //
【1】创建与src同类型和大小的矩阵(dst)
3. cvtColor( srcImage1, grayImage,
CV_BGR2GRAY ); // 【2】将原图像转换为灰度图像
4. dstImage = Scalar::all(0);//【5】将g_dstImage内的所有元素设置为0
5.Canny边缘检测:Canny()函数 P250
6.sobel算子 P253
7.Laplacian算子 P256
8.convertScaleAbs()函数 用线性变换转换输入数组元素成8位无符号整型
9.scharr滤波器
主要是陪着Sobel算子的运算而存在
10. 霍夫变换(HoughTransform)
它是图像处理中的一种特征提取技术,该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换的结果。
优点:
分割结果Robustness,对数据的不完全或者噪声不是非常敏感。
11. 霍夫变换运用两个坐标空间之间的变换将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化为统计峰值问题。
12. 在使用霍夫变换之前,首先要对图像进行边缘检测处理,即霍夫变换的直接输入只能是边缘的二值图像。
23.OpenCV支持三种不同的霍夫线变换:
标准霍夫变换(StandardHough Transform, SHT);
多尺度霍夫变换(Multi-ScaleHough Transform, MSHT);
累计概率霍夫变换(ProgressiveProbabilistic Hough Transform, PPHT).
24.HoughLines函数用来调用标准霍夫变换(SHT)和多尺度霍夫变换(MSHT)。
25. HoughLinesP函数调用累计概率霍夫变换(PPHT)。PPHT执行效率很高,相比于HoughLines,更倾向于HoughLinesP函数。
26. 霍夫线变换的原理 P268
(1)一条直线在图像二维空间,可由两个变量表示
笛卡尔坐标系:用斜率和截距
极坐标系:用极径和极角
对于霍夫变换采用极坐标系来表示直线。
(2)对于一个点(x,y),每一对的极坐标系的参数,极径和极角,代表一条通过(x,y)的直线。
(3)对于一个定点(x,y),在极坐标下,对极径极角绘制出所有通过(x,y)的直线,得到的是一条正弦曲线。
(4)如果两个不同的点进行上述操作,得到的曲线在平面θ-r相交,则意味着它们通过同一条直线。
(5)一条直线可以通过在平面θ-r寻找交于一点的曲线数量来检测;越多的曲线交于一点,意味着这个交点表示的直线由更多的点组成。一般,可以设置直线上的点的阈值来定义多少条曲线交于一点,这样才认为检测到了一条直线。
(6)霍夫变换要做的就是追踪图像中每个点对应曲线间的角点,若交于一点的曲线数量超过了阈值,则可认为这个角点所代表的参数对(θ,rθ)为原图像中的一条直线。
27. 霍夫圆变换HoughCircles()函数
28. 重映射 remap() 函数
29. 放射变换:wrapAffine()函数
30. 求得仿射变换:
warpMat=getAffineTransform(srcTriangle,dstTriangle);
31. 计算二维旋转变换矩阵:getRotationMatrix2D()函数
32. 直方图均衡化:equalizeHist()函数
chap8. 图像轮廓与图像分割修复
1. 寻找轮廓:findContours()函数
2. 绘制轮廓drawContours()函数
3. 寻找凸包:convexHull()函数
4. 使用多边形将轮廓包围
返回外部矩形边界:boundingRect()函数
寻找最小包围矩形:minAreaRect()函数
寻找最小包围圆形:minEnclosingCircle()函数
用椭圆拟合二维点集:fitEllipse()函数
逼近多边形曲线:approxPolyDP()函数
5. 图像的矩
矩的计算:moments()函数
计算轮廓面积:contourArea()函数
计算轮廓长度:arcLength()函数
6. 分水岭算法:watershed()函数
7. 图像修补:inpaint()函数
chap9. 直方图与匹配
1. 计算直方图:calcHist()函数
2. 找寻最值:minMaxLoc()函数 p348
3. 对比直方图:compareHist()函数
4.MatND类是用于存储直方图的一种数据结构。
5. 计算反向投影:calBackProject()函数
6. 通道复制:mixChannels()函数
7. 模板匹配:matchTemplate()函数
模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术。
模板匹配不是基于直方图的,而是通过在输入图像上滑动图像快,对实际的图像快和输入图像进行匹配的一种匹配方法。
chap10. 角点检测
1. 角点,通常定义为两条边的角点;
更严格的说,角点的局部邻域应该具有两个不同区域的不同方向的边界;
在实际中,大多数角点检测方法检测的是拥有特定特征的图像点,而不仅仅是“角点”;
这些特征点在图像中有具体的坐标,并具有某些数学特征,如,局部最大、最小灰度、某些梯度特征等。
2. 兴趣点(interestpoints),也称为关键点(keypoints)、特征点(featurepoints)
3. 图像特征类型可分为三种:边缘、角点(感兴趣关键点)、斑点(Blobs)(感兴趣区域);
4. 角点:
如果,某一点在任意方向的一个微笑变动都会引起灰度很大的变化,则将其称之为角点!
5. 角点的具体描述:
a.一阶导数(即灰度的梯度)的局部最大所对应的像素点;
b.两条及两条以上边缘的交点;
c.图像中梯度值和梯度方向的变化速率都很高的点;
d.角点处的一阶导数最大,二阶导数为0,它只是了物体边缘变化的不连续的方向。
6. 当前图像处理领域,角点检测算法:
a.基于灰度图像的;
b.基于二值图像的;
c.基于轮廓图像的。
7. 基于灰度图像的角点检测,可分为三种:
a.基于梯度;
b.基于模板;
c.基于模板梯度。
8. 基于模板的方法,主要考虑像素领域点的灰度变化,即图像亮度的变化,将与邻点亮度对比足够大的点定义为角点。
9. 常见的基于模板的角点检测算法有:
a.Kitchen-Rosenfeld角点检测算法
b.Harris角点检测算法
c.KLT角点检测算法
d.SUSAN角点检测算法
10.Harris角点检测是一种直接基于灰度图像的角点提取算法,稳定性高,尤其对L型角点检测精度高。
但,由于采用了高斯滤波,运算速度相对较慢,角点信息有丢失和位置偏移的现象,且角点提取有聚簇现象。
11.Shi-Tomasi算子是Harriss算法的改进。
12. 寻找亚像素角点:cornerSubPix()函数
chap11. 特征检测与匹配
1.SUFT, SpeededUp Robust Features,加速版的具有鲁棒性的特征。
2.SUFT是SIFT(尺度不变特征变换算法)的加速版;一般来说,标准的SUFT算子比SIFT算子快好几倍;且,在多幅图片下具有更好的稳定性。
3. 绘制关键点:drawKeypoints()函数 p401
4.KeyPoint类 p402
5. //【2】使用SURF算子检测关键点
intminHessian = 700;//SURF算法中的hessian阈值
SurfFeatureDetector detector( minHessian );//定义一个SurfFeatureDetector(SURF)特征检测类对象
std::vector<KeyPoint>keyPoint1, keyPoints2;//vector模板类,存放任意类型的动态数组
//【3】调用detect函数检测出SURF特征关键点,保存在vector容器中
detector.detect(srcImage1, keyPoint1 );
detector.detect(srcImage2, keyPoints2 );
//【4】计算描述符(特征向量)
SurfDescriptorExtractor extractor;
Mat descriptors1, descriptors2;
extractor.compute(srcImage1, keyPoint1, descriptors1 );
extractor.compute( srcImage2,keyPoints2, descriptors2 );
6. //【5】使用BruteForce进行匹配
//实例化一个匹配器
BruteForceMatcher<
L2<float>> matcher;
std::vector<
DMatch >matches;
//匹配两幅图中的描述子(descriptors)
matcher.match( descriptors1,descriptors2, matches );
7.
//【6】绘制从两个图像中匹配出的关键点
Mat imgMatches;
drawMatches( srcImage1, keyPoint1,srcImage2, keyPoints2, matches, imgMatches );//进行绘制
8. 绘制匹配点:drawMatches()函数 p405
9.BruteForceMatcher类
用于进行暴力匹配。
10. //【6】存下符合条件的匹配结果(即其距离小于2*min_dist的),使用radiusMatch同样可行
std::vector<
DMatch >good_matches;
for(inti = 0; i < descriptors_1.rows; i++ )
{
if(matches[i].distance < 2*min_dist )
{good_matches.push_back( matches[i]); }
}
11.ORB特征提取 p425
相关文章推荐
- OpenCV3编程入门-读书笔记2-core组件
- 《OpenCV3编程入门》读书笔记--SURF算法
- OpenCV3编程入门-读书笔记3-滤波
- 【读书笔记】建造者模式代码完成与大家分享
- 《Effective Java》读书笔记03-私有构造器
- 《javascript高级程序设计》读书笔记(四)引用类型
- 第五章读书笔记
- [读书笔记] 重构改善既有代码的设计(4)
- 《笑傲测试》读书笔记二---谁能做好测试
- 沟通至上 《高效程序员的45个习惯》读书笔记
- Spring3.x企业应用开发实战-读书笔记 第二章--快速入门
- 《Linux内核设计与实现》第五章读书笔记
- 编写java程序151条建议读书笔记(4)
- 《ERP从内部集成起步》读书笔记——第3章 用信息集成的观念理解MRP到ERP II 3.1MPR—物料需求计划
- 【读书笔记】UNIX进程间通信
- OpenCV学习笔记(五十四)——概述FaceRecognizer人脸识别类contrib
- 【读书笔记】Linux内核设计与实现(第十八章)
- java并发读书笔记(1)
- C++ primer 读书笔记系列——(7)关联容器
- 《Effective Java》读书笔记07--覆盖equals时要遵守通用约定