一、目标

用OpenCV的DNN模块加载Googlenet模型用来识别图像。

运用cnn的人脸识别 cnn实现图像识别_数据

二、DNN模块介绍

在OpenCV3.3版本发布中把DNN模块从扩展模块移到了OpenCV正式发布模块中,当前DNN模块最早来自Tiny-dnn,可以加载预先训练好的Caffe模型数据,OpenCV做了近一步扩展支持所有主流的深度学习框架训练生成与导出模型数据加载,常见的有如下:

  • Caffe
  • TensorFlow
  • Torch/pytorch OpenCV中DNN模块已经支持与测试过这些常见的网络模块:
  • Alexnet
  • Googlenet
  • ResNet
  • SqueezeNet 。。。。。 OpenCV通过支持加载这些预先训练好的模型,实现图像分类、对象检测、语义分割、风格迁移等功能。支持Android/iOS等移动端平台开发。下面我们就以OpenCV3.3 使用Caffe的GoogleNet数据模型为例,实现对图像常见分类,OpenCV3.3的DNN模块使用的模型支持1000种常见图像分类、googlenet深度学习网络模型是2014图像分类比赛的冠军、首先是下载相关的数据模型文件
  • bvlc_googlenet.caffemodel
  • bvlc_googlenet.prototxt 第一个是模型的网络的结构文本,第二个是已经训练好的参数结果 其中prototxt是一个文本的JSON文件、一看就明白啦,另外一个文件二进制文件。文本文件只有你下载了OpenCV3.3解压缩之后就会在对应的目录发现。模型文件需要从以下地址下载即可: http://dl.caffe.berkeleyvision.org/bvlc_googlenet.caffemodel

三、实现

单个图像的处理

# 导入工具包
import utils_paths
import numpy as np
import cv2
'''
第一步:训练模型、导入模型
第二步:对图像进行预处理
第三步:前项传播,给出预测结果,可视化输出
'''
# 标签文件处理
# rows = open("synset_words.txt").read().strip().split("\n")
rows_1 = open("synset_words.txt").read()
rows_2 = rows_1.strip()
rows = rows_2.split("\n")
classes = [r[r.find(" ") + 1:].split(",")[0] for r in rows]
#将rows中的每一项取出,找到其中的第一个空格的位置,从这个位置再往前的一个位置开始遍历,按逗号分隔(结果是两个字符),返回第一个字符

# Caffe所需配置文件
net = cv2.dnn.readNetFromCaffe("bvlc_googlenet.prototxt",
	"bvlc_googlenet.caffemodel") #第一个参数是模型文件,第二个参数是模型参数

# 图像路径
imagePaths = sorted(list(utils_paths.list_images("images/")))

# 图像数据预处理
image = cv2.imread(imagePaths[0])
resized = cv2.resize(image, (224, 224))
# image scalefactor size mean swapRB 
blob = cv2.dnn.blobFromImage(resized, 1, (224, 224), (104, 117, 123)) #blob就代表输入数据
#参数分别是:输入数据、缩放因子、(w,h)、mean均值(RGB分别三通道的均值)这三个数是inagenet的默认均值
print("First Blob: {}".format(blob.shape)) #caffe中shape的返回值是batch_size,channel,h,w其他的可能不一样

# 得到预测结果
net.setInput(blob) #给网络(net)指定输入数据
preds = net.forward() #前向传播

# 排序,从one-hot-coding中排序,取分类可能性最大的
idx = np.argsort(preds[0])[::-1][0]
#这里的preds是二维数组(1,1000)这里的preds[0]代表取第一组(其实只有一组,但是不加这个的话不行)
#这句的意思就是取preds第一组的数据,然后整体从后到前(升序)排序,然后倒序排放,取第一个元素
text = "Label: {}, {:.2f}%".format(classes[idx],
	preds[0][idx] * 100)
cv2.putText(image, text, (5, 25),  cv2.FONT_HERSHEY_SIMPLEX,
	0.7, (0, 0, 255), 2)

# 显示
cv2.imshow("Image", image)
cv2.waitKey(0)

