Opencv Python 简明教程
OpenCV Python - Overview
OpenCV 代表 Open Source Computer Vision ,是一个在实际计算机视觉应用程序编程中非常有用的函数库。计算机视觉一词用于描述使用计算机程序执行数字图像和视频分析的主题。计算机视觉是现代学科(如人工智能和机器学习)的一个重要组成部分。
OpenCV 最初由英特尔开发,是一个用 C++ 编写、跨平台的库,但也具有为许多其他编程语言(例如 Java 和 Python)而开发的 OpenCV C 接口包装器。在该教程中,我们将描述 OpenCV 的 Python 库的功能。
OpenCV Python - Environment Setup
在大多数情况下,使用 pip 就足以在电脑上安装 OpenCV-Python。
用于安装 pip 的命令如下 −
pip install opencv-python
建议在新虚拟环境中执行此安装。当前版本的 OpenCV-Python 是 4.5.1.48,可以通过以下命令进行验证 −
>>> import cv2
>>> cv2.__version__
'4.5.1'
由于 OpenCV-Python 依赖于 NumPy,后者也将自动安装。根据选择,你可以安装 Matplotlib 来渲染某些图形化输出。
在 Fedora 上,你可以通过下面提到的命令安装 OpenCV-Python −
$ yum install numpy opencv*
OpenCV-Python 也可以通过从 http://sourceforge.net 获得的源构建来安装。请按照为其提供的安装说明进行操作。
OpenCV Python - Reading an image
CV2 程序包(OpenCV-Python 库的名称)提供了 imread() 函数来读取图像。
读取图像的命令如下 −
img=cv2.imread(filename, flags)
flags 参数是对以下常量的枚举 −
-
cv2.IMREAD_COLOR (1) - 载入彩色图像。
-
cv2.IMREAD_GRAYSCALE (0) - 以灰度模式载入图像
-
cv2.IMREAD_UNCHANGED (-1) - 按原样载入图像,包括 Alpha 通道
该函数将返回一个图像对象,可以使用 imshow() 函数来渲染该对象。imshow() 函数的命令如下 −
cv2.imshow(window-name, image)
图像将在一个已命名窗口中显示。使用 AUTOSIZE 标志集创建一个新的窗口。 WaitKey() 是一种键盘绑定函数。它的参数以毫秒为单位的时间。
该函数将等待指定的时间并保持窗口显示,直到按下某个键。最后,我们可以销毁所有已创建的窗口。
该函数将等待指定的时间并保持窗口显示,直到按下某个键。最后,我们可以销毁所有已创建的窗口。
显示 OpenCV 徽标的程序如下 −
import numpy as np
import cv2
# Load a color image in grayscale
img = cv2.imread('OpenCV_Logo.png',1)
cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
上述程序将按如下方式显示 OpenCV 徽标 −
OpenCV Python - Write an image
CV2 程序包有 imwrite() 函数,可将图像对象保存到指定的文件中。
使用 imwrite() 函数保存图像的命令如下 −
cv2.imwrite(filename, img)
图像格式由 OpenCV 从文件扩展名自动确定。OpenCV 支持 .bmp, *.dib , *.jpeg, *.jpg, *.png, .webp、 .sr, .tiff、*.tif 等图像文件类型。
OpenCV Python - Using Matplotlib
OpenCV Python - Image Properties
OpenCV 会在 NumPy 数组中读取图像数据。此 ndarray 对象的 shape() 方法揭示了图像属性,例如维度和通道。
使用 shape() 方法的命令如下:
>>> img = cv.imread("OpenCV_Logo.png", 1)
>>> img.shape
(222, 180, 3)
在上述命令中:
-
前两个项目 shape[0] 和 shape[1] 表示图像的宽度和高度。
-
Shape[2] 表示通道数。
-
3 表示图像有红绿蓝 (RGB) 通道。
类似地,size 属性返回图像大小。图像大小的命令如下:
>>> img.size
119880
ndarray 中的每个元素代表一个图像像素。
我们可以借助下面提到的命令访问和操作任何像素的值。
>>> p=img[50,50]
>>> p
array([ 1, 1, 255], dtype=uint8)
OpenCV Python - Bitwise Operations
位操作用于图像处理和提取图像中的基本部分。
OpenCV 中实现了以下运算符:
-
bitwise_and
-
bitwise_or
-
bitwise_xor
-
bitwise_not
Example 1
为了演示如何使用这些运算符,获取了两张带有已填充和未填充圆圈的图像。
以下程序演示了如何在 OpenCV-Python 中使用位运算符:
import cv2
import numpy as np
img1 = cv2.imread('a.png')
img2 = cv2.imread('b.png')
dest1 = cv2.bitwise_and(img2, img1, mask = None)
dest2 = cv2.bitwise_or(img2, img1, mask = None)
dest3 = cv2.bitwise_xor(img1, img2, mask = None)
cv2.imshow('A', img1)
cv2.imshow('B', img2)
cv2.imshow('AND', dest1)
cv2.imshow('OR', dest2)
cv2.imshow('XOR', dest3)
cv2.imshow('NOT A', cv2.bitwise_not(img1))
cv2.imshow('NOT B', cv2.bitwise_not(img2))
if cv2.waitKey(0) & 0xff == 27:
cv2.destroyAllWindows()
Example 2
在另一个涉及位运算的示例中,将 opencv 标志叠加到另一张图像上。在这里,我们通过 threshold() 函数调用从标志中获得一个掩码数组,并在它们之间执行 AND 操作。
类似地,通过 NOT 操作,我们得到一个反向掩码。此外,我们还可以用背景图像进行 AND 操作。
以下程序确定了位操作的使用:
import cv2 as cv
import numpy as np
img1 = cv.imread('lena.jpg')
img2 = cv.imread('whitelogo.png')
rows,cols,channels = img2.shape
roi = img1[0:rows, 0:cols]
img2gray = cv.cvtColor(img2,cv.COLOR_BGR2GRAY)
ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY)
mask_inv = cv.bitwise_not(mask)
# Now black-out the area of logo
img1_bg = cv.bitwise_and(roi,roi,mask = mask_inv)
# Take only region of logo from logo image.
img2_fg = cv.bitwise_and(img2,img2,mask = mask)
# Put logo in ROI
dst = cv.add(img2_fg, img1_bg)
img1[0:rows, 0:cols ] = dst
cv.imshow(Result,img1)
cv.waitKey(0)
cv.destroyAllWindows()
OpenCV Python - Draw Shapes and Text
在本章中,我们将学习如何在 OpenCV-Python 的帮助下在图像中绘制形状和文本。让我们从了解在图像上绘制形状开始。
Draw Shapes on Images
我们需要了解 OpenCV-Python 中所需的函数,这有助于我们绘制图像上的形状。
Functions
OpenCV-Python 程序包(称为 cv2)包含以下用于绘制相应形状的函数。
Function |
Description |
Command |
cv2.line() |
绘制连接两个点的线段。 |
cv2.line(img, pt1, pt2, color, thickness) |
cv2.circle() |
在给定点处绘制指定半径的圆作为中心 |
cv2.circle(img, center, radius, color, thickness) |
cv2.rectangle |
绘制一个矩形,给定点为左上和右下。 |
cv2.rectangle(img, pt1, pt2, color, thickness) |
cv2.ellipse() |
绘制简单或粗糙的椭圆弧或填充椭圆扇形。 |
cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness) |
Parameters
以上函数的通用参数如下 −
Sr.No. |
Function & Description |
1 |
img 您想绘制形状的图像 |
2 |
color 形状的颜色。对于 BGR,将它作为元组传递。对于灰度,只需传递标量值。 |
3 |
thickness 线或圆等的粗细。如果为封闭图形(如圆形)传递-1,它将填充该形状。 |
4 |
lineType 线型,8 方向连接、抗锯齿线等。 |
Example
以下示例显示如何在图像上绘制形状。程序如下 −
import numpy as np
import cv2
img = cv2.imread('LENA.JPG',1)
cv2.line(img,(20,400),(400,20),(255,255,255),3)
cv2.rectangle(img,(200,100),(400,400),(0,255,0),5)
cv2.circle(img,(80,80), 55, (255,255,0), -1)
cv2.ellipse(img, (300,425), (80, 20), 5, 0, 360, (0,0,255), -1)
cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Draw Text
提供 cv2.putText() 函数在图像上写文本。命令如下 −
img, text, org, fontFace, fontScale, color, thickness)
Fonts
OpenCV 支持以下字体 −
Font Name |
Font Size |
FONT_HERSHEY_SIMPLEX |
0 |
FONT_HERSHEY_PLAIN |
1 |
FONT_HERSHEY_DUPLEX |
2 |
FONT_HERSHEY_COMPLEX |
3 |
FONT_HERSHEY_TRIPLEX |
4 |
FONT_HERSHEY_COMPLEX_SMALL |
5 |
FONT_HERSHEY_SCRIPT_SIMPLEX |
6 |
FONT_HERSHEY_SCRIPT_COMPLEX |
7 |
FONT_ITALIC |
16 |
OpenCV Python - Handling Mouse Events
OpenCV 能够用一个回调函数来注册各种鼠标相关事件。这用于根据鼠标事件的类型来启动一个特定用户定义的动作。
Sr.No |
Mouse event & Description |
1 |
cv.EVENT_MOUSEMOVE 当鼠标指针在窗口上移动时。 |
2 |
cv.EVENT_LBUTTONDOWN 表示按下鼠标左键。 |
3 |
cv.EVENT_RBUTTONDOWN 按下鼠标右键的事件。 |
4 |
cv.EVENT_MBUTTONDOWN 表示按下鼠标中键。 |
5 |
cv.EVENT_LBUTTONUP 当释放鼠标左键时。 |
6 |
cv.EVENT_RBUTTONUP 当释放鼠标右键时。 |
7 |
cv.EVENT_MBUTTONUP 表示释放鼠标中键。 |
8 |
cv.EVENT_LBUTTONDBLCLK 该事件在双击鼠标左键时发生。 |
9 |
cv.EVENT_RBUTTONDBLCLK 表示双击鼠标右键。 |
10 |
cv.EVENT_MBUTTONDBLCLK 表示双击鼠标中键。 |
11 |
cv.EVENT_MOUSEWHEEL 正向滚动为正值,反向滚动为负值。 |
要根据鼠标事件来调用函数,就必须使用 setMouseCallback() 函数来注册。对应的命令如下所示:
cv2.setMouseCallback(window, callbak_function)
此函数将事件的类型和位置传递给回调函数以进行进一步处理。
Example 1
以下代码会在窗口图像背景上发生左键双击事件时绘制一个圆:
import numpy as np
import cv2 as cv
# mouse callback function
def drawfunction(event,x,y,flags,param):
if event == cv.EVENT_LBUTTONDBLCLK:
cv.circle(img,(x,y),20,(255,255,255),-1)
img = cv.imread('lena.jpg')
cv.namedWindow('image')
cv.setMouseCallback('image',drawfunction)
while(1):
cv.imshow('image',img)
key=cv.waitKey(1)
if key == 27:
break
cv.destroyAllWindows()
Example 2
根据用户输入(1、2 或 3)交互式绘制矩形、线或圆:
import numpy as np
import cv2 as cv
# mouse callback function
drawing=True
shape='r'
def draw_circle(event,x,y,flags,param):
global x1,x2
if event == cv.EVENT_LBUTTONDOWN:
drawing = True
x1,x2 = x,y
elif event == cv.EVENT_LBUTTONUP:
drawing = False
if shape == 'r':
cv.rectangle(img,(x1,x2),(x,y),(0,255,0),-1)
if shape == 'l':
cv.line(img,(x1,x2),(x,y),(255,255,255),3)
if shape=='c':
cv.circle(img,(x,y), 10, (255,255,0), -1)
img = cv.imread('lena.jpg')
cv.namedWindow('image')
cv.setMouseCallback('image',draw_circle)
while(1):
cv.imshow('image',img)
key=cv.waitKey(1)
if key==ord('1'):
shape='r'
if key==ord('2'):
shape='l'
if key==ord('3'):
shape='c'
#print (shape)
if key == 27:
break
cv.destroyAllWindows()
如果按下“1”,将在鼠标左键按下和抬起的坐标之间绘制一个矩形。
如果用户选择 2,将使用坐标作为端点绘制一条线。
如果选择 3 绘制圆,则会在鼠标抬起事件的坐标处绘制一个圆。
在成功执行以上程序后,输出将显示如下图像:
OpenCV Python - Add Trackbar
OpenCV 中的轨迹条是滑块控件,通过手动在条上滑动标签,帮助从连续范围内选取一个变量值。标签位置与一个值同步。
createTrackbar() 函数使用以下命令创建一个 Trackbar 对象 −
cv2.createTrackbar(trackbarname, winname, value, count, TrackbarCallback)
在以下示例中,为用户提供了三个轨迹条,以便从 0 到 255 的灰度范围内设置 R、G 和 B 的值。
使用轨迹条位置值,绘制一个矩形,其填充颜色对应于 RGB 颜色值。
Example
以下程序用于添加轨迹条 −
import numpy as np
import cv2 as cv
img = np.zeros((300,400,3), np.uint8)
cv.namedWindow('image')
def nothing(x):
pass
# create trackbars for color change
cv.createTrackbar('R','image',0,255,nothing)
cv.createTrackbar('G','image',0,255,nothing)
cv.createTrackbar('B','image',0,255,nothing)
while(1):
cv.imshow('image',img)
k = cv.waitKey(1) & 0xFF
if k == 27:
break
# get current positions of four trackbars
r = cv.getTrackbarPos('R','image')
g = cv.getTrackbarPos('G','image')
b = cv.getTrackbarPos('B','image')
#s = cv.getTrackbarPos(switch,'image')
#img[:] = [b,g,r]
cv.rectangle(img, (100,100),(200,200), (b,g,r),-1)
cv.destroyAllWindows()
OpenCV Python - Resize and Rotate an Image
在本章中,我们将了解如何使用 OpenCVPython 调整图像大小和旋转图像。
Resize an Image
可以使用 cv2.resize() 函数放大或缩小图像。
resize() 函数的使用方式如下:
resize(src, dsize, dst, fx, fy, interpolation)
一般来说,插值是在已知数据点之间估计算值的处理过程。
当图形数据包含一个间隙,但在间隙的两侧或间隙内的几个特定点内有数据可用时,插值允许我们估算间隙内的值。
在上 resize() 函数中,插值标记确定用于计算目标图像大小的插值类型。
Types of Interpolation
插值类型如下:
-
INTER_NEAREST − 最近邻插值。
-
INTER_LINEAR − 双线性插值(默认使用)
-
INTER_AREA − 使用像素区域关系进行重采样。它是图像抽稀的首选方法,但当图像缩放时,它类似于 INTER_NEAREST 方法。
-
INTER_CUBIC − 在 4x4 像素邻域上执行双三次插值
-
INTER_LANCZOS4 − 在 8x8 像素邻域上执行 Lanczos 插值
首选插值方法是 cv2.INTER_AREA 用于缩小,cv2.INTER_CUBIC(慢)和 cv2.INTER_LINEAR 用于缩放。
Rotate an image
OpenCV 使用仿射变换函数对图像进行诸如平移和旋转之类的操作。仿射变换是一种变换,可以表示为矩阵乘法(线性变换)后跟向量加法(平移)。
cv2 模块提供两个函数 cv2.warpAffine 和 cv2.warpPerspective ,您可以使用它们执行各种变换。cv2.warpAffine 采用 2x3 变换矩阵,而 cv2.warpPerspective 采用 3x3 变换矩阵作为输入。
为了找到用于旋转的变换矩阵,OpenCV 提供了一个函数 cv2.getRotationMatrix2D ,如下所示:
getRotationMatrix2D(center, angle, scale)
然后我们将 warpAffine 函数应用于 getRotationMatrix2D()函数返回的矩阵,以获得旋转后的图像。
以下程序将原始图像旋转 90 度,而不改变其尺寸:
Example
import numpy as np
import cv2
img = cv2.imread('OpenCV_Logo.png',1)
h, w = img.shape[:2]
center = (w / 2, h / 2)
mat = cv2.getRotationMatrix2D(center, 90, 1)
rotimg = cv2.warpAffine(img, mat, (h, w))
cv2.imshow('original',img)
cv2.imshow('rotated', rotimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
OpenCV Python - Image Threshold
在数字图像处理中,阈值处理是一个基于像素强度阈值创建二值图像的过程。阈值处理过程将前景像素与背景像素分离开来。
OpenCV 提供了执行 simple, adaptive 和 Otsu’s 阈值处理的函数。
在简单阈值化中,所有值小于阈值的像素都被设置为零,其余的则被设置为最大像素值。这是最简单的阈值化形式。
cv2.threshold() 函数具有以下定义。
cv2.threshold((src, thresh, maxval, type, dst)
Parameters
图像阈值化的参数如下:
-
Src: Input array.
-
Dst:相同大小的输出数组。
-
Thresh: Threshold value.
-
Maxval: Maximum value.
-
Type: Thresholding type.
Types of Thresholding
其他类型的阈值化如下所示:
Sr.No |
Type & Function |
1 |
cv.THRESH_BINARY dst(x,y) = maxval if src(x,y)>thresh 0 otherwise |
2 |
cv.THRESH_BINARY_INV dst(x,y)=0 if src(x,y)> thresh maxval otherwise |
3 |
cv.THRESH_TRUNC dst(x,y)=threshhold if src(x,y)>thresh src(x,y) otherwise |
4 |
cv.THRESH_TOZERO dst(x,y)=src(x,y) if src(x,y)>thresh 0 otherwise |
5 |
cv.THRESH_TOZERO_INV dst(x,y)=0 if src(x,y)>thresh src(x,y)otherwise |
这些阈值类型根据以下图表对输入图像执行操作:
threshold() 函数返回所使用的阈值和阈值图像。
以下程序通过将阈值设为 127,从原始图像生成一个从 255 到 0 具有灰色值渐变的二进制图像。
Example
最初的和产生的阈值二进制图像使用 Matplotlib 库并排绘制。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('gradient.png',0)
ret,img1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
plt.subplot(2,3,1),plt.imshow(img,'gray',vmin=0,vmax=255)
plt.title('Original')
plt.subplot(2,3,2),plt.imshow(img1,'gray',vmin=0,vmax=255)
plt.title('Binary')
plt.show()
Output
自适应阈值化根据其周围的一个小区域确定像素的阈值。因此,得到了同一图像中不同区域的不同阈值。这为照明不同的图像提供了更好的结果。
cv2.adaptiveThreshold() 方法采用以下输入参数:
cv.adaptiveThreshold( src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst] )
adaptiveMethod 具有以下枚举值:
-
cv.ADAPTIVE_THRESH_MEAN_C − 阈值是邻域区域的平均值减去常数 C。
-
cv.ADAPTIVE_THRESH_GAUSSIAN_C - 阈值是邻域值的加权高斯和减去常数 C。
Example
在下面的示例中,原始图像 (messi.jpg) 应用平均值和高斯自适应阈值化。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('messi.jpg',0)
img = cv.medianBlur(img,5)
th1 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\
cv.THRESH_BINARY,11,2)
th2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv.THRESH_BINARY,11,2)
titles = ['Original', 'Mean Thresholding', 'Gaussian Thresholding']
images = [img, th1, th2]
for i in range(3):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
Example
OTSU 算法从图像直方图中自动确定阈值。我们需要在 THRESH-BINARY 标志中传递 cv.THRES_OTSU 标志。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('messi.jpg',0)
# global thresholding
ret1,img1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
# Otsu's thresholding
ret2,img2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
plt.subplot(2,2,1),plt.imshow(img,'gray',vmin=0,vmax=255)
plt.title('Original')
plt.subplot(2,2,2),plt.imshow(img1,'gray')
plt.title('Binary')
plt.subplot(2,2,3),plt.imshow(img2,'gray')
plt.title('OTSU')
plt.show()
OpenCV Python - Image Filtering
图像基本上是像素矩阵,表示为 0 到 255 之间的二进制值,对应于灰度值。彩色图像将是一个三维矩阵,具有对应于 RGB 的多个通道。
图像滤波是对像素值进行平均的过程,目的是改变原始图像的色调、亮度、对比度等。
通过应用低通滤波器,我们可以去除图像中的任何噪声。高通滤波器有助于检测边缘。
OpenCV 库提供 cv2.filter2D() 函数。它通过一个大小为 3X3 或 5X5 等的正方形矩阵内核对原始图像进行卷积。
卷积将一个内核矩阵横向和纵向滑动跨越图像矩阵。对于每个位置,将内核下方的所有像素相加,取内核下方的像素的平均值,并将中心像素替换为平均值。
对所有像素执行此操作以获取输出图像像素矩阵。参考如下给出的图表 −
cv2.filter2D() 函数需要输入数组、内核矩阵和输出数组参数。
Example
下图使用此函数获得二维卷积的平均图像结果。相关的程序如下 −
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('opencv_logo_gs.png')
kernel = np.ones((3,3),np.float32)/9
dst = cv.filter2D(img,-1,kernel)
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title('Convolved')
plt.xticks([]), plt.yticks([])
plt.show()
OpenCV Python - Edge Detection
此处的边缘表示图像中对象的边界。OpenCV 有一个 cv2.Canny() 函数,通过实现 Canny 算法识别图像中各种对象的边缘。
Canny 边缘检测算法由 John Canny 发明。据此,对象的边缘通过执行以下步骤确定 −
第一步是减少图像中的噪声像素。这通过应用 5X5 高斯滤波器来完成。
第二步涉及查找图像的强度梯度。通过应用 Sobel 算子来滤波第一阶段的平滑图像,以获得水平和垂直方向上的 一 阶导数 (Gx 和 Gy)。
均方根值给出边缘梯度,导数的反正切比率给出边缘的方向。
\mathrm{边缘\:梯度\:G\:=\:\sqrt{G_x 2+G_y 2}}
\mathrm{角度\:\theta\:=\:\tan^{-1}(\frac{G_{y}}{G_{x}})}
在获得梯度大小和方向之后,对图像进行全面扫描以移除任何可能不构成边缘的意外像素。
下一步是根据最小值和最大值阈值执行迟滞阈值处理。小于最小值和最大值的强度梯度是非边缘,因此需要丢弃。两者之间的基于其连通性被视为边缘点或非边缘。
所有这些步骤都通过 OpenCV 的 cv2.Canny() 函数执行,该函数需要输入图像数组和最小值和最大值参数。
Example
以下是 Canny 边缘检测的示例。程序如下所示:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('lena.jpg', 0)
edges = cv.Canny(img,100,200)
plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(edges,cmap = 'gray')
plt.title('Edges of original Image'), plt.xticks([]), plt.yticks([])
plt.show()
OpenCV Python - Histogram
直方图显示了图像中的强度分布。它在 X 轴上绘制像素值(0 至 255),在 Y 轴上绘制像素数。
通过使用直方图,可以理解指定图像的对比度、亮度和强度分布。直方图中的柱状条表示 X 轴上值的增量部分。
在我们的案例中,它是像素值,默认柱状条大小为 1。
在 OpenCV 库中,函数 cv2.calcHist() 根据输入图像计算直方图。函数的命令如下 −
cv.calcHist(images, channels, mask, histSize, ranges)
Parameters
函数 cv2.calcHist() 的参数如下 −
-
images − 这是 [img] 中的 uint8 或 float32 类型的源图像,用方括号表示。
-
channels − 这是用于计算直方图的通道索引。对于灰度图像,其值为 [0]。对于 BGR 图像,可以传递 [0]、[1] 或 [2] 来计算每个通道的直方图。
-
mask − 掩码图像指定为全图的 “None”。对于图像的特定区域,必须为其创建一个掩码图像,并将其指定为掩码。
-
histSize − 这表示 BIN 计数。
-
ranges − 通常情况下为 [0,256]。
Example
通过以下程序计算图像的每个通道的直方图(lena.jpg),并绘制每个通道的强度分布 −
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('lena.jpg')
color = ('b','g','r')
for i,col in enumerate(color):
hist = cv.calcHist([img],[i],None,[256],[0,256])
plt.plot(hist, color = col)
plt.xlim([0,256])
plt.show()
OpenCV Python - Color Spaces
色彩空间是一个数学模型,描述了如何表示颜色。它在特定的、可测量的、固定的可能颜色和亮度值范围内描述了颜色。
OpenCV 支持以下知名的色彩空间 −
-
RGB Color space − 它是一种加色空间。通过组合红色、绿色和蓝色色彩值来获取颜色值。每个值由介于 0 到 255 之间的一个数字表示。
-
HSV color space − H、S 和 V 分别代表色相、饱和度和明度。这是一种替代 RGB 的色彩模型。该模型应该更接近人眼感知颜色的方式。色相值在 0 到 179 之间,而 S 和 V 值在 0 到 255 之间。
-
CMYK color space − 与 RGB 不同,CMYK 是一种减色模型。字母分别代表青色、品红色、黄色和黑色。白色光减去红色得到青色,白色减去绿色得到品红色,白色减去蓝色得到黄色。所有值都以 0 到 100% 的比例表示。
-
CIELAB color space − LAB 色彩空间有三个分量,分别是代表亮度的 L、从绿色到洋红色的颜色分量 A 和从蓝色到黄色的分量 B。
-
YCrCb color space − 在此处,Cr 代表 R-Y,Cb 代表 B-Y。这有助于将亮度从色度分离到不同的通道中。
OpenCV 支持使用 cv2.cvtColor() 函数在色域之间转换图像。
cv2.cvtColor() 函数的命令如下 −
cv.cvtColor(src, code, dst)
Conversion Codes
转换受以下预定义的转换代码控制。
Sr.No. |
Conversion Code & Function |
1 |
cv.COLOR_BGR2BGRA 向 RGB 或 BGR 图像添加 Alpha 通道。 |
2 |
cv.COLOR_BGRA2BGR 从 RGB 或 BGR 图像中移除 Alpha 通道。 |
3 |
cv.COLOR_BGR2GRAY 在 RGB/BGR 和灰度之间转换。 |
4 |
cv.COLOR_BGR2YCrCb Convert RGB/BGR to luma-chroma |
5 |
cv.COLOR_BGR2HSV Convert RGB/BGR to HSV |
6 |
cv.COLOR_BGR2Lab 将 RGB/BGR 转换为 CIE Lab |
7 |
cv.COLOR_HSV2BGR 后退转换 HSV 到 RGB/BGR |
OpenCV Python - Morphological Transformations
基于形状图像上的简单操作称为形态变换。最常见的两种转换是 erosion and dilation 。
Erosion
腐蚀会消除前景对象的边界。类似于 2D 卷积,内核滑过图像 A。如果内核下的所有像素均为 1,则保留原始图像中的像素。
否则将其变为 0,从而导致腐蚀。丢弃所有边界附近的像素。此过程对去除白噪声很有用。
OpenCV 中 erode() 函数的命令如下 −
cv.erode(src, kernel, dst, anchor, iterations)
Dilation
它与腐蚀正好相反。此处,如果内核下的至少一个像素为 1,则像素元素为 1。因此,它增加了图像中的白色区域。
dilate() 函数的命令如下 −
cv.dilate(src, kernel, dst, anchor, iterations)
Parameters
dilate() 函数具有与 erode() 函数相同参数。这两个函数可以有 BorderType 和 borderValue 这两个其他可选参数。
BorderType 是图像边界的一种枚举类型(CONSTANT、REPLICATE、TRANSPERANT 等)
borderValue 用于恒定边界的情况。默认情况下,它为 0。
Example
下面给出了一个示例程序显示 erode() 和 dilate() 函数的使用 −
import cv2 as cv
import numpy as np
img = cv.imread('LinuxLogo.jpg',0)
kernel = np.ones((5,5),np.uint8)
erosion = cv.erode(img,kernel,iterations = 1)
dilation = cv.dilate(img,kernel,iterations = 1)
cv.imshow('Original', img)
cv.imshow('Erosion', erosion)
cv.imshow('Dialation', dilation)
OpenCV Python - Image Contours
轮廓是一条连接所有沿边界具有相同颜色或强度的连续点的曲线。轮廓非常适用于形状分析和对象检测。
Find Contour
在找到轮廓之前,我们应该应用阈值或 Canny 边缘检测。然后,通过使用 findContours() 方法,我们可以在二进制图像中找到轮廓。
使用 findContours() 函数的命令如下 −
cv.findContours(image, mode, method, contours)
Parameters
findContours() 函数的参数如下 −
-
image − 源,8 位单通道图像。
-
mode − 轮廓检索模式。
-
method − 轮廓逼近方法。
mode 参数的值枚举如下 −
-
cv.RETR_EXTERNAL − 仅提取最外侧轮廓。
-
cv.RETR_LIST − 提取所有轮廓,不建立任何层次关系。
-
cv.RETR_CCOMP − 提取所有轮廓,并将其组织到一个两级层次结构中。
-
cv.RETR_TREE − 提取所有轮廓,并重建嵌套轮廓的完整层次结构。
另一方面,近似方法可以是从以下方法中选取一个 −
-
cv.CHAIN_APPROX_NONE − 存储绝对所有轮廓点。
-
cv.CHAIN_APPROX_SIMPLE − 压缩水平、垂直和对角线线段,只保留其端点。
Draw Contour
在检测到轮廓矢量后,使用 cv.drawContours() 函数在原始图像上绘制轮廓。
cv.drawContours()函数的命令如下 −
cv.drawContours(image, contours, contourIdx, color)
Parameters
drawContours() 函数的参数如下 −
-
image − Destination image.
-
contours − 所有输入轮廓。每个轮廓存储为一个点矢量。
-
contourIdx − 指示要绘制的轮廓的参数。如果为负数,则绘制所有轮廓。
-
color − 轮廓的颜色。
Example
以下代码示例中绘制轮廓的输入图像有三个填充有黑色颜色的图形。
第一步,我们获取一个灰度图像,然后执行 Canny 边缘检测。
然后,我们在生成图像上调用 findContours() 函数。其结果是点矢量。然后,我们调用 drawContours() 函数。
完整代码如下 −
import cv2
import numpy as np
img = cv2.imread('shapes.png')
cv2.imshow('Original', img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
canny = cv2.Canny(gray, 30, 200)
contours, hierarchy = cv2.findContours(canny,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print("Number of Contours = " ,len(contours))
cv2.imshow('Canny Edges', canny)
cv2.drawContours(img, contours, -1, (0, 255, 0), 3)
cv2.imshow('Contours', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
OpenCV Python - Template Matching
模板匹配技术用于检测图像中与样本或模板图像匹配的一个或多个区域。
Cv.matchTemplate() 函数在 OpenCV 中定义用作此目的,对该函数的命令如下所示:
cv.matchTemplate(image, templ, method)
其中 image 为输入图像,其中要查找 templ(模板)模式。method 参数采用以下值之一 −
-
cv.TM_CCOEFF,
-
cv.TM_CCOEFF_NORMED, cv.TM_CCORR,
-
cv.TM_CCORR_NORMED,
-
cv.TM_SQDIFF,
-
cv.TM_SQDIFF_NORMED
此方法使模板图像在输入图像上滑动。这与卷积的过程相似,并将输入图像下的模板和补丁与模板图像进行比较。
它返回一个灰度图像,其中每个像素都表示它与模板的匹配程度。如果输入图像大小为 (WxH),并且模板图像大小为 (wxh),则输出图像的大小将为 (W-w+1, H-h+1)。因此,该矩形是模板的区域。
Example
在下面的示例中,将印度板球队队员 Virat Kohli 的脸部图像用作模板,与另一张描绘他和另一位印度板球队队员 M.S.Dhoni 合影的图像进行匹配。
以下程序使用 80% 的阈值并围绕匹配的脸部绘制一个矩形 −
import cv2
import numpy as np
img = cv2.imread('Dhoni-and-Virat.jpg',1)
cv2.imshow('Original',img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
template = cv2.imread('virat.jpg',0)
cv2.imshow('Template',template)
w,h = template.shape[0], template.shape[1]
matched = cv2.matchTemplate(gray,template,cv2.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where( matched >= threshold)
for pt in zip(*loc[::-1]):
cv2.rectangle(img, pt, (pt[0] + w, pt[1] + h), (0,255,255), 2)
cv2.imshow('Matched with Template',img)
OpenCV Python - Image Pyramids
有时,我们可能需要将图像转换为不同于其原始大小的大小。为此,您可以将图像放大(放大)或缩小(缩小)。
图像金字塔是一组图像(由单一原始图像构建),连续向下采样指定次数。
高斯金字塔用于对图像进行下采样,而拉普拉斯金字塔则使用分辨率较低的图像金字塔中的一张图像重建一张上采样的图像。
将金字塔视为一组图层。图像如下所示 −
金字塔较高层中的图像尺寸较小。要制作高斯金字塔中下一层的图像,我们将较低水平的图像与高斯核进行卷积。
\frac{1}{16}\begin{bmatrix}1 & 4 & 6 & 4 & 1 \\4 & 16 & 24 & 16 & 4 \\6 & 24 & 36 & 24 & 6 \\4 & 16 & 24 & 16 & 4 \\1 & 4 & 6 & 4 & 1\end{bmatrix}
现在移除所有偶数行的行和列。得到的图像将是其前身面积的 1/4。对原始图像进行此过程的迭代就会产生整个金字塔。
为了使图像变大,需要使用零填充列。首先,将图像放大至每个维度都为原来的两倍,获得新的偶数行,然后使用核进行卷积以近似缺失像素的值。
cv.pyrUp() 函数会使原始尺寸加倍; cv.pyrDown() 函数会使原始尺寸减半。
Example
以下程序根据用户输入的“I”或“o”分别调用 pyrUp() 和 pyrDown() 函数。
请注意,当我们减小图像尺寸时,图像的信息就会丢失。一旦缩小尺寸,再重新缩放回原始尺寸时,我们将丢失部分信息,且新图像的分辨率远低于原始图像。
import sys
import cv2 as cv
filename = 'chicky_512.png'
src = cv.imread(filename)
while 1:
print ("press 'i' for zoom in 'o' for zoom out esc to stop")
rows, cols, _channels = map(int, src.shape)
cv.imshow('Pyramids', src)
k = cv.waitKey(0)
if k == 27:
break
elif chr(k) == 'i':
src = cv.pyrUp(src, dstsize=(2 * cols, 2 * rows))
elif chr(k) == 'o':
src = cv.pyrDown(src, dstsize=(cols // 2, rows // 2))
cv.destroyAllWindows()
OpenCV Python - Image Addition
读取图像的图像对象本质上是一个二维或三维矩阵,具体取决于图像是否为灰度图像或 RGB 图像。
因此, cv2.add() 函数将两个图像矩阵相加,并返回另一个图像矩阵。
Example
以下代码读取两张图像并执行其二进制加法:
kalam = cv2.imread('kalam.jpg')
einst = cv2.imread('einstein.jpg')
img = cv2.add(kalam, einst)
cv2.imshow('addition', img)
Result
OpenCV 有一个 addWeighted() 函数来执行两个数组的加权和,而不是线性二进制加法。对应的命令如下:
Cv2.addWeighted(src1, alpha, src2, beta, gamma)
Parameters
addWeighted() 函数的参数如下:
-
src1 - 第一个输入数组。
-
alpha - 第一个数组元素的权重。
-
src2 - 大小和通道数与第一个数组相同的第二个输入数组。
-
beta - 第二个数组元素的权重。
-
gamma - 添加到每个和的标量。
此函数根据以下方程式将图像相加:
\mathrm{g(x)=(1-\alpha)f_{0}(x)+\alpha f_{1}(x)}
在上述示例中获得的图像矩阵用于执行加权和。
通过将 a 从 0 更改到 1,可以平滑地从一张图像过渡到另一张图像,以便它们融合在一起。
第一张图像的权重为 0.3,第二张图像的权重为 0.7。将余弦因子设置为 0.
addWeighted() 函数的命令如下:
img = cv2.addWeighted(kalam, 0.3, einst, 0.7, 0)
可以看出,与二进制加法相比,图像加法更加平滑。
OpenCV Python - Image Blending with Pyramids
通过使用图像金字塔可以最大程度减少图像的不连续性。这将产生无缝融合的图像。
采取以下步骤来实现最终结果 −
首先加载图像并为两幅图像寻找高斯金字塔。以下是执行此操作的程序 -
import cv2
import numpy as np,sys
kalam = cv2.imread('kalam.jpg')
einst = cv2.imread('einstein.jpg')
### generate Gaussian pyramid for first
G = kalam.copy()
gpk = [G]
for i in range(6):
G = cv2.pyrDown(G)
gpk.append(G)
# generate Gaussian pyramid for second
G = einst.copy()
gpe = [G]
for i in range(6):
G = cv2.pyrDown(G)
gpe.append(G)
从高斯金字塔获取相应的拉普拉斯金字塔。以下是执行此操作的程序 -
# generate Laplacian Pyramid for first
lpk = [gpk[5]]
for i in range(5,0,-1):
GE = cv2.pyrUp(gpk[i])
L = cv2.subtract(gpk[i-1],GE)
lpk.append(L)
# generate Laplacian Pyramid for second
lpe = [gpe[5]]
for i in range(5,0,-1):
GE = cv2.pyrUp(gpe[i])
L = cv2.subtract(gpe[i-1],GE)
lpe.append(L)
然后,在金字塔中的每个层中将第一张图像的左半部分与第二张图像的右半部分结合在一起。因此,该程序如下所示 −
# Now add left and right halves of images in each level
LS = []
for la,lb in zip(lpk,lpe):
rows,cols,dpt = la.shape
ls = np.hstack((la[:,0:int(cols/2)], lb[:,int(cols/2):]))
LS.append(ls)
最后,从这个联合金字塔中重建图像。因此,该程序如下所示 −
ls_ = LS[0]
for i in range(1,6):
ls_ = cv2.pyrUp(ls_)
ls_ = cv2.add(ls_, LS[i])
cv2.imshow('RESULT',ls_)
OpenCV Python - Fourier Transform
傅里叶变换通过将其分解为正弦分量和余弦分量来将图像从其空间域转换到其频域。
在数字图像的情况下,基本的灰度图像值通常在 0 到 255 之间。因此,傅里叶变换也需要是 Discrete Fourier Transform (DFT) 。它用于找到频域。
从数学上讲,二维图像的傅里叶变换表示如下 −
\mathrm{F(k,l)=\displaystyle\sum\limits_{i=0}^{N-1}\: \displaystyle\sum\limits_{j=0}^{N-1} f(i,j)\:e^{-i2\pi (\frac{ki}{N},\frac{lj}{N})}}
如果幅值在短时间内变化很快,你可以说它是一个高频信号。如果变化缓慢,则它是一个低频信号。
在图像的情况下,幅值在边缘点或噪声处发生急剧变化。因此,边缘和噪声是图像中的高频内容。如果幅值没有太大变化,则它是一个低频分量。
OpenCV 为此目的提供了 cv.dft() 和 cv.idft() 函数。
cv.dft() 执行一维或二维浮点数组的离散傅里叶变换。命令如下所示 −
cv.dft(src, dst, flags)
在此,
-
src − 可能是实数或复数的输入数组。
-
dst − 输出数组,其大小和类型取决于标志。
-
flags − 转换标志,表示 DftFlags 的组合。
cv.idft() 计算一维或二维数组的离散傅里叶逆变换。命令如下所示 −
cv.idft(src, dst, flags)
为了获得离散傅里叶变换,将输入图像转换为 np.float32 数据类型。然后使用获得的变换将零频率分量移到频谱的中心,从中计算幅度谱。
Example
下面是使用 Matplotlib 的程序,我们绘制原始图像和幅度谱 −
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('lena.jpg',0)
dft = cv.dft(np.float32(img),flags = cv.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
magnitude_spectrum = 20*np.log(cv.magnitude(dft_shift[:,:,0],dft_shift[:,:,1]))
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()
OpenCV Python - Capture Video from Camera
通过在 OpenCV 库中使用 VideoCapture() 函数,可以非常轻松地从相机在 OpenCV 窗口上实时捕捉视频流。
此函数需要设备索引作为参数。你的电脑可能连接了多个相机。它们从内置网络摄像头开始按索引编号排列。此函数返回一个 VideoCapture 对象。
cam = cv.VideoCapture(0)
打开摄像头后,我们可以借助 read() 函数从摄像头连续读取帧。
ret,frame = cam.read()
read() 函数读取下一个可用的帧,并返回一个值(True/False)。此帧现在呈现为 cvtColor() 函数所需的色彩空间,并在 OpenCV 窗口中显示。
img = cv.cvtColor(frame, cv.COLOR_BGR2RGB)
# Display the resulting frame
cv.imshow('frame', img)
你可以使用 imwrite() 函数将当前帧捕捉到图像文件中。
cv2.imwrite(“capture.png”, img)
OpenCV 提供了 VideoWriter() 函数来将摄像头实时流保存到视频文件中。
cv.VideoWriter( filename, fourcc, fps, frameSize)
fourcc 参数是视频编解码器的标准化代码。OpenCV 支持各种编解码器,如 DIVX、XVID、MJPG、X264 等。fps 和 framesize 参数取决于视频采集设备。
VideoWriter() 函数返回一个 VideoWrite 流对象,捕获的帧会连续写入其中,形成一个循环。最后,释放帧和 VideoWriter 对象,以便最终完成视频的创建。
Example
以下示例会从内置网络摄像头读取实时视频,并将其保存到 ouput.avi 文件中。
import cv2 as cv
cam = cv.VideoCapture(0)
cc = cv.VideoWriter_fourcc(*'XVID')
file = cv.VideoWriter('output.avi', cc, 15.0, (640, 480))
if not cam.isOpened():
print("error opening camera")
exit()
while True:
# Capture frame-by-frame
ret, frame = cam.read()
# if frame is read correctly ret is True
if not ret:
print("error in retrieving frame")
break
img = cv.cvtColor(frame, cv.COLOR_BGR2RGB)
cv.imshow('frame', img)
file.write(img)
if cv.waitKey(1) == ord('q'):
break
cam.release()
file.release()
cv.destroyAllWindows()
OpenCV Python - Play Video from File
VideoCapture() 函数也可以从视频文件中而不是摄像机中提取帧。因此,我们只需要把摄像机索引替换为在 OpenCV 窗口中播放的视频文件的名称。
video=cv2.VideoCapture(file)
虽然这对于渲染视频文件来说已足够,但如果它带有音频,音频不会一同播放。为此,你需要安装 ffpyplayer 模块。
FFPyPlayer
FFPyPlayer 是 FFmpeg 库的 Python 绑定,可用于播放和编写媒体文件。要安装,请使用 pip 安装器实用程序,并使用以下命令。
pip3 install ffpyplayer
该模块中 MediaPlayer 对象的 get_frame() 方法返回一个音频帧,它将与从视频文件中读取的每个帧一起播放。
以下是播放视频文件及其音频的完整代码 −
import cv2
from ffpyplayer.player import MediaPlayer
file="video.mp4"
video=cv2.VideoCapture(file)
player = MediaPlayer(file)
while True:
ret, frame=video.read()
audio_frame, val = player.get_frame()
if not ret:
print("End of video")
break
if cv2.waitKey(1) == ord("q"):
break
cv2.imshow("Video", frame)
if val != 'eof' and audio_frame is not None:
#audio
img, t = audio_frame
video.release()
cv2.destroyAllWindows()
OpenCV Python - Extract Images from Video
视频只不过是由一系列帧组成的,且每个帧都是一幅图像。使用 OpenCV 时,可以通过执行 imwrite() 函数直至视频结束来提取组成视频文件的所有帧。
cv2.read() 函数将返回下一个可用的帧。该函数还会提供一个返回值,该值在流结束前一直保持为 true。在此处,循环内部将对计数器递增,并将其用作文件名。
以下程序演示如何从视频中提取图像:
import cv2
import os
cam = cv2.VideoCapture("video.avi")
frameno = 0
while(True):
ret,frame = cam.read()
if ret:
# if video is still left continue creating images
name = str(frameno) + '.jpg'
print ('new frame captured...' + name)
cv2.imwrite(name, frame)
frameno += 1
else:
break
cam.release()
cv2.destroyAllWindows()
OpenCV Python - Video from Images
在上一章中,我们使用 VideoWriter() 函数将相机的实时流保存为视频文件。为了将多张图像拼接成一个视频,我们应使用该函数。
首先,确保所有必需的图像都放在一个文件夹中。内置的 glob 模块中的 Python 的 glob() 函数构建一个图像数组,以便我们可以对其进行迭代。
从文件夹中的图像中读取图像对象,并添加到图像数组。
以下程序说明如何将多个图像拼接成一个视频。
import cv2
import numpy as np
import glob
img_array = []
for filename in glob.glob('*.png'):
img = cv2.imread(filename)
height, width, layers = img.shape
size = (width,height)
img_array.append(img)
通过使用 VideoWriter() 函数创建一个视频流,将图像数组的内容写入到其中。下面给出了该程序。
out = cv2.VideoWriter('video.avi',cv2.VideoWriter_fourcc(*'DIVX'), 15, size)
for i in range(len(img_array)):
out.write(img_array[i])
out.release()
您应该在当前文件夹中找到名为 ‘video.avi’ 的文件。
OpenCV Python - Face Detection
OpenCV 使用 Haar 基于特征的级联分类器进行对象检测。它是一种基于机器学习的算法,级联函数由大量正负图像训练。然后,它用于检测其他图像中的对象。该算法使用了级联分类器的概念。
人脸、眼睛等预训练分类器可从 https://github.com 下载
对于以下示例,请从该 URL 下载并 copy haarcascade_frontalface_default.xml 和 haarcascade_eye.xml 。然后,加载将用于灰度模式人脸检测的输入图像。
CascadeClassifier 类的 DetectMultiScale() 方法检测输入图像中的对象。它以矩形形式返回检测到的人脸的位置及其尺寸 (x,y,w,h)。一旦获得这些位置,我们就可以将其用于眼睛检测,因为眼睛始终在人脸上!
Example
人脸检测的完整代码如下 −
import numpy as np
import cv2
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')
img = cv2.imread('Dhoni-and-virat.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x,y,w,h) in faces:
img = cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
roi_gray = gray[y:y+h, x:x+w]
roi_color = img[y:y+h, x:x+w]
eyes = eye_cascade.detectMultiScale(roi_gray)
for (ex,ey,ew,eh) in eyes:
cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
OpenCV Python - Meanshift and Camshift
在本章中,我们一起来了解一下 OpenCV-Python 中的均值漂移和 CAMShift。首先,我们来了解一下什么是均值漂移。
Meanshift
均值漂移算法可以识别数据集中数据点高度集中或集群的位置。该算法会在每个数据点放置一个内核并对它们求和,以做出一个 Kernel Density Estimation (KDE)。
KDE 会有高和低数据点密度的区域,具有相应的值。均值漂移是一种很有用的方法,可以用来跟踪视频中某个指定对象。
视频中的每个实例都会以该帧的像素分布形式被检查。初始窗口(即目标区域 (ROI) )通常是正方形或圆形。为此,会通过硬编码指定位置,并标识最高像素分布区域。
随着视频播放,ROI 窗口会向着最高像素分布区域移动。移动方向取决于我们的跟踪窗口中心与该窗口内所有 k 像素的质心之间的差值。
要在 OpenCV 中使用均值漂移,首先要查找我们目标的直方图(其中仅考虑色调),然后可以针对每个帧 反投影其目标,以计算均值漂移。我们还需要提供 ROI 窗口的初始位置。
我们反复计算直方图的反投影,并计算均值漂移以获得跟踪窗口的新位置。随后,我们会使用它的尺寸在帧上绘制一个矩形。
Functions
程序中使用的 OpenCV 函数包括:
-
cv.calcBackProject() − 计算直方图的反投影。
-
cv.meanShift() − 使用初始搜索窗口和迭代搜索算法的停止准则的反向投影对象直方图。
Example
以下是均值漂移的示例程序:
import numpy as np
import cv2 as cv
cap = cv.VideoCapture('traffic.mp4')
ret,frame = cap.read()
# dimensions of initial location of window
x, y, w, h = 300, 200, 100, 50
tracker = (x, y, w, h)
region = frame[y:y+h, x:x+w]
hsv_reg = cv.cvtColor(region, cv.COLOR_BGR2HSV)
mask = cv.inRange(hsv_reg, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
reg_hist = cv.calcHist([hsv_reg],[0],mask,[180],[0,180])
cv.normalize(reg_hist,reg_hist,0,255,cv.NORM_MINMAX)
# Setup the termination criteria
criteria = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
while(1):
ret, frame = cap.read()
if ret == True:
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
dst = cv.calcBackProject([hsv],[0],reg_hist,[0,180],1)
# apply meanshift
ret, tracker = cv.meanShift(dst, tracker, criteria)
# Draw it on image
x,y,w,h = tracker
img = cv.rectangle(frame, (x,y), (x+w,y+h), 255,2)
cv.imshow('img',img)
k = cv.waitKey(30) & 0xff
if k==115:
cv.imwrite('capture.png', img)
if k == 27:
break
在运行程序时,均值漂移算法会将我们的窗口移动到密度最大的新位置。
Camshift
Meanshift 算法的一个缺点是,跟踪窗口的大小保持不变,与对象到摄像机的距离无关。此外,只有当窗口处于该对象区域内时,窗口才会跟踪该对象。因此,我们必须手动对窗口进行硬编码,并且应该小心进行。
CAMshift(代表 Continuously Adaptive Meanshift )给出了这些问题的解决方案。一旦 meanshift 收敛,Camshift 算法就会更新窗口的大小,使得跟踪窗口的大小可能会改变,甚至旋转以更好地与跟踪对象的动作相关联。
在以下代码中,使用了 camshift() 函数,而不是 meanshift() 函数。
首先,它使用 meanShift 查找对象中心,然后调整窗口大小并找到最佳旋转。该函数返回对象的位置、大小和方向。使用 polylines() 绘制函数在帧上绘制位置。
OpenCV Python - Feature Detection
在图像处理中,特征是图像关键区域的数学描述。它们是图像视觉内容的矢量表示。
特征使得能够对它们进行数学运算。各种计算机视觉应用包括目标检测、运动估计、分割、图像对齐等。
任何图像中的突出特征包括边缘、角或图像的部分。OpenCV 支持 Haris corner detection 和 Shi-Tomasi corner detection 算法。OpenCV 库还提供用于实现 SIFT (尺度不变特征变换)、 SURF (加速稳健特征)和角检测快速算法的功能。
Harris 和 Shi-Tomasi 算法是旋转不变的。即使图像旋转,我们也可以找到相同的角。但是当图像被放大时,图像中的某个角可能不再是角。下图描述了这一点。
D.Lowe 的新算法 Scale Invariant Feature Transform (SIFT)提取关键点并计算其描述符。
这是通过以下步骤实现的 −
-
Scale-space Extrema Detection.
-
Keypoint Localization.
-
Orientation Assignment.
-
Keypoint Descriptor.
-
Keypoint Matching.
至于 OpenCV 中 SIFT 的实现,它从加载图像并将其转换为灰度开始。 cv.SHIFT_create() 函数创建 SIFT 对象。
OpenCV Python - Feature Matching
OpenCV 为特征匹配提供了两种技术。蛮力匹配和 FLANN 匹配器技术。
Example
以下示例使用蛮力方法
import numpy as np
import cv2
img1 = cv2.imread('lena.jpg')
img2 = cv2.imread('lena-test.jpg')
# Convert it to grayscale
img1_bw = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
img2_bw = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
orb = cv2.ORB_create()
queryKeypoints, queryDescriptors = orb.detectAndCompute(img1_bw,None)
trainKeypoints, trainDescriptors = orb.detectAndCompute(img2_bw,None)
matcher = cv2.BFMatcher()
matches = matcher.match(queryDescriptors,trainDescriptors)
img = cv2.drawMatches(img1, queryKeypoints,
img2, trainKeypoints, matches[:20],None)
img = cv2.resize(img, (1000,650))
cv2.imshow("Feature Match", img)
OpenCV Python - Digit Recognition with KNN
KNN 代表 K-Nearest Neighbour ,是一种基于监督学习的机器学习算法。它试图将新数据点放入与可用类别最相似的类别中。所有可用数据都分类为不同的类别,并根据相似性将新数据点放入其中一个类别。
KNN 算法遵循以下原理:
-
根据要检查的邻居数量,最好选择一个奇数作为 K。
-
Calculate their Euclidean distance.
-
根据计算的欧氏距离获取 K 个最近邻居。
-
计算每个类别中的数据点数量。
-
数据点最多的类别是新数据点被分类到的类别。
作为使用 OpenCV 实现 KNN 算法的示例,我们将使用以下包含 5000 幅手写数字图像(每幅图像像素为 20X20)的 digits.png。
第一个任务是将该图像分成 5000 个数字。这是我们的特征集。将其转换为 NumPy 数组。该程序如下:
import numpy as np
import cv2
image = cv2.imread('digits.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
fset=[]
for i in np.vsplit(gray,50):
x=np.hsplit(i,100)
fset.append(x)
NP_array = np.array(fset)
现在,我们将这些数据分成训练集和测试集,每个的大小为 (2500,20x20),如下所示:
trainset = NP_array[:,:50].reshape(-1,400).astype(np.float32)
testset = NP_array[:,50:100].reshape(-1,400).astype(np.float32)
接下来,我们必须为每个数字创建 10 个不同的标签,如下所示:
k = np.arange(10)
train_labels = np.repeat(k,250)[:,np.newaxis]
test_labels = np.repeat(k,250)[:,np.newaxis]
我们现在可以开始 KNN 分类。创建分类器对象并训练数据。
knn = cv2.ml.KNearest_create()
knn.train(trainset, cv2.ml.ROW_SAMPLE, train_labels)
将 k 值选择为 3,获取分类器的输出。
ret, output, neighbours, distance = knn.findNearest(testset, k = 3)
比较输出与测试标签以检查分类器的性能和准确性。
程序在准确检测手写数字方面显示了 91.64% 的准确性。
result = output==test_labels
correct = np.count_nonzero(result)
accuracy = (correct*100.0)/(output.size)
print(accuracy)