图像金字塔是图像多尺度表达的一种,是一种以多分辨率来解释图像的有效但概念简单的结构。一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。
在图像处理学科里,图像金字塔被广泛的用于图像融合,图像金字塔融合原理可用下图直白的表示:
Gaussian pyramid: 通常用来进行向下采样(缩小)图像(zoom out)
原理:
首先将原图像作为最底层图像G0(高斯金字塔的第0层),
1.利用高斯核(5*5)对其进行卷积,
2.然后对卷积后的图像进行下采样(去除偶数行和列),
得到上一层图像G1,将此图像作为输入,重复卷积和下采样操作得到更上一层图像,反复迭代多次,形成一个金字塔形的图像数据结构,即高斯金字塔。==
Gaussian pyramid: 向上采样(放大)图像(zoom in)
原理:
首先将图像在,
1.每个方向扩大为原来的两倍,新增的行和列以0填充,
2.再使用和先前同样的内核(乘以4)与放大后的图像卷积,
获得 “新增像素”的近似值。
function
def pyrUp(src, dst=None, dstsize=None, borderType=None):
对图像进行采样然后使其模糊。
默认情况下,输出图像的大小计算为:
S i z e ( s r c . c o l s 2 , s r c . r o w s 2 ) Size(\frac{src.cols}{2}, \frac{src.rows}{2}) Size(2src.cols,2src.rows)
但无论如何,应满足以下条件:
∣ d s t s i z e . w i d t h − s r c . c o l s × 2 ∣ ≤ ( d s t s i z e . w i d t h m o d 2 ) |dstsize.width−src.cols×2|≤(dstsize.width \mod 2) ∣dstsize.width−src.cols×2∣≤(dstsize.widthmod2)
∣ d s t s i z e . h e i g h t − s r c . r o w s × 2 ∣ ≤ ( d s t s i z e . h e i g h t m o d 2 ) |dstsize.height−src.rows×2|≤(dstsize.height \mod 2) ∣dstsize.height−src.rows×2∣≤(dstsize.heightmod2)
该函数执行高斯金字塔结构的上采样步骤,尽管它实际上可以用于构造拉普拉斯金字塔。
首先,它通过注入偶数零行和列来对源图像进行上采样,
然后将结果与pyrDown中的相同内核进行卷积乘以4。
参数
src 输入图像。
dst 输出与src相同大小和类型的图像。
dstsize 输出图像的大小。
borderType 像素外推方法,请参阅BorderTypes(仅支持BORDER_DEFAULT)
代码实现:
#导入工具包
import matplotlib.pyplot as plt
import cv2
#定义一个函数,用来输出图像
def cv_show(name,img):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
#读取一张图片
img = cv2.imread('lena.jpg')
cv_show('img',img)
print(img.shape)
Systemout:(263, 263, 3)
#向上采样
up = cv2.pyrUp(img)
cv_show('up',up)
print(up.shape)
Systemout:(526, 526, 3)
#向下采样
down = cv2.pyrDown(img)
cv_show('down',down)
print(down.shape)
Systemout:(132, 132, 3)
#向上采样两次
up2 = cv2.pyrUp(up)
cv_show('up2',up2)
print(up2.shape)
Systemout:(1052, 1052, 3)
#先向上采样,再向下采样分析其变化
up = cv2.pyrUp(img)
down = cv2.pyrDown(up)
print(down.shape)
res = np.hstack((img,down))
cv_show('up2down',res)
向上采样后,再向下采样并不能恢复到原图的像素,因为在向上采样后会丢弃数据,再进行向下采样会用0来填充,进而会使得图像,变得模糊。
Laplacian pyramid: 通常用来进行向上采样图像(zoom in)
高斯金字塔的上采样和下采样是非线性处理,是不可逆的有损处理,因此,如果下采样后的图像想还原回原来的尺寸的话会丢失很多信息,使图片变得模糊,为了解决这个问题,需要提前保存因下采样而造成的缺失信息,拉普拉斯金字塔可以近似地做到这一点。
拉普拉斯金字塔实际上是通过计算图片先下采样再上采样后的结果和原图片的残差来保存缺失信息的,公式为:
L i = G i − U P ( G i + 1 ) × g 5 × 5 L_i = G_i - UP(G_{i+1}) ×g_{5×5} Li=Gi−UP(Gi+1)×g5×5
因此,我们可以直接用OpenCV进行拉普拉斯运算
L i = G i − P y r U p ( P y r D o w n ( G i ) ) L_i=G_i-PyrUp(PyrDown(G_i)) Li=Gi−PyrUp(PyrDown(Gi))
原理:
用高斯金字塔的每一层图像减去其上一层图像上采样并高斯卷积之后的预测图像,得到一系列的差值图像即为LP分解图像。
步骤:
1.低通滤波
2.缩小尺寸
3.放大尺寸
4.图像相减
代码实现:
# import module
import cv2
import numpy as np
#cv_show
def cv_show(name,img):
cv2.imshow(name,img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# import cat image
img = cv2.imread("cat.jpg")
cv_show('cat',img)
# pyrDown
down = cv2.pyrDown(img)
# pyrUp
down_up = cv2.pyrUp(down)
# L_i=G_i - down_updown
down_up = cv2.resize(down_up,(img.shape[1],img.shape[0]))
L_1 = img - down_up
#show the image
cv_show('L_i',L_1)
结论:
拉普拉斯金字塔更像是高斯向下采样的一个逆运算,或者说是,保存了高斯金字塔向下采样过程中丢失的那部分信息,以便于,向上采样时的完整性。
图像的轮廓可以简单地解释为连接所有连续点(沿着边界),具有相同颜色或强度的曲线。
轮廓是形状分析和物体检测和识别的有用工具:
function
def findContours(image, mode, method, contours=None, hierarchy=None, offset=None):
在二进制图像中查找轮廓。
该函数使用Satoshi Suzuki等人提出的算法,从二进制图像中检索轮廓, 轮廓是形状分析和物体检测和识别的有用工具。
参数
image 源图像,一个8位的单通道图像。在源图中,非0像素将会被视为1,而0像素保持为0,因此此图像被视为一个二值化的图像。
可以使用opencv中的compare函数,inRange函数,threshold函数,adaptiveThreshold函数,Canny函数等等,来从灰度或者彩色图像中
创建二进制图像。
如果在findContours函数中设置mode为RETR_CCOMP或者RETR_FLOODFILL,则输入也可以是标签为(CV_32SC1)的32位整数图像。
contours 检测到的轮廓信息,每个轮廓都存储为点向量(例如std :: vector >)。
hierarchy 可选输出向量(例如std :: vector ),包含有关图像拓扑的信息。
它具有与轮廓数量一样多的元素。 对于每个第i个轮廓轮廓[i],元素层次[i] [0],层次[i] [1],层次[i] [2]和层次[i] [3]被设置为0,
基于相同等级的下一轮和前轮廓的轮廓,第一轮廓和父轮廓的基础索引。
如果轮廓i没有下一个,前一个,父级或嵌套轮廓,则层次结构[i]的相应元素将为负数。
mode 轮廓检索模式,如下图所示:
method 轮廓近似方法,如下图所示:
offset 每个轮廓点移动的可选偏移量。 如果从图像ROI中提取轮廓然后应在整个图像上下文中分析轮廓,则这个方法非常有用。
function
def drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, \
hierarchy=None, maxLevel=None, offset=None):
绘制轮廓轮廓或填充轮廓。
如果?????????≥0,则该函数在图像中绘制轮廓轮廓,或者如果?????????<0,则填充由轮廓限定的区域。
下面的示例显示了如何从二进制映像中检索连接的组件并标记它们:
参数
image 目标图像
contours 所有输入轮廓,每个轮廓都存储为点向量。
contourIdx 指向要绘制的轮廓对象下标, 如果是负数,则绘制所有轮廓。
color 轮廓的颜色。
thickness 绘制轮廓的线条粗细。 如果它是负的(例如,厚度= CV_FILLED),则绘制轮廓内部。
lineType 轮廓样式。
LineTypes
Type | format |
---|---|
FILLED | 填充 |
LINE_4 | 4连线 |
LINE_8 | 8连线 |
LINE_AA | 抗锯齿线 |
hierarchy 有关层次结构的可选信。只有在想要绘制一些轮廓时才需要它(参见maxLevel)。
maxLevel 绘制轮廓的最大级别,如果为0,则仅绘制指定的轮廓,如果为1,则该函数绘制轮廓和所有嵌套轮廓。
如果为2,则该函数绘制轮廓,所有嵌套轮廓,所有嵌套到嵌套轮廓等等,仅当有可用的层次结构时才考虑此参数。
offset 可选的轮廓移位参数。 按指定的??????=(dx,dy)移动所有绘制的轮廓。
function
def boundingRect(array):
计算点集的右上边界矩形,该函数计算并返回指定点集的最小右上边界矩形。
参数
points 输入2D点集,存储在std :: vector或Mat中。
function
def rectangle(img, pt1, pt2, color, thickness=None, lineType=None, shift=None):
绘制一个简单,厚实或填充右上方的矩形。rectangle函数绘制矩形轮廓或填充矩形,其两个对角是pt1和pt2。
参数
img 图像
pt1 矩形的对角点。
pt2 矩形的对角点与pt1相对。
color 矩形颜色或亮度(灰度图像)。
thickness 构成矩形的线条的粗细。 负值(如CV_FILLED)表示函数必须绘制填充矩形。
lineType 线的类型。
shift 点坐标中的小数位数。
function
def contourArea(contour, oriented=None):
计算轮廓区域。
该功能计算轮廓区域。与时刻类似,使用绿色公式计算面积。因此,如果使用drawContours或fillPoly绘制轮廓,则返回的区域和非零像素的数量
可以不同,此外,对于具有自交叉的轮廓,该函数肯定会给出错误的结果。
参数
contour 输入2D点(轮廓顶点)的矢量,存储在std :: vector或Mat中。
oriented 面向领域的旗帜。 如果为true,则该函数返回有符号区域值,具体取决于轮廓方向(顺时针或逆时针)。
使用此功能,您可以通过拍摄区域的符号来确定轮廓的方向。 默认情况下,参数为false,表示返回绝对值。
function
def minEnclosingCircle(points):
查找包含2D点集的最小区域的圆。
该函数使用迭代算法找到2D点集的最小包围圆。
参数
points 输入2D点的矢量,存储在std :: vector <>或Mat中
center 圆的输出中心。
radius 圆的输出半径。
function
def circle(img, center, radius, color, thickness=None, lineType=None, shift=None):
画一个圆圈,函数圆绘制一个具有给定中心和半径的简单或实心圆。
参数
img 绘制圆的图像。
center 圆心。
radius 圆的半径。
color 圆形颜色。
thickness 圆形轮廓的厚度,如果是正的。负厚度意味着要绘制实心圆。
lineType 圆边界的类型。
shift 中心坐标和半径值中的小数位数。
代码实现:
#图像的预处理
#读取一张图片
img = cv2.imread('area.png')
#转化成灰度图
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv_show('gray',gray)
#图像二值化
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_1 = img.copy() #绘制轮廓会把轮廓直接画在图像上,所以要用到copy()函数
draw_img_2 = img.copy() #绘制轮廓会把轮廓直接画在图像上,所以要用到copy()函数
res_1 = cv2.drawContours(draw_img_1,contours,-1,(0,255,0),1) #参数:源图像,轮廓信息,轮廓对象,画笔颜色,轮廓粗细
res_2 = cv2.drawContours(draw_img_2,contours,-1,(0,255,0),2) #参数:源图像,轮廓信息,轮廓对象,画笔颜色,轮廓粗细
res = np.hstack((res_1,res_2))
cv_show('border1 vs border2',res)
#轮廓特征
cnt = contours[0] #第0个轮廓信息
#计算面积
print(cv2.contourArea(cnt))
#计算周长
print(cv2.arcLength(cnt,True))
Systemout:106153.5
Systemout:2254.305691599846
#轮廓近似
#设置不同的阈值,会有不一样的近似结果
epsilon_1 = 0.1*cv2.arcLength(cnt,True)
epsilon_2 = 0.01*cv2.arcLength(cnt,True)
epsilon_3 = 0.001*cv2.arcLength(cnt,True)
approx_1 = cv2.approxPolyDP(cnt,epsilon_1,True)
approx_2 = cv2.approxPolyDP(cnt,epsilon_2,True)
approx_3 = cv2.approxPolyDP(cnt,epsilon_3,True)
res_1 = cv2.drawContours(img.copy(),[approx_1],-1,(0,0,255),2)
res_2 = cv2.drawContours(img.copy(),[approx_2],-1,(0,0,255),2)
res_3 = cv2.drawContours(img.copy(),[approx_3],-1,(0,0,255),2)
res = np.hstack((res_1,res_2,res_3))
cv_show('Epl_0.1 Vs Epl_0.01 Vs Epl_0.001',res)
#读取图像
img = cv2.imread('pictures.png')
#显示
cv_show('img',img)
#灰度化
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[1]
#绘制轮廓
draw_image = cv2.drawContours(img.copy(),[cnt],-1,(0,0,255),1)
#显示图像
cv_show('draw_image',draw_image)
#边界矩形
x,y,w,h = cv2.boundingRect(cnt)
img1 = cv2.rectangle(img.copy(),(x,y),(x+w,y+h),(0,255,0),1)
cv_show("img1",img1)
#轮廓面积与外接矩形比
area = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area
print("轮廓面积与边界矩形比:",extent)
Systemout:轮廓面积与边界矩形比: 0.3975231612781244
#外接圆
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
img2 =cv2.circle(img.copy(),center,radius,(0,255,0),2)
cv_show('img2',img2)
链接:
参考文档.
什么是模板匹配?
模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术。
原理:
我们需要2幅图像:
原图像 (I): 在这幅图像里,我们希望找到一块和模板匹配的区域。
模板 (T): 将和原图像比照的图像块。
我们的目标是检测最匹配的区域:
为了确定匹配区域, 我们不得不滑动模板图像和原图像进行 比较 :
通过 滑动, 我们的意思是图像块一次移动一个像素 (从左往右,从上往下). 在每一个位置, 都进行一次度量计算来表明它是 “好” 或 “坏” 地与那个位置匹配 (或者说块图像和原图像的特定区域有多么相似).
对于 T T T 覆盖在 I I I 上的每个位置,你把度量值 保存 到 结果图像矩阵 R R R 中. 在 R R R 中的每个位置 ( x , y ) (x,y) (x,y) 都包含匹配度量值:
上图就是 TM_CCORR_NORMED 方法处理后的结果图像 R . 最白的位置代表最高的匹配. 正如您所见, 红色椭圆框住的位置很可能是结果图像矩阵中的最大数值, 所以这个区域 (以这个点为顶点,长宽和模板图像一样大小的矩阵) 被认为是匹配的。
在openCV中的matchTemplate()函数中有以下几种模板匹配算法:
Name | Formula | 程度方向 |
---|---|---|
平方差匹配 method=CV_TM_SQDIFF | ||
标准平方差匹配 method=CV_TM_SQDIFF_NORMED | ||
相关匹配 method=CV_TM_CCORR | ||
标准相关匹配 method=CV_TM_CCORR_NORMED | ||
相关匹配 method=CV_TM_CCOEFF | ||
标准相关匹配 method=CV_TM_CCOEFF_NORMED |
Name | 程度方向 |
---|---|
平方差匹配 method=CV_TM_SQDIFF | 最好匹配为0,匹配越差,匹配值越大 |
标准平方差匹配 method=CV_TM_SQDIFF_NORMED | 最好匹配为0,匹配越差,匹配值越大,做归一化处理 |
相关匹配 method=CV_TM_CCORR | 较大的数表示匹配程度较高,0标识最坏的匹配效果 |
标准相关匹配 method=CV_TM_CCORR_NORMED | 较大的数表示匹配程度较高,0标识最坏的匹配效果,进行归一化处理 |
相关匹配 method=CV_TM_CCOEFF | 将模版对其均值的相对值与图像对其均值的相关值进行匹配,1表示完美匹配,-1表示糟糕的匹配,0表示没有任何相关性(随机序列) |
标准相关匹配 method=CV_TM_CCOEFF_NORMED | 将模版对其均值的相对值与图像对其均值的相关值进行匹配,1表示完美匹配,-1表示糟糕的匹配,0表示没有任何相关性(随机序列),做归一化处理 |
function
def matchTemplate(image, templ, method, result=None, mask=None):
将模板与重叠的图像区域进行比较。
在函数完成比较后,可以使用minMaxLoc函数找到最佳匹配作为全局最小值(使用TM_SQDIFF时)或最大值(使用TM_CCORR或TM_CCOEFF时)。
在彩色图像的情况下,分子中的模板求和以及分母中的每个和,在所有通道上完成,并且对于每个通道使用单独的平均值。
也就是说,该功能可以采用颜色模板和彩色图像,结果仍然是单通道图像,更容易分析。
参数
image 正在运行搜索的图像,它必须是8位或32位浮点。
templ 搜索模板,它必须不大于源图像并具有相同的数据类型。
result Map of comparison results. It must be single-channel 32-bit floating-point.
If image is W×Hw×h(W−w+1)×(H−h+1)
method 指定比较方法的参数,详见上面6种匹配方法。
mask 搜索模板的掩码,它必须与templ具有相同的数据类型和大小,默认情况下不设置。
function
def minMaxLoc(src, mask=None):
查找数组中的全局最小值和最大值。
函数cv :: minMaxLoc查找最小和最大元素值及其位置。在整个数组中搜索极值,或者如果掩码不是空数组,则在指定的数组区域中搜索极值。
该功能不适用于多通道阵列,如果需要在所有通道中找到最小或最大元素,请首先使用Mat :: reshape将数组重新解释为单通道。
或者可以使用extractImageCOI或mixChannels或split来提取特定通道。
参数
src 输入单通道阵列。
minVal 指向返回的最小值的指针; 如果不需要,则使用NULL。
maxVal 指向返回的最大值的指针; 如果不需要,则使用NULL。
minLoc 指向返回的最小位置的指针(在2D情况下); 如果不需要,则使用NULL。
maxLoc 指向返回的最大位置的指针(在2D情况下); 如果不需要,则使用NULL。
mask 用于选择子数组的可选掩码。
#读取图像 cv2.IMREAD_GRAYSCALE
img = cv2.imread('lena.jpg',0)
cv_show('img',img)
#读取模板
template = cv2.imread("lena_head.png",0)
cv_show('template',template)
#赋值宽高
h1,w1 = img.shape[:2]
h2,w2 = template.shape[:2]
#输出宽高
print(img.shape)
print(template.shape)
Systemout:(263, 263)
Systemout:(98, 78)
#六种匹配方法
method = {'cv2.TM_CCOEFF','cv2.TM_CCOEFF_NORMED','cv2.TM_CCORR', \
'cv2.TM_CCORR_NORMED','cv2.TM_SQDIFF','cv2.TM_SQDIFF_NORMED'}
res = cv2.matchTemplate(img,template,cv2.TM_SQDIFF)
print(res.shape)
print("(%d, %d)"%(h1-h2+1,w1-w2+1)) #这里为什么shape会加1呢?
Systemout:(166, 186)
Systemout:(166, 186)
min_val,max_val,min_loc,max_loc = cv2.minMaxLoc(res)
print(min_val)
print(max_val)
print(min_loc)
print(max_loc)
Systemout:3928.0
Systemout:60489980.0
Systemout:(108, 101)
Systemout:(158, 56)
#遍历显示所有匹配结果
for meth in method:
#复制一张图像
img_copy = img.copy()
#匹配方法的真值
method = eval(meth)
#模板匹配
res = cv2.matchTemplate(img,template,method)
#得到匹配度最高的坐标
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]+w2,top_left[1]+h2)
#画矩形
cv2.rectangle(img_copy,top_left,bottom_right,255,2)
cv2.imshow('a',img_copy)
cv2.imshow('b',cv2.resize(res,img_copy.shape))
cv2.waitKey(0)
cv2.destroyAllWindows()
#多模板匹配
img_rgb = cv2.imread('Kv.png')
cv_show('img_rgb',img_rgb)
img_gray = cv2.cvtColor(img_rgb,cv2.COLOR_BGR2GRAY)
cv_show('img_gray',img_gray)
template = cv2.imread('Kv_template.png',0)
cv_show('template',template)
h,w = template.shape[:2]
#匹配模板
res = cv2.matchTemplate(img_gray,template,cv2.TM_CCORR_NORMED)
cv_show('res',res)
#对应的匹配方法不同,阈值设置也各不相同
threshold = 0.9
#取匹配程度大于90%的坐标
loc = np.where(res>=threshold)
for pt in zip(*loc[::-1]):#*号表示可选参数
bottom_right = (pt[0]+w,pt[1]+h)
cv2.rectangle(img_rgb,pt,bottom_right,(0,255,0),2)
cv_show('img_rgb',img_rgb)
END