图像路径的处理代码

import os


image_types = (".jpg", ".jpeg", ".png", ".bmp", ".tif", ".tiff")


def list_images(basePath, contains=None):
    # return the set of files that are valid
    return list_files(basePath, validExts=image_types, contains=contains)


def list_files(basePath, validExts=None, contains=None):
    # loop over the directory structure
    for (rootDir, dirNames, filenames) in os.walk(basePath):
        # loop over the filenames in the current directory
        for filename in filenames:
            # if the contains string is not none and the filename does not contain
            # the supplied string, then ignore the file
            if contains is not None and filename.find(contains) == -1:
                continue

            # determine the file extension of the current file
            ext = filename[filename.rfind("."):].lower()

            # check to see if the file is an image and should be processed
            if validExts is None or ext.endswith(validExts):
                # construct the path to the image and yield it
                imagePath = os.path.join(rootDir, filename)
                yield imagePath

os.walk(top, topdown=True, οnerrοr=None, followlinks=False):主要用来遍历一个目录内各个子目录和子文件。 可以得到一个三元tupple(dirpath, dirnames, filenames),

  • 第一个为起始路径,第二个为起始路径下的文件夹,第三个是起始路径下的文件。
  • dirpath 是一个string,代表目录的路径,
  • dirnames 是一个list,包含了dirpath下所有子目录的名字。
  • filenames 是一个list,包含了非目录文件的名字。 这些名字不包含路径信息,如果需要得到全路径,需要使用os.path.join(dirpath, name).

find(".")与rfind(".")的区别

  • find是从左往右数找到第一个.返回下标
  • rfind是从右往左数找到第一个.返回下标
  • 下标都是从左往右的

str.lower() 将字符串中的大写字母转换为小写字母

a = "Hello World"
print(a.lower())

>>>hello world
string.endswith(str, beg=[0, end=len(string)])
参数说明:
  • string: 被检测的字符串
  • str:指定的字符或者子字符串(可以使用元组,会逐一匹配)
  • beg:设置字符串检测的起始位置(可选,从左数起)
  • end:设置字符串检测的结束位置(可选,从左数起) 如果存在参数 beg 和 end,则在指定范围内检查,否则在整个字符串中检查
返回值:
  • 如果检测到字符串,则返回True,否则返回False。
yeild与return的区别:
  • return:在程序函数中返回某个值,返回之后函数不在继续执行,彻底结束。
  • yield: 带有yield的函数是一个迭代器,函数返回某个值时,会停留在某个位置,返回函数值后,会在前面停留的位置继续执行,直到程序结束。就是说当执行到yeild时返回当前需要返回的东西后,遇到next(g)时,会从上次yeild之后的位置继续执行,第一次执行的值不被保留(可能会造成某些值为None的情况)

np.argsort使用方法:y=x.argsort()函数是将x中的元素从小到大排列,提取其对应的index(索引),然后输出到y。 首先,看一下[-1]、[:-1]、[::-1]、[2::-1]的用法:

import numpy as np
a=[1,2,3,4,5]
print(a)
print(a[-1]) ###取最后一个元素
print(a[:-1])  ### 除了最后一个取全部
print(a[::-1]) ### 取从后向前(相反)的元素
print(a[2::-1]) ### 取从下标为2的元素翻转读取
####输出:
[1, 2, 3, 4, 5]
5
[1, 2, 3, 4]
[5, 4, 3, 2, 1]
[3, 2, 1]
#--------------------------------------------
x=np.array([1,4,3,-1,6,9])
#排序后的结果是-1,1,3,4,6,9
#在原数组中对应的索引是3,0,2,1,4,5
y=x.argsort()
print(y)
####输出:
[3 0 2 1 4 5]

批次图像的处理

import utils_paths
import numpy as np
import cv2
rows = open("synset_words.txt").read().strip().split("\n")
classes = [r[r.find(" ") + 1:].split(".")[0] for r in rows]
#配置caffe所需文件
net = cv2.dnn.readNetFromCaffe("bvlc_googlenet.prototxt",
	"bvlc_googlenet.caffemodel")
