开发语言:python
开发环境:windows,ubuntu
图像数据库:ImageNet
实验目的:图像识别,识别手表
文章结构:
- OpenCV4Python环境配置
- 数据准备
- 训练过程
- 使用生成的xml文件进行识别
OpenCV4Python环境配置
windows:
下载安装Anaconda3,然后下载文件
opencv_python-3.2.0-cp36-cp36m-win_amd64.whl
并放到目录
C:\Anaconda3\Lib\site-packages
下,执行pip install opencv_python-3.2.0-cp36-cp36m-win_amd64.whl
打开cmd,输入python后,输入
import cv2
若不报错,则环境配置成功。
ubuntu:
在root用户下:
sudo apt install libopencv-dev python-opencv
sudo apt install build-essential
sudo apt install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev
sudo apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev
创建一个文件夹:
mkdir opencv_workspace
然后clone opencv源码到当前目录:
git clone https://github.com/Itseez/opencv.git
至此环境配置完成。
数据准备
需要的图像数据分为正样本和负样本,正样本只有一副手表的图片(正样本的数量看情况而定,对于复杂的图像比如人脸,正样本的数量需要很多才行,而对于简单的样本识别,正样本需要一张就够了)
正样本:
处理图像尺寸的python代码:
import cv2
import numpy as np
img = cv2.imread('watch.jpg',cv2.IMREAD_GRAYSCALE)
img5050 = cv2.resize(img,(50,50))
cv2.imwrite('watch5050.jpg',img5050)
负样本:
负样本越多越好,我从ImgNet获取负样本数据,获取图像的URL集合的地址(随意,只要不是手表就行)
http://www.image-net.org/api/text/imagenet.synset.geturls?wnid=n02992529
然后访问该地址会返回一堆图片的链接集合:
像这样:
http://www.newsmobile.it/n/xeniumx530_11.jpg
http://farm4.static.flickr.com/3176/2825095151_f114959525.jpg
http://farm2.static.flickr.com/1135/921900930_54f4036d52.jpg
http://www.telefonino.net/news/images/13388_04.jpg
http://farm1.static.flickr.com/90/218188661_35ab70df72.jpg
http://passionemobile.files.wordpress.com/2008/09/passionemobileworld116.jpg
http://farm1.static.flickr.com/24/123429226_53b56824bb.jpg
http://farm4.static.flickr.com/3007/2654921621_9414540318.jpg
http://www.agenciainfomania.com/images/user/Image/NotaRoja/06Robo%20celular_1.jpg
http://farm4.static.flickr.com/3283/2496609968_2a993b0458.jpg
http://farm3.static.flickr.com/2084/1547357191_c80e5b36b7.jpg
http://www.giordanoshop.com/images/fotoinserzioni/3315/telefonino-g3315-5.jpg
http://files.splinder.com/683fe5ed949a1b1b1fbfb8a47d5e875b.jpg
http://farm3.static.flickr.com/2092/2264605380_a43ebdd11e.jpg
http://s3.amazonaws.com/rede_prod/assets/0046/9805/2340078090_7808eacc4e_m_thumb.jpg
http://farm1.static.flickr.com/231/446448280_54f7fd97d6.jpg
http://farm1.static.flickr.com/133/363412760_cee4855645.jpg
http://farm2.static.flickr.com/1423/856683107_b64988031e.jpg
http://farm4.static.flickr.com/3082/2595357619_7674575e62.jpg
……
将这些链接对应的图片下载到本地,存储到文件夹neg
中,当前文件夹目录结构:
/
neg/
watch5050.jpg
img_handle.py
获取图像到本地的python代码:
def store_raw_images():
neg_images_link = 'http://www.image-net.org/api/text/imagenet.synset.geturls?wnid=n02992529'
neg_images_urls = urllib.request.urlopen(neg_images_link).read().decode()
if not os.path.exists('neg'):
os.makedirs('neg')
pic_num = 1
for i in neg_images_urls.split('\n'):
try:
print(i)
urllib.request.urlretrieve(i,"neg/"+str(pic_num)+'.jpg')
img = cv2.imread("neg/"+str(pic_num)+'.jpg',cv2.IMREAD_GRAYSCALE)
resized_image = cv2.resize(img,(100,100))
cv2.imwrite("neg/"+str(pic_num)+'.jpg',resized_image)
pic_num += 1
except Exception as e:
print(str(e))
图片下载后会发现,会有一些无效的图片,新建一个文件夹uglies,随便选一张五项的图片(因为这些图片都是一样的)放入到该文件夹中:
/
neg/
uglies/
watch5050.jpg
img_handle.py
删除无效图片的python代码:
#删除损坏的数据文件
def find_uglies():
for file_type in ['neg']:
for img in os.listdir(file_type):
for ugly in os.listdir('uglies'):
try:
current_image_path = str(file_type)+'/'+str(img)
ugly = cv2.imread('uglies/'+str(ugly))
question = cv2.imread(current_image_path)
if ugly.shape == question.shape and not(np.bitwise_xor(ugly,question).any()):
print('Useless image')
print(current_image_path)
os.remove(current_image_path)
except Exception as e:
print(str(e))
创建对于负样本的描述文件bg.txt
,代码如下:
#创建描述文件(包含一些图像的路径及文件名信息)
def create_pos_n_neg():
for file_type in ['neg']:
for img in os.listdir(file_type):
if(file_type=='neg'):
line = file_type+'/'+img+'\n'
with open('bg.txt','a') as f:
f.write(line)
elif (file_type == 'pos'):
line = file_type + '/' + img + ' 1 0 0 50 50\n'
with open('info.txt', 'a') as f:
f.write(line)
至此,windows本地的工作告一段落。
训练过程
训练过程在我的一台linux服务器上进行,将刚才生成的文件夹neg
,文件bg.txt
,watch5050.jpg
上传到目录opencv_workspace
下,并在当前目录下创建文件夹:
mkdir data #用于存储Cascade分类器数据
mkdir info #用于存放正样本图像数据
接下来,生成正样本数据:
opencv_createsamples -img watch5050.jpg -bg bg.txt -info info/info.lst -pngoutput info -maxxangle -0.5 -maxyangle 0.5 -maxzangle 0.5 -num 1100
参数介绍如下:
-img 正样本图像,你也可以替换成你自己的图像,用于识别其他物体,而不只限于是手表
-bg 用于传入负样本描述信息文件的名称
-info 用于生成正样本的信息,类似于bg.txt,包括图片路径,图片中对象的个数以及这些对象的位置
-pngoutput 图像的输出路径,生成的正样本图像都保存到这个目录下
-num 生成的正样本的数量
接下来生产成vector
文件:
opencv_createsamples -info info/info.lst -num 1100 -w 20 -h 20 -vec positives.vec
Note:
-w,-h选择为20的原因是节约训练时间(越大越耗时)
最后,训练分类器文件:
opencv_traincascade -data data -vec positives.vec -bg bg.txt -numPos 500 -numNeg 250 -numStages 16 -w 20 -h 20
参数:
-data 用于指定输出文件所在的位置
-numPos 每级分类器训练时所用到的正样本数目,应小于vec文件中正样本的数目,具体数目限制条件为:numPos+(numStages-1)numPos(1-minHitRate)<=vec文件中正样本的数目
-numNeg 每级分类器训练时所用到的负样本数目,可以大于-bg指定的图片数目(和上面一样,具体大小要根据主机内存的大小而定,内存不足设置过大会导致训练过程被KILLED)
-numStages 训练分类器的级数,强分类器的个数
注意,若训练时间比较长,而你又想在关闭putty等终端的情况下让训练继续进行,需要在上述的代码前加一条指令nohup
,这样进程会被放入到后台继续运行。可通过命令‘htop’命令查看系统中各个进程运行情况。
至此,训练结束,查看data
文件夹,会发现分类器文件已经生成。
通过改变
opencv_traincascade -data data -vec positives.vec -bg bg.txt -numPos 500 -numNeg 250 -numStages 16 -w 20 -h 20
中-numStages
选项,可以改变加载的分类器的不同的stage
。
使用生成的xml文件进行识别
源代码:
import cv2
watch_cascade = cv2.CascadeClassifier('watch-cascade-12stages.xml')
img = cv2.imread('test.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
watches = watch_cascade.detectMultiScale(gray)
for (x,y,w,h) in watches:
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]
cv2.imshow('img',img)
k = cv2.waitKey(0)
结果如图: