性能测量和改进技术
测量性能
-
getTickCount()
- 返回参考事件(如机器开启时刻)到调用此函数的时钟周期数
- 因此,如果在函数执行之前和之后调用它,则会获得用于执行函数的时钟周期数。
-
getTickCount()
- 返回时钟周期的频率,或每秒钟的时钟周期数
e1 = cv2.getTickCount()
#你的执行的代码
#......
e2 = cv2.getTickCount()
#计算执行时间
time = (e2 - e1)/cv2.getTickFrequency()
- 注:也可以用时间模块计算执行时间,使用 time.time()函数来替代getTickCount(),然后取两次的差异,比如start=time.time(), end = time.time(), print(end-start)。
默认优化
- 许多 OpenCV 功能都使用 SSE2,AVX 等进行了优化。它还包含未经优化的代码。
- 优化功能在编译时是默认启用的,因此,OpenCV 在启用时运行优化代码,否则运行未优化代码
- useOptimized() 检查它是否已启用/禁用
- setUseOptimized(False) 启用/禁用它
标量操作
- Python 标量操作比 Numpy 标量操作更快。因此对于包含一个或两个元素的操作,Python 标量比 Numpy 数组更好。当阵列的大小稍大时,Numpy 会占据优势。
函数性能
- 通常,OpenCV 函数比 Numpy 函数更快。因此,对于相同的操作,OpenCV 功能是首选。但是,可能有例外,尤其是当 Numpy 使用视图而不是副本时。
图像金字塔
理论内容
-
在搜索图像中的某些内容如脸部信息时(需要局部信息),但并不确定该内容在图像中占据的大小。在这种情况下,我们需要创建一组不同分辨率的相同图像,并在所有图像中搜索该内容。这些不同分辨率的图像被称为图像金字塔(因为当它们堆叠排列时,底部为最高分辨率图像而顶部为最低分辨率图像,看起来像金字塔)。
-
图像金字塔有两种: 1)高斯金字塔和 2)拉普拉斯金字塔
-
通过去除较低级别图像(较高分辨率)中的连续行和列来形成高斯金字塔中的较高级别图像(较低分辨率)。较高级别图像的每个像素值由低一级别图像的 5 个像素值高斯加权得到。
-
面积减少到原来的四分之一。这些图像被称为 Octave(组)。当我们在金字塔中上升时(即分辨率降低),重复相似的操作。同理,在金字塔中下降时,每个级别的图像面积大小为上一级的四倍。
-
pyrDown()
-
pyrUp()
img = cv.imread('messi5.jpg')lower_reso = cv.pyrDown(higher_reso)
higher_reso2 = cv.pyrUp(lower_reso)
#higher_reso不等于higher_reso2.
#因为一旦降低了分辨率,就会丢失信息。
- 拉普拉斯金字塔由高斯金字塔转化而来,没有专属功能。拉普拉斯金字塔图像与边缘图像相似,它的大部分元素都是零,可以用于图像压缩。
使用金字塔的图像混合
-
金字塔的一个应用是图像混合。例如,在图像拼接中,您需要将两个图像堆叠在一起,但由于图像之间的不连续性,它可能看起来不太好。在这种情况下,使用金字塔进行图像混合可以实现无缝混合,而不会在图像中留下多余数据。典型的例子是混合橙子和苹果图像。
-
步骤:
- 加载苹果和橙色的两个图像
- 找到苹果和橙色的高斯金字塔(在这个例子中,级别数是 6)
- 从高斯金字塔,找到他们的拉普拉斯金字塔
- 现在加入左半部分的苹果和右半部分的拉普拉斯金字塔
- 最后,从这个联合图像金字塔,重建原始图像。
import cv2 as cv
import numpy as np,sys
A = cv.imread('apple.jpg')
B = cv.imread('orange.jpg')
# generate Gaussian pyramid for A
G = A.copy()
gpA = [G]
for i in xrange(6):
G = cv.pyrDown(G)
gpA.append(G)
# generate Gaussian pyramid for B
G = B.copy()
gpB = [G]
for i in xrange(6):
G = cv.pyrDown(G)
gpB.append(G)
# generate Laplacian Pyramid for A
lpA = [gpA[5]]
for i in xrange(5,0,-1):
GE = cv.pyrUp(gpA[i])
L = cv.subtract(gpA[i-1],GE)
lpA.append(L)
# generate Laplacian Pyramid for B
lpB = [gpB[5]]
for i in xrange(5,0,-1):
GE = cv.pyrUp(gpB[i])
L = cv.subtract(gpB[i-1],GE)
lpB.append(L)
# Now add left and right halves of images in each level
LS = []
for la,lb in zip(lpA,lpB):
rows,cols,dpt = la.shape
ls = np.hstack((la[:,0:cols/2], lb[:,cols/2:]))
LS.append(ls)
# now reconstruct
ls_ = LS[0]
for i in xrange(1,6):
ls_ = cv.pyrUp(ls_)
ls_ = cv.add(ls_, LS[i])
# image with direct connecting each half
real = np.hstack((A[:,:cols/2],B[:,cols/2:]))
cv.imwrite('Pyramid_blending2.jpg',ls_)
cv.imwrite('Direct_blending.jpg',real)
轮廓
轮廓特征
矩
-
图像矩可以计算某些特征,例如物体的重心,物体的面积等。
-
moments()
import numpy as np
import cv2 as cv
img = cv.imread('star.jpg',0)
ret,thresh = cv.threshold(img,127,255,0)
contours,hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
M = cv.moments(cnt)
print( M )
检测凸度
- isContourConvex() 用来检查曲线是否为凸多边形。它只是返回True还是False。
k = cv.isContourConvex(cnt)
最小外接圆
-
minEnclosingCircle() 找到对象的外接圆。它是一个以最小面积完全覆盖对象的圆圈。
(x,y),radius = cv.minEnclosingCircle(cnt) center = (int(x),int(y)) radius = int(radius) cv.circle(img,center,radius,(0,255,0),2)
拟合椭圆
- fitEllipse() 它返回椭圆所在的旋转矩形。
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img,ellipse,(0,255,0),2)
拟合线
- 同样,我们可以将一条直线拟合到一组点。下图包含一组白点。我们可以近似一条直线。
rows,cols = img.shape[:2]
[vx,vy,x,y] = cv.fitLine(cnt, cv.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
轮廓属性
1.长宽比
- 它是对象边界矩形的宽度与高度的比率。
x,y,w,h = cv.boundingRect(cnt)
aspect_ratio = float(w)/h
2.范围
- 范围是轮廓区域与边界矩形区域的比率。
area = cv.contourArea(cnt)
x,y,w,h = cv.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area
3.固实性
- 固实性是轮廓面积与其凸包面积的比率。
area = cv.contourArea(cnt)
hull = cv.convexHull(cnt)
hull_area = cv.contourArea(hull)
solidity = float(area)/hull_area
4.等效直径
- 等效直径是面积与轮廓面积相同的圆的直径。
area = cv.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)
5.方向
- 方向是物体指向的角度。以下方法还给出了主轴和副轴的长度。
(x,y),(MA,ma),angle = cv.fitEllipse(cnt)
6.遮罩和像素点
- 在某些情况下,我们可能需要构成该对象的所有点。
- 在这里,给出了两种方法,一种使用Numpy函数,另一种使用OpenCV函数(最后注释的行)执行相同的操作。结果也相同,但略有不同。Numpy以(行,列)格式给出坐标,而OpenCV以(x,y)格式给出坐标。因此,基本上答案是可以互换的。注意,row = x,column = y。
mask = np.zeros(imgray.shape,np.uint8)
cv.drawContours(mask,[cnt],0,255,-1)
pixelpoints = np.transpose(np.nonzero(mask))
# pixelpoints = cv.findNonZero(mask)
7.最大值、最小值及其位置
#可以使用遮罩图像找到这些参数
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(imgray,mask = mask)
8.平均颜色或平均强度
-
在这里,我们可以找到对象的平均颜色。或者可以是灰度模式下物体的平均强度。我们再次使用相同的蒙版进行此操作。
mean_val = cv.mean(im,mask = mask)
9.极端点
-
极点是指对象的最顶部,最底部,最右侧和最左侧的点。
leftmost = tuple(cnt[cnt[:,:,0].argmin()][0]) rightmost = tuple(cnt[cnt[:,:,0].argmax()][0]) topmost = tuple(cnt[cnt[:,:,1].argmin()][0]) bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])
更多功能
凸包缺陷
- 物体与该船体的任何偏离都可以视为凸包缺陷。
- convexityDefects() 来查找该函数。
hull = cv.convexHull(cnt,returnPoints = False)
defects = cv.convexityDefects(cnt,hull)
注意 请记住,在寻找凸包时,我们必须传递returnPoints = False,以便寻找凸缺陷。
- 它返回一个数组,其中每行包含这些值 [起点,终点,最远点,到最远点的近似距离]。
- 因此可以标出凸包中的最远点
import cv2 as cv
import numpy as np
img = cv.imread('star.jpg')
img_gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(img_gray, 127, 255,0)
contours,hierarchy = cv.findContours(thresh,2,1)
cnt = contours[0]
hull = cv.convexHull(cnt,returnPoints = False)
defects = cv.convexityDefects(cnt,hull)
for i in range(defects.shape[0]):
s,e,f,d = defects[i,0]
start = tuple(cnt[s][0])
end = tuple(cnt[e][0])
far = tuple(cnt[f][0])
cv.line(img,start,end,[0,255,0],2)
cv.circle(img,far,5,[0,0,255],-1)
cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
点多边形测试
- 此功能查找图像中的点与轮廓之间的最短距离。它返回的距离为:当点在轮廓外时为负;当点在轮廓内时为正;如果点在轮廓上,则返回零。
dist = cv.pointPolygonTest(cnt,(50,50),True)
- 第三个参数是measureDist。如果为True,则找到带符号的距离。如果为False,它将查找该点是在轮廓内部还是外部或轮廓上(它分别返回+ 1,-1、0)。
注意 如果您不想查找距离,请确保第三个参数为False,因为这是一个耗时的过程。因此,将其设置为False可使速度提高2-3倍。
匹配形状
- matchShapes()
- 能够比较两个形状或两个轮廓,并返回显示相似性的度量。结果越低,匹配越好。
import cv2 as cv
import numpy as np
img1 = cv.imread('star.jpg',0)
img2 = cv.imread('star2.jpg',0)
ret, thresh = cv.threshold(img1, 127, 255,0)
ret, thresh2 = cv.threshold(img2, 127, 255,0)
contours,hierarchy = cv.findContours(thresh,2,1)
cnt1 = contours[0]
contours,hierarchy = cv.findContours(thresh2,2,1)
cnt2 = contours[0]
ret = cv.matchShapes(cnt1,cnt2,1,0.0)
print( ret )
- 即使图像旋转也不会对该比较产生太大影响。
直方图
查找直方图
1.OpenCV中的API
- calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
- images:它是uint8或float32类型的源图像。它应该放在方括号中,即“ [img]”。
- channels:也以方括号给出。它是我们计算直方图的通道的索引。例如,如果输入为灰度图像,则其值为[0]。对于彩色图像,您可以传递[0],[1]或[2]分别计算蓝色,绿色或红色通道的直方图。
- mask:遮罩图像。为了找到完整图像的直方图,将其指定为“无”。但是,如果要查找图像特定区域的直方图,则必须为此创建一个遮罩图像并将其作为遮罩。(我将在后面显示一个示例。)
- histSize:这表示我们的BIN计数。需要放在方括号中。对于全尺寸,我们通过[256]。
- ranges:这是我们的RANGE。通常为[0,256]。 因此,让我们从示例图像开始。只需在灰度模式下加载图像并找到其完整的直方图即可。
img = cv.imread('home.jpg',0)
hist = cv.calcHist([img],[0],None,[256],[0,256])
- hist是256x1的数组,每个值对应于该图像中具有相应像素值的像素数。
2.Numpy中的API
- histogram()
hist,bins = np.histogram(img.ravel(),256,[0,256])
-
hist与我们之前计算的相同。但是bin将具有257个元素,因为Numpy计算出bin的范围为0-0.99、1-1.99、2-2.99等。因此最终范围为255-255.99。为了表示这一点,他们还在料箱末端添加了256。但是我们不需要256。最多255就足够了。
-
Numpy还有另一个函数 np.bincount() ,它比np.histogram()快10倍左右。
注意 OpenCV函数比 np.histogram() 快(大约40倍)。
绘制直方图
1.简短方法:使用Matplotlib绘图功能
- Matplotlib带有直方图绘图功能:matplotlib.pyplot.hist()
- 它直接找到直方图并将其绘制。无需使用calcHist或 np.histogram() 函数来查找直方图。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('home.jpg',0)
plt.hist(img.ravel(),256,[0,256]); plt.show()
2.使用OpenCV绘图功能
-
可以调整直方图的值及其bin值,使其看起来像x,y坐标,以便可以使用 cv.line()或 cv.polyline() 函数绘制它以生成与上述相同的图像
-
遮罩的应用:
- 如果要查找图像某些区域的直方图怎么办?
- 只需在要查找直方图的区域上创建白色的蒙版图像,否则创建黑色。然后通过这个作为mask。
img = cv.imread('home.jpg',0)
# create a mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
masked_img = cv.bitwise_and(img,img,mask = mask)
# Calculate histogram with mask and without mask
# Check third argument for mask
hist_full = cv.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv.calcHist([img],[0],mask,[256],[0,256])
plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask,'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0,256])
plt.show()
- 结果:在直方图中,蓝线表示完整图像的直方图,绿线表示遮蔽区域的直方图。
霍夫变换
霍夫线变换
- 检测图像中的线条
- HoughLines()
- 它简单的返回了一个(rho, theta)值的数组。
- rho 的单位是像素,theta的单位是弧度。
- 第一个参数,输入图像应该是个二元图像,所以在应用霍夫线性变换之前先来个阈值法或者Canny边缘检测。
- 第二、第三参数分别是 rho 和theta 的精度。
- 第四个参数则是一个阈值,它代表了一个(rho,theta)单元被认为是一条直线需要获得的最低票数。
- 要记住的是,得票数其实取决于这条直线穿过了多少个点。所以它也代表了应被检测出的线条最少有多长。
import cv2 as cv
import numpy as np
img = cv.imread(cv.samples.findFile('sudoku.png'))
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray,50,150,apertureSize = 3)
lines = cv.HoughLines(edges,1,np.pi/180,200)
for line in lines:
rho,theta = line[0]
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
cv.line(img,(x1,y1),(x2,y2),(0,0,255),2)
cv.imwrite('houghlines3.jpg',img)
-
概率Hough变换
-
它不去取所有的点来列入考虑,取而代之的是取足够完成直线检测的这些点的随机子集。只要我们把阈值下调一点。
-
HoughLinesP() 它比之前介绍的函数多出来两个参数:
- minLineLength - 最小线长。比这个值小的线条会被丢弃。
- maxLineGap - 允许线段之间的最大间隙,以便将(在同一条直线上的)线段视为同一条。
-
它直接返回直线的两个端点。
import cv2 as cv
import numpy as np
img = cv.imread(cv.samples.findFile('sudoku.png'))
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray,50,150,apertureSize = 3)
lines = cv.HoughLinesP(edges,1,np.pi/180,100,minLineLength=100,maxLineGap=10)
for line in lines:
x1,y1,x2,y2 = line[0]
cv.line(img,(x1,y1),(x2,y2),(0,255,0),2)
cv.imwrite('houghlines5.jpg',img)
霍夫圆变换
- HoughCircles() 它利用了边缘的梯度信息。
import numpy as np
import cv2 as cv
img = cv.imread('opencv-logo-white.png',0)
img = cv.medianBlur(img,5)
cimg = cv.cvtColor(img,cv.COLOR_GRAY2BGR)
circles = cv.HoughCircles(img,cv.HOUGH_GRADIENT,1,20,
param1=50,param2=30,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv.imshow('detected circles',cimg)
cv.waitKey(0)
cv.destroyAllWindows()
光流
- 光流是由于对象或者相机的移动引起的两个连续帧之间的时变图像的运动模式。这是一个二维的矢量场,其中,每一个矢量都是一个位移矢量用以显示从第一帧到第二帧的点的移动(位移)。
用 Lucas-Kanade 光流算法
- OpenCV 将这些功能都集成在了一个函数cv.calcOpticalFlowPyrLK()中。这里,我们创建了一个用以在视频中跟踪某些点的简单程序。为了决定特征点,我们使用goodFeaturesToTrack()函数。获取第一帧,并在其中检测 Shi-Tomasi 角点,然后我们使用 Lucas-Kanade 光流算法对于这些点进行迭代跟踪。对于函数calcOpticalFlowPyrLK(),我们将前一帧,之前的特征点和下一帧传入函数。它将返回下一组特征点以及状态向量,如果相应的特征点被发现,状态向量的每个元素被设置为 1,否则,被置为 0。我们将返回的这些点作为下一次迭代中所传递的参数。
import numpy as np
import cv2 as cv
cap = cv.VideoCapture('slow.flv')
# ShiTomasi 角点检测的参数
feature_params = dict( maxCorners = 100,
qualityLevel = 0.3,
minDistance = 7,
blockSize = 7 )
# Lucas-Kanade 光流算法的参数
lk_params = dict( winSize = (15,15),
maxLevel = 2,
criteria = (cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.03))
# 创建一组随机颜色数
color = np.random.randint(0,255,(100,3))
# 取第一帧并寻找角点
ret, old_frame = cap.read()
old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)
p0 = cv.goodFeaturesToTrack(old_gray, mask = None, **feature_params)
# 创建绘制轨迹用的遮罩图层
mask = np.zeros_like(old_frame)
while(1):
ret,frame = cap.read()
frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
# 计算光流
p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# 选取最佳始末点
good_new = p1[st==1]
good_old = p0[st==1]
# 绘制轨迹
for i,(new,old) in enumerate(zip(good_new,good_old)):
a,b = new.ravel()
c,d = old.ravel()
mask = cv.line(mask, (a,b),(c,d), color[i].tolist(), 2)
frame = cv.circle(frame,(a,b),5,color[i].tolist(),-1)
img = cv.add(frame,mask)
cv.imshow('frame',img)
k = cv.waitKey(30) & 0xff
if k == 27:
break
# 更新选取帧与特征点
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2)
cv.destroyAllWindows()
cap.release()
计算疏光流
- Lucas-Kanade 方法是求稀疏光流的一种重要方法(在我们的例子中,使用 Shi-Tomasi 算法检测到角点)。而 OpenCV 提供了另一种算法用以计算稠密光流。这个方法将计算一帧中所有点的光流。这个方法基于 Gunner Farneback 算法
- 我们得到一个带有光流向量的双通道矩阵, ( u , v ) (u,v) (u,v)。我们将寻找其大小与方向。各种颜色代码用以获得更好的视觉效果。方向对应于图像的色相值。而大小则对应明度位面。
import cv2 as cv
import numpy as np
cap = cv.VideoCapture("vtest.avi")
ret, frame1 = cap.read()
prvs = cv.cvtColor(frame1,cv.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[...,1] = 255
while(1):
ret, frame2 = cap.read()
next = cv.cvtColor(frame2,cv.COLOR_BGR2GRAY)
flow = cv.calcOpticalFlowFarneback(prvs,next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
mag, ang = cv.cartToPolar(flow[...,0], flow[...,1])
hsv[...,0] = ang*180/np.pi/2
hsv[...,2] = cv.normalize(mag,None,0,255,cv.NORM_MINMAX)
bgr = cv.cvtColor(hsv,cv.COLOR_HSV2BGR)
cv.imshow('frame2',bgr)
k = cv.waitKey(30) & 0xff
if k == 27:
break
elif k == ord('s'):
cv.imwrite('opticalfb.png',frame2)
cv.imwrite('opticalhsv.png',bgr)
prvs = next
cap.release()
cv.destroyAllWindows()