#设置图像的路径
imagePaths = sorted(list(utils_paths.list_images("images/")))
#图像数据的预处理
# image = cv2.imread(imagePaths[0])
# resized = cv2.resize(image,(224,224))
# blob = cv2.dnn.blobFromImage(resized,1,(224,224),(104,117,123))
# print("First Blob:".format(blob.shape))

# #得到预测结果:
# net.setInput(blob)
# preds = net.forward()
#
# idx = np.argsort(preds[0])[::-1][0]
# text = "Label:{}, {:.2f}%".format(classes[idx],preds[0][idx] * 100)
# cv2.putText(image,text,(5,25),cv2.FONT_HERSHEY_SIMPLEX,0.7,(0,0,255),2)
# cv2.imshow("image",image)
# cv2.waitKey(0)

images=[] #制作一个batch的数据
for p in imagePaths[1:]:
    image = cv2.imread(p)
    image = cv2.resize(image,(224,224))
    images.append(image)
blob = cv2.dnn.blobFromImages(images,1,(224,224),(104,117,123)) #数据预处理
print("Second Blob: {}".format(blob.shape)) 
net.setInput(blob)
preds = net.forward()
for (i,p) in enumerate(imagePaths[1:]):
    image = cv2.imread(p)
    idx =  np.argsort(preds[i])[::-1][0]
    text = "Label:{}, {:.2f}%".format(classes[idx],preds[i][idx] * 100)
    cv2.putText(image, text, (5, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
    cv2.imshow("image",image)
    cv2.waitKey(0)

在这里补上关于cv2.dnn.blobFromImage的理解 这里引用cv2.dnn.blobFromImage函数

这里引用一下语符律博客中的一段话: 为了从深度神经网络获得预测结果,你首先需要对你的数据进行预处理。 在深度学习和图像分类领域,预处理任务通常包含: 1.减均值(Mean subtraction) 2.按比例缩放(Scaling by some factor) OpenCV新的神经网络模块dnn包含两个预处理函数,为通过预训练深度学习模型进行分类,做好准备。

这个函数(还有加s的版本)都是实现对图像的预处理,可以执行的功能包括:

1 减均值 2 缩放 3 通道交换

cv2.dnn.blobFromImage 和 cv2.dnn.blobFromImages功能差不多,blob = cv2.dnn.blobFromImage(image, scalefactor=1.0, size, mean, swapRB=True,crop=False,ddepth = CV_32F ) 1.image,这是传入的,需要进行处理的图像。 2.scalefactor,执行完减均值后,需要缩放图像,默认是1,需要注意,scalefactor = 1 / σ,这是真正乘上的值。 3.size,这是神经网络真正支持输入的值,我们预处理的结果必须满足这个维度。 4.mean,这是我们要减去的均值,可以是R,G,B均值三元组,或者是一个值,每个通道都减这值。如果执行减均值,通道顺序是R、G、B。 如果,输入图像通道顺序是B、G、R,那么请确保swapRB = True,交换通道。 5.swapRB,OpenCV认为图像 通道顺序是B、G、R,而减均值时顺序是R、G、B,为了解决这个矛盾,设置swapRB=True即可,这也是我们为什么要进行通道交换的原因。 6.crop,如果crop裁剪为真,则调整输入图像的大小,使调整大小后的一侧等于相应的尺寸,另一侧等于或大于。然后,从中心进行裁剪。如果“裁剪”为“假”,则直接调整大小而不进行裁剪并保留纵横比。 7.ddepth, 输出blob的深度,选则CV_32F or CV_8U。 cv2.dnn.blobFromImage函数返回的blob是我们输入图像进行随意从中心裁剪,减均值、缩放和通道交换的结果。cv2.dnn.blobFromImages和cv2.dnn.blobFromImage不同在于,前者接受多张图像,后者接受一张图像。多张图像使用cv2.dnn.blobFromImages有更少的函数调用开销,你将能够更快批处理图像或帧。