计算机视觉(OpenCV+TensorFlow)
文章目录
- 计算机视觉(OpenCV+TensorFlow)
- 前言
- 3.图像金字塔
- 3.1 高斯金字塔
- 3.2 拉普拉斯金字塔
- 4.图像轮廓
- 图像边缘和图像轮廓的区别
- 检测图像
- 绘制边缘
- 5.轮廓近似
- 外接矩形
- 外接圆
- 6. 模板匹配
- 6.1 什么是模板匹配
- 总结
前言
本系列文章是OpenCV系列文章的第三篇,仍然跟随上篇内容主要聚焦于图像的一些操作
3.图像金字塔
在通常情况下我们使用大小恒定的图像。但在某些情况下,我们需要使用不同分辨率的同幅图像,例如,在搜索图像中的某些内容比如脸部信息时,并不确定该内容在图像中占据的大小。这种情况下,我们需要创建一组不同的分辨率的相同图像,并在所有图像中搜索该内容。这些不同分辨率的图像被称为图像金字塔(因为当它们堆叠排列时,底部为最高分辨率图像而顶部为最低分辨率图像,看起来像金字塔)
**图像金字塔主要有两种:**
- 高斯金字塔
- 拉普拉斯金字塔
3.1 高斯金字塔
向上采样:在图像金字塔中,越靠下的分辨率越大,所以向上采样指的是从分辨率大的图像中得到分辨率小的图像
方法:
- 将图像与下方的高斯核进行卷积,就是我们前几次一直说的对应位置相乘最后都加在一起从而代替中间像素的那个值
- 将所有偶数行和偶数列去除。
为什么要先卷积再去除呢:
是因为如果直接去除偶数行和偶数列,那势必会导致大量数据的丢失,如果我们先卷积,卷积后的值是它周围的值共同作用的,所以卷积后再去除损失的数据量比直接去除要小很多,但是还是会少一部分数据
向下采样:在图像金字塔中,越靠上的分辨率越大,所以向下采样指的是从分辨率小的图像中得到分辨率大的图像
**方法: **
我们看第一步放大后,有近四分之三的数据都是0,第二部卷积后,实际上是把不是0的数据给分配了一部分给0
那实际上,就是把原有的数据给往四周分散了。那在放大的时候,原来的数据给了其他的,那对应自身来说不也就是丢失了一部分数据。
# 引入图片
img=cv2.imread("AM.png")
cv_show(img,'img')
print (img.shape)
# 向上采样
up=cv2.pyrUp(img)
cv_show(up,'up')
print (up.shape)
# 向下采样
down=cv2.pyrDown(img)
cv_show(down,'down')
print (down.shape)
**而为了让数据尽量的少丢失点,我们又有了拉普拉斯金字塔。 **
3.2 拉普拉斯金字塔
https://blog.csdn.net/ftimes/article/details/106731558
** 在高斯金字塔中,我们提到了上采样和下采样。但是无论是上采样还是下采样都会丢失像素值,所以这两种操作并不是可逆的。也就是说,对一幅图先进行上采样再进行下采样,是无法恢复到原始状态。同样,先下采样再上采用也无法恢复到原始状态**
因此我们引入了拉普拉斯金字塔(The Laplacian pramid)
简而言之,拉普拉斯金字塔的第 i 层,是由【高斯金字塔的第 i 层】 与 【高斯金字塔中的第 i+1 层的向上采样结果】之差。
简单来说,拉普拉斯金字塔是一个 高斯差值金字塔,下面这个图就是一个拉普拉斯每一层的流程
下面这个图简化了上图的流程,我们从后往前看,G3向上取样后,G2减去它就是 L2。
而 G3加上L2就是G2。因为L2=G2-G3向上
up=cv2.pyrUp(img)
up_down=cv2.pyrDown(up)
cv_show(img-up_down,'img-up_down')
4.图像轮廓
图像边缘和图像轮廓的区别
** 前面我们在图像形态学操作里,用cv2.morphologyEx()这个函数实现图像梯度的提取,用膨胀图像-腐蚀图像,获取一个图像中前景图像的边缘。还有我们的礼帽和黑帽一定程度也能提取图像的边缘信息。依旧我们在图像梯度中详细讲过的 sobel算子,scharr算子,laplasian算子,canny边缘检测,这些都是检测图像中边缘线条的。**
** 而本章讲的是图像轮廓,图像轮廓和图像边缘不少一回事,图像边缘不少图像轮廓!!!!图像边缘是图像中的线条,这些线条是不连续的,零散的线段,只要是由梯度,把由梯度的像素点提取出来就可以了,这是边缘检测的操作手法。而图像轮廓首先要是一个整体的,就是将边缘连接起来形成一个整体,这才叫轮廓。**
** 边缘检测主要是通过一些手段检测数字图像中明暗变化剧烈(即梯度变化比较大)像素点,偏向于图像中像素点的变化。如Canny边缘检测,结果通常保存在和原图片一样尺寸和类型的边缘图中。轮廓检测指检测图像中的对象边距,更偏向于关注上层语义对象,主要用来分析物体的形态,比如物体的周长和面积等。可以说 边缘包括轮廓。**
** 边缘主要是作为图像的特征使用,比如可以用边缘特征可以区分脸和手,而轮廓则是一个很好的图像目标的外部特征,这种特征对于我们进行图像分析,目标识别和理解等更深层次的处理都有很重要的意义**
检测图像
在OpenCV中,我们用 image, contours, hierarchy = **cv2.findContours(img,mode,method) **这个函数来得到轮廓
- img:要做轮廓检测的图像,必须是8位单通道二值图像。所以,一般情况下我们都是将图像处理位二值图像后再将其作为参数传入。在很多情况下,我们是预先对图像进行阈值分割或者边缘检测处理(比如经过Canny,拉普拉斯等边缘检测算子处理过的二值图像),在得到满意的二值图像后再作为参数传入使用,这样效果会更好。
- mode:轮廓检索模式。均可了轮廓的提取方式:
- cv2.RETR_EXTERNAL:只检测最外面的轮廓
- cv2.RETR_LIST:减少所有的轮廓,并将其保存到一条链表当中,对检测到的轮廓不建立等级关系
- cv2.RETR_CCOMP:检索所有轮廓并将它们组织成两级层次结构。顶层是各部分的外部边界,第二层是空洞的边界。
- cv2.RETR_TREE:检索所有轮廓,并建立一个等级树结构的轮廓,就是重构嵌套轮廓的整个层次
- 说明:一般情况下,我们只用第4种模式,因为第4种模式是检测所有的轮廓,并且把这些轮廓按层次保存成一个树结构,后面如果我们有需要直接调用即可。
- method:轮廓逼近方法,就是如何表达轮廓,意思就是我是用线表示轮廓呢?还是简单的用2个点就表示一条线的轮廓:
- cv2.CHAIN_APPROX_NONE:以 Freeman 链码的方式输出轮廓,意思就是我存储了所有的轮廓点,就是相连两个点的像素位置差不超过 1 ,我可以用完整的线条来表示轮廓,就是我可以画出一个完整的轮廓。
- cv2.CHAIN_APPROX_SIMPLE:压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标。比如一个矩形只用4个点就可以表示。同理如果是一个多边形,我们就输出这个多边形的订单序列
函数返回值:
- image:与参数img的尺寸一致的。后面高版本的opencv已经不返回这个对象
- contours:是返回的轮廓。这个轮廓是一个数组
- hierachy:是轮廓的层次信息,就是mode参数决定的返回的轮廓数据的组织结构
- 说明:在opencv中,我们都是从黑色背景种查找白色对象,因此,对象必须是白色的,背景必须是黑色的
使用轮廓检测函数 cv2.findContours()要注意的点:
- 我们检测一张彩图的轮廓是,首先我们要把彩图转换为灰度图像,然后我们用阈值函数把灰度图像处理成二值图像,此时才能作为参数 img 传入到函数中
- 我们检测轮廓的时候,一张图片可能有多个轮廓,也可能有一个轮廓礼貌套几个轮廓(就是空洞)所以我们要明确我们想要检测几级轮廓。
- 当我们只想检测最外面的一层轮廓时,参数 mode=cv2.RETR_EXTERNAL
- 当我们像检测所有的轮廓(就是轮廓里面套轮廓,可以嵌套多层)时,参数mode可以选择其他三种
- cv2.RETR_LIST是把所用的轮廓放到一起,不区分轮廓之间的等级关系
- cv2.RETR_CCOMP是把所有的轮廓分2级
- cv2.RETR_TREE是建立一个树结构的层次关系
- 当我们轮廓检测完毕后,不管是检测一个轮廓还是检测所有的轮廓,检测所有轮廓不管是保存轮廓之间的等级关系还是不保存,我们的轮廓本身数据可以有两种方式存储,、
- cv2.CHAIN_APPROX_NONE:就是保存轮廓的所有像素点,此时返回的 contours可视化处理后就是轮廓线。
- cv2.CHAIN_APPROX_SIMPLE:就是表示我们轮廓数据不是连续的像素点而是轮廓的顶点序列,此时我们可视化 contours时就是一些顶点,就是轮廓的顶点,不是线。
- countours的属性:
- len(contours)返回的就是我们检测到了几个轮廓
- len(contours[i])返回第 i 个轮廓长度,就是它有多少个像素点
- contours[i].shape返回的就是轮廓内点的形状,比如(4,1,2)就表示轮廓 i 有4个轮廓点,每个点是1行两列
- [[79,270]] [[79,383]]** [[195,383]]** [[195, 270]] 这其实就是一个方框轮廓的4个点的坐标值。**
- hierarchy是我们检测到的轮廓的等级关系的数据,这个数据可以反映我们的轮廓之间是如何连接的。
绘制边缘
轮廓绘制函数:cv2.drawContours(img, contours, contourIdx, color [ thickness, lineType, hierarchy, maxLevel, offset] )
- img:待绘制轮廓的图像
- contours:需要绘制的轮廓,这个参数就是 findContours()的输出
- contourldx:需要绘制的轮廓的索引号,如果 contourldx=-1,表示绘制全部轮廓;如果这个参数是零或者正整数,表示要绘制的轮廓是对应的索引号的轮廓
- color:绘制的颜色,用 BGR 表示
- thickness:表示轮廓的粗细,如果 thickness=-1则表示要绘制实心轮廓
- lineType:轮廓的线条形状
- hierarchy:cv2.findContours()返回的层次信息。
- maxLevel:要绘制的轮廓的偏移到不同的位置展示出来
注意:由于该函数是在 img 的基础上绘制的,不会再重新生成一个带轮廓的新对象,所有这个函数是在原图中绘制的。所以我们要保存原图
轮廓检测步骤小结:
- 当我们拿到的原图是一张彩色图的时候,第一步我们要把彩色图变成灰度图
- 把灰度图用阈值函数处理成二值图像
- 检测轮廓
- 绘制轮廓
# 导入图片
img = cv2.imread('contours.png')
# 彩色图转灰色图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 灰色图转二值图
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
cv_show(thresh,'thresh')
# 检测轮廓
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# 画轮廓
draw_img = img.copy()
res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2)
cv_show(res,'res')
5.轮廓近似
我们看下面这个图片,它不是一个规则的图形,是一个不规则的图形,如果要精确的描述轮廓的话就是第三幅图
如果像要近似的表示的话,就是第二幅图,用一个矩形就可以表示
https://blog.csdn.net/SSJJRRRR/article/details/108478898
那OpenCV的 approxPolyDP 函数就可以实现这个功能。
approxPolyDP函数使用了 Douglas-Peucker算法:
- 先从轮廓中找出两个最远的点,将两点相连,即 b-c ;
- 在原来的轮廓上查找一个里线段距离最远的点,将该点加入逼近后的新轮廓中,即 c-d ;
- 然后重复前面的算法,不断迭代,将最远的点添加进来,直到所有的点到多边形的最短距离小于指定的精度
在OpenCV中 cv2.approxPolyDP(cnt,epsilon,True) 就是用来得到近似轮廓
cnt 就是 findContours函数得到的 contours
epsilon就是精度
最后一个布尔值:如果为true,则闭合近似曲线(其第一个和最后一个顶点为连接的);否则,不闭合。
# 导入图片
img = cv2.imread('contours2.png')
# 转成灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 转为二值图
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 检测轮廓
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]
# 画出轮廓
draw_img = img.copy()
res = cv2.drawContours(draw_img, [cnt], -1, (0, 0, 255), 2)
cv_show(res,'res')
# arcLength轮廓的周长,epsilon是精度
epsilon = 0.15*cv2.arcLength(cnt,True)
# 得到近似轮廓
approx = cv2.approxPolyDP(cnt,epsilon,True)
# 画出轮廓
draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)
cv_show(res,'res')
外接矩形
x,y,w,h=boundingRect(cnt)
cnt 就是一个轮廓,x,y是矩形的左上角坐标,而(w,h)为矩形的宽度和高度
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
img:画轮廓的图片
(x,y):矩形的左上角的坐标
(x+w,y+h):矩形的右下角的坐标
(0,255,0):颜色 BGR
2:线条宽度
# 引入坐标
img = cv2.imread('contours.png')
# 转为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 转为二值图
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 检测轮廓
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]
# 找到外接矩形的信息
x,y,w,h = cv2.boundingRect(cnt)
# 画矩形
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv_show(img,'img')
外接圆
(x,y),radius = cv2.minEnclosingCircle(cnt)
cnt:就是一个轮廓
(x,y):圆心坐标
radius:半径
img = cv2.circle(img,center,radius,(0,255,0),2)
参数基本上与外接矩形都差不多
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
img = cv2.circle(img,center,radius,(0,255,0),2)
cv_show(img,'img')
6. 模板匹配
6.1 什么是模板匹配
https://blog.csdn.net/m0_37579176/article/details/116950903
模板匹配可以看作是对象检测的一种非常基本的形式。使用模板匹配,我们可以使用包含要检测对象的”模板“来检测输入图像中的对象。
也就是说,我们需要两个图像来应用模板匹配:
- 源图像:这是我们希望在其中找到的与模板匹配的图像
- 模板图像:我们要搜索的图像
为了在源图像中找到模板图像,我们在源图像中从左到右和从上到下依次滑动模板:
应用模板匹配,就像在源图像上从左到右,从上到下滑动模板,在每一个位置都计算一个指标以表明这个位置处两个图像块之间匹配程度的高低
在每个(x,y)位置,都会计算一个度量来表示匹配的“好”或“坏”。通常,我们使用归一化的相关系数来确定两个图像块之间像素强度有多“相似”
相关系数有很多计算方式。
‘
对于模板 T 在源图像 I 上的每个位置,取两者重合部分的图像块,计算相似度度量结果,存储在结果矩阵 R 中。源图像中的每个(x,y)坐标在结果矩阵 R 中包含一个条目,除非模板越界
下图就是结果矩阵与源图像重叠后的图像
在这里,我们可以可视化叠加在原始图像上的结果矩阵R。注意R与原始模板大小不相同。这是因为整个模板必须在源图像的内部滑动,得到等大的两个图像块,才能计算相关性。如果模板超出了源的边界,我们将不计算相似性度量。
结果矩阵中 R 最亮的位置表示最佳匹配位置,而暗区表示该点源图像和模板图像之间的相关性很小
当模板图像中的水杯,与源图像中的水杯,两者完全重合的时候,模板图像左上角所在的源图像位置,存储的是模板与源图像相似的的最大值。
但是我们需要确保要检测的模板与源图像中检测的对象几乎完全相同。即使外观很小的偏差也会极大地影响匹配的结果。
在OpenCV中我们可以使用 cv2.matchTemplate()进行模板匹配,这个函数有三个参数:
- 输入图像:包含我们要检测的对象的图像
- 模板图像:对象的图像
- 模板匹配方法
- TM_SQDIFF:计算平方不同,计算出来的值越小,越相关
- TM_CCORR:计算相关性,计算出来的值越大,越相关
- TM_CCOEFF:计算相关系数,计算出来的值越大,越相关
- TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关
- TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关
- TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关
另外值得注意的是,如果您只想检测模板图像上的特定区域,则可以为模板图像提供一个掩膜,如下所示:
**result = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED, mask) **
掩膜,即为模板图像上感兴趣的区域,用于忽略模板图像上无用的干扰的特征,即不属于检测目标的干扰特征。对于模板上你不希望被搜索的区域,掩膜值应该设置为0。对于模板图像上您要进行搜索的区域,掩膜值应该设置为255。掩膜与模板图像具有相同的维度,并且每个元素的类型也需要一致。
# 引入源图像
img = cv2.imread('lena.jpg', 0)
# 引入模板图像
template = cv2.imread('face.jpg', 0)
# 模板匹配
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
# 得到最大的像素点和最小的像素点的值和坐标
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# 根据方法选择最大值或最小值的坐标
# 如果是平方差匹配TM_SQDIFF或归一化平方差匹配TM_SQDIFF_NORMED,取最小值
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:top_left = min_loc
else:top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
# 画矩形
cv2.rectangle(img2, top_left, bottom_right, 255, 2)
总结
这次紧接者上篇内容再一次讲解了一些有关图像的操作,预计还需要一期把图像操作讲个差不多后,进入实战环节,将上述图像内容,通过实战进行一个强化训练
我是Mayphry,从一点点到亿点点,我们下次再见