OpenCV-Python实战(19)——OpenCV与深度学习的碰撞
Posted 盼小辉丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV-Python实战(19)——OpenCV与深度学习的碰撞相关的知识,希望对你有一定的参考价值。
OpenCV-Python实战(19)——OpenCV与深度学习的碰撞
0. 前言
OpenCV
中包含深度神经网络 (Deep Neural Networks, DNN) 模块,可以使用深度神经网络实现前向计算(推理阶段),使用一些流行的深度学习框架进行预训练的网络(例如 Caffe、TensorFlow、Pytorch、Darknet 等)就可以轻松用在 OpenCV
项目中了。
在《深度学习简介与入门示例》中,我们已经介绍了许多流行的深度学习网络架构。在本文中,我们将学习如何将这些架构应用于目标检测和图像分类。
1. cv2.dnn.blobFromImage() 函数详解
OpenCV
中深度神经网络为了执行前向计算,其输入应该是一个 blob
,blob
可以看作是经过预处理(包括缩放、裁剪、归一化、通道交换等)以馈送到网络的图像集合。
在 OpenCV
中,使用 cv2.dnn.blobFromImage()
构建 blob
:
# 图像加载
image = cv2.imread("example.jpg")
# 利用 image 创建 4 维 blob
blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), [104., 117., 123.], False, False)
上述代码意味着我们进行以下预处理:调整为 300 x 300 的 BGR 图像、分别对蓝色、绿色和红色通道执行 (104, 117, 123) 均值减法 。
接下来,我们将可以将 blob
设置为输入并获得检测结果如下:
# 将 blob 设置为输入并获取检测结果
net.setInput(blob)
detections = net.forward()
接下来,首先详细讲解 cv2.dnn.blobFromImage()
和 cv2.dnn.blobFromImages()
函数。理解这些函数后,对于后续项目的构建将很有帮助。
cv2.dnn.blobFromImage()
函数的用法如下:
retval = cv2.dnn.blobFromImage(image[, scalefactor[, size[, mean[, swapRB[, crop[, ddepth]]]]]])
此函数从 image
创建一个四维 blob
,参数含义如下所示:
参数 | 解释 |
---|---|
image | 要预处理的输入图像 |
scalefactor | 缩放因子,用于缩放像素值,默认值为 1.0,即不执行缩放 |
size | 输出图像的尺寸 |
mean | 将在图像中减去的标量,如果使用均值减法,在 swapRB =True 时,结果为 (mean-R, mean-G, mean-B) |
swapRB | 若此标志设置为 True,此标志可用于交换图像中的 R 和 B 通道 |
crop | 此标志用于指示在调整大小后是否会裁剪图像 |
ddepth | 输出 blob 的深度,可选值包括 CV_32F 或 CV_8U |
如果 crop=False
,则在不裁剪的情况下执行图像的大小调整;否则,将首先调整大小,然后从中心裁剪图像
cv2.dnn.blobFromImage()
函数的默认值如下:scalefactor=1.0, size = Size(), mean = Scalar(), swapRB = false, crop = false, ddepth = CV_32F。
cv2.dnn.blobFromImages()
函数的用法如下:
retval = cv.dnn.blobFromImages(images[, scalefactor[, size[, mean[, swapRB[, crop[, ddepth]]]]]])
此函数可以从多个图像创建一个四维 blob
,通过这种方式,可以对整个网络执行一次前向计算获得多个图像的输出:
# 创建图像列表
images = []
for img in glob.glob('*.png'):
images.append(cv2.imread(img))
blob_images = cv2.dnn.blobFromImages(images, 1.0, (300, 300), [104., 117., 123.], False, False)
# 前向计算
net.setInput(blob_images)
detections = net.forward()
为了测试 cv2.dnn.blobFromImage()
函数,首先加载一个 BGR 图像,然后使用 cv2.dnn.blobFromImage()
函数创建一个四维 blob
。然后,我们编写 get_image_from_blob()
函数,该函数可用于执行逆预处理变换以再次获取输入图像,以更好地理解 cv2.dnn.blobFromImage()
函数的预处理:
# 加载图像
image = cv2.imread("example.png")
# 调用 cv2.dnn.blobFromImage() 函数
blob_image = cv2.dnn.blobFromImage(image, 1.0, (300, 300), [104., 117., 123.], False, False)
# blob_image 的尺寸为 (1, 3, 300, 300)
print(blob_image.shape)
def get_image_from_blob(blob_img, scalefactor, dim, mean, swap_rb, mean_added):
images_from_blob = cv2.dnn.imagesFromBlob(blob_img)
image_from_blob = np.reshape(images_from_blob[0], dim) / scalefactor
image_from_blob_mean = np.uint8(image_from_blob)
image_from_blob = image_from_blob_mean + np.uint8(mean)
if mean_added is True:
if swap_rb:
image_from_blob = image_from_blob[:, :, ::-1]
return image_from_blob
else:
if swap_rb:
image_from_blob_mean = image_from_blob_mean[:, :, ::-1]
return image_from_blob_mean
# 从 blob 中获取不同的图像
# img_from_blob 图像对应于调整为 (300,300) 的原始 BGR 图像,并且已经添加了通道均值
img_from_blob = get_image_from_blob(blob_image, 1.0, (300, 300, 3), [104., 117., 123.], False, True)
# img_from_blob_swap 图像对应于调整大小为 (300,300) 的原始 RGB 图像
# img_from_blob_swap 交换了蓝色和红色通道,并且已经添加了通道均值
img_from_blob_swap = get_image_from_blob(blob_image, 1.0, (300, 300, 3), [104., 117., 123.], True, True)
# img_from_blob_mean 图像对应于调整大小为 (300,300) 的原始 BGR 图像,其并未添加通道均值
img_from_blob_mean = get_image_from_blob(blob_image, 1.0, (300, 300, 3), [104., 117., 123.], False, False)
# img_from_blob_mean_swap 图像对应于调整为 (300,300) 的原始 RGB 图像
# img_from_blob_mean_swap 交换了蓝色和红色通道,并未添加通道均值
img_from_blob_mean_swap = get_image_from_blob(blob_image, 1.0, (300, 300, 3), [104., 117., 123.], True, False)
# 可视化
def show_img_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(1, 4, pos)
plt.imshow(img_RGB)
plt.title(title, fontsize=10)
plt.axis('off')
show_img_with_matplotlib(img_from_blob, "img from blob " + str(img_from_blob.shape), 1)
show_img_with_matplotlib(img_from_blob_swap, "img from blob swap " + str(img_from_blob.shape), 2)
show_img_with_matplotlib(img_from_blob_mean, "img from blob mean " + str(img_from_blob.shape), 3)
show_img_with_matplotlib(img_from_blob_mean_swap, "img from blob mean swap " + str(img_from_blob.shape), 4)
程序的输出如下图所示:
接下来,我们首先加载目标文件夹中的所有图像,然后使用 cv2.dnn.blobFromImages()
函数创建一个四维 blob
,同样,我们编写 get_images_from_blob()
函数用于执行逆预处理变换以再次获取输入图像。
get_images_from_blob 函数的代码如下:
def get_images_from_blob(blob_imgs, scalefactor, dim, mean, swap_rb, mean_added):
images_from_blob = cv2.dnn.imagesFromBlob(blob_imgs)
imgs = []
for image_blob in images_from_blob:
image_from_blob = np.reshape(image_blob, dim) / scalefactor
image_from_blob_mean = np.uint8(image_from_blob)
image_from_blob = image_from_blob_mean + np.uint8(mean)
if mean_added is True:
if swap_rb:
image_from_blob = image_from_blob[:, :, ::-1]
imgs.append(image_from_blob)
else:
if swap_rb:
image_from_blob_mean = image_from_blob_mean[:, :, ::-1]
imgs.append(image_from_blob_mean)
return imgs
如前所述,get_images_from_blob() 函数使用 OpenCV cv2.dnn.imagesFromBlob() 函数从 blob 返回图像。在脚本中,我们使用此函数从 blob 中获取不同的图像,如下所示:
# 加载图像并构造图像列表
images = []
for img in glob.glob('*.png'):
images.append(cv2.imread(img))
# 调用 cv2.dnn.blobFromImages() 函数
blob_images = cv2.dnn.blobFromImages(images, 1.0, (300, 300), [104., 117., 123.], False, False)
# 打印形状
print(blob_images.shape)
# 从 blob 中获取不同的图像
# imgs_from_blob 图像对应于调整大小为 (300,300) 的原始 BGR 图像,并且已经添加了通道均值
imgs_from_blob = get_images_from_blob(blob_images, 1.0, (300, 300, 3), [104., 117., 123.], False, True)
# img_from_blob_swap 图像对应于调整大小为 (300,300) 的原始 RGB 图像
# img_from_blob_swap 交换了蓝色和红色通道,并且已经添加了通道均值
imgs_from_blob_swap = get_images_from_blob(blob_images, 1.0, (300, 300, 3), [104., 117., 123.], True, True
# img_from_blob_mean 图像对应于调整大小为 (300,300) 的原始 BGR 图像,其并未添加通道均值
imgs_from_blob_mean = get_images_from_blob(blob_images, 1.0, (300, 300, 3), [104., 117., 123.], False, False)
# img_from_blob_mean_swap 图像对应于调整为 (300,300) 的原始 RGB 图像
# img_from_blob_mean_swap 交换了蓝色和红色通道,并未添加通道均值
imgs_from_blob_mean_swap = get_images_from_blob(blob_images, 1.0, (300, 300, 3), [104., 117., 123.], True, False)
# 可视化,show_img_with_matplotlib() 函数与上例相同
for i in range(len(images)):
show_img_with_matplotlib(imgs_from_blob[i], "img from blob " + str(imgs_from_blob[i].shape), i * 4 + 1)
show_img_with_matplotlib(imgs_from_blob_swap[i], "img from blob swap " + str(imgs_from_blob_swap[i].shape), i * 4 + 2)
show_img_with_matplotlib(imgs_from_blob_mean[i], "img from blob mean " + str(imgs_from_blob_mean[i].shape), i * 4 + 3)
show_img_with_matplotlib(imgs_from_blob_mean_swap[i], "img from blob mean swap " + str(imgs_from_blob_mean_swap[i].shape), i * 4 + 4)
程序输出如下图所示:
cv2.dnn.blobFromImage()
和 cv2.dnn.blobFromImages()
的最后一个重要的参数是 crop
参数,它指示图像是否需要裁剪,在 crop = True
的情况下,图像从中心进行裁剪。为了更好的理解 OpenCV
在 cv2.dnn.blobFromImage()
和 cv2.dnn.blobFromImages()
函数中执行的裁剪,我们编写 get_cropped_img()
函数进行复刻:
def get_cropped_img(img):
img_copy = img.copy()
size = min(img_copy.shape[1], img_copy.shape[0])
x1 = int(0.5 * (img_copy.shape[1] - size))
y1 = int(0.5 * (img_copy.shape[0] - size))
return img_copy[y1:(y1 + size), x1:(x1 + size)]
如上所示,裁剪图像的大小基于原始图像的最小边。
images = []
for img in glob.glob('*.png'):
images.append(cv2.imread(img))
# 使用 get_cropped_img() 函数进行裁剪
cropped_img = get_cropped_img(images[0])
# crop = False 不进行裁剪
blob_images = cv2.dnn.blobFromImages(images, 1.0, (300, 300), [104., 117., 123.], False, False)
print(blob_images)
# crop = True 进行裁剪
blob_blob_images_cropped = cv2.dnn.blobFromImages(images, 1.0, (300, 300), [104., 117., 123.], False, True)
imgs_from_blob = get_images_from_blob(blob_images, 1.0, (300, 300, 3), [104., 117., 123.], False, True)
imgs_from_blob_cropped = get_images_from_blob(blob_blob_images_cropped, 1.0, (300, 300, 3), [104., 117., 123.], False, True)
# 可视化,show_img_with_matplotlib() 函数与上例相同
for i in range(len(images)):
show_img_with_matplotlib(imgs_from_blob[i], "img from blob ".format(i) + str(imgs_from_blob[i].shape), i + 1)
show_img_with_matplotlib(imgs_from_blob_cropped[i], "img from blob cropped ".format(i) + str(imgs_from_blob[i].shape), i + 5)
程序输出结果如下图所示,可以看到裁剪保持了图像的纵横比:
2. OpenCV DNN 人脸检测器
接下来,将多个图像馈送到网络进行前向计算输出人脸检测结果,以更好的理解 cv2.dnn.blobFromImages()
函数。
首先查看当 cv2.dnn.blobFromImages()
函数中 crop=True
时的检测效果:
net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000_fp16.caffemodel")
# 加载图片,构建 blob
img_1 = cv2.imread('example_1.png')
img_2 = cv2.imread('example_2.png')
images = [img_1.copy(), img_2.copy()]
blob_images = cv2.dnn.blobFromImages(images, 1.0, (300, 300), [104., 117., 123.], False, False)
# 前向计算
net.setInput(blob_images)
detections = net.forward()
for i in range(0, detections.shape[2]):
# 首先,获得检测结果所属的图像
img_id = int(detections[0, 0, i, 0])
# 获取预测的置信度
confidence = detections[0, 0, i, 2]
# 过滤置信度较低的预测
if confidence > 0.25:
# 获取当前图像尺寸
(h, w) = images[img_id].shape[:2]
# 获取检测的 (x, y) 坐标
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
(startX, startY, endX, endY) = box.astype("int")
# 绘制边界框和概率
text = ":.2f%".format(confidence * 100)
y = startY - 10 if startY - 10 > 10 else startY + 10
cv2.rectangle(images[img_id], (startX, startY), (endX, endY), (0, 0, 255), 2)
cv2.putText(images[img_id], text, (startX, y), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 2)
# 可视化
show_img_with_matplotlib(img_1, "input img 1", 1)
show_img_with_matplotlib(img_2, "input img 2", 2)
show_img_with_matplotlib(images[0], "output img 1", 3)
show_img_with_matplotlib(images[1], "output img 2", 4)
接下来使用保持纵横比进行裁剪后的检测结果,可以看到纵横比保持的情况下,检测到的置信度更高:
《Nuitka打包实战指南》实战打包OpenCV-Python
OpenCV-Python实战(番外篇)——利用 SVM 算法识别手写数字
OpenCV-Python实战(番外篇)——OpenCV实现图像卡通化
OpenCV-Python实战(番外篇)——OpenCVNumPy和Matplotlib直方图比较