图像分类器:基于opencv、随机森林、逻辑回归算法实现
本文介绍了什么是图像分类以及图像分类的过程,介绍了图像分类的关键操作、opencv的使用、图像的三种颜色空间:RGB、HSV、L*a*b*、以及颜色直方图,另外基于
RGB模式及L*a*b*模式下使用随机森林、逻辑回归算法实现对4种图片的分类。
以下部分是正文:
−−−by:朱世新 − − − b y : 朱 世 新
1 项目简介
1.1 图像分类器 - 什么是图像分类
1.2 案例目标
1.3 图像分类过程
1.4 图像分类关键点
1.4.1 关键点1:数据准备
1.4.2 关键点2:数据集划分
1.4.3 关键点3:特征表示
1.4.4 关键点4:分类算法
1.4.5 关键点5:模型评估
2 opencv简介及安装使用
2.1 opencv安装
> conda install opencv
2.2 什么是opencv
2.3 opencv使用
>>在线文档:https://docs.opencv.org/2.4.11/<<
2.4 代码演示:
import cv2
# 读取图片
img_file = '../data/images/image_crocus_0001.png'
'''
cv2.imread(filename,(flags))
参数说明:
filename:图片文件名
flags:指定图片的颜色类型
- IMREAD_ANYCOLOR 返回无损图片
- IMREAD_ANYDEPTH 如果载入图像的深度为16位或者32位,就返回对应深度图,否则就转换为8位图像
- IMREAD_COLOR 总是把图片转为3通道的BGR图片
- IMREAD_GRAYSCALE 返回灰度图片
- IMREAD_UNCHANGED 返回原始状态图片
支持的图片格式
- Windows位图:*.bmp, *.dib
-JPEG文件:*.jpeg, *.jpg, *.jpe
- PNG文件:*.png
- Portable image format:*.pbm, *.pgm, *.ppm
- Sun rasters:*.sr, *.ras
- TIFF文件:*.tiff, *.tif
'''
img = cv2.imread(img_file)
'''
读入的图片得到的是ndarray对象,0~255的整数
'''
type(img) >> np.ndarray
'''
ndarray的三个维度分别是图片的:高、宽、通道
'''
img.shape
# 图片的显示方法
'''
方法一:命令行输入:
python 2.0_cv2_imshow.py ../data/images/image_crocus_0001.png IMREAD_COLOR
方法二:使用matplotlib显示图片
plt.imshow()在显示图片的时候是按RGB通道的顺序显示,与cv2的方式正好相反
需要通过np.flip(img,axis=2)调整3个通道的顺序
'''
import matplotlib.pyplot as plt
import numpy as np
plt.imshow(np.flip(img,axis=2))
ple.asix('off')
plt.show()
# 保存图片
output_image = os.path.join(output_dir,'image.png')
cv2.imwrite(output_image,img)
3 图像特征 - 颜色直方图(代码演示)
import numpy as np
import cv2
import matplotlib.pyplot as plt
pic_file='data/images/image_crocus_0001.png'
img_bgr = cv2.imread(pic_file,cv2.IMREAD_COLOR)
3.1 RGB颜色空间
# 分别获取三个通道的ndarray数据
img_b=img_bgr[...,0]
img_g=img_bgr[...,1]
img_r=img_bgr[...,2]
# 分通道显示图片
fig = plt.gcf()
fig.set_size_inches(10,10)
plt.subplot(221)
plt.imshow(np.flip(img_bgr,axis=2))
plt.axis('off')
plt.title('Image')
plt.subplot(222)
plt.imshow(img_r,cmap='gray')
plt.axis('off')
plt.title('R')
plt.subplot(223)
plt.imshow(img_g,cmap='gray')
plt.axis('off')
plt.title('G')
plt.subplot(224)
plt.imshow(img_b,cmap='gray')
plt.axis('off')
plt.title('B')
plt.show()
3.2 HSV颜色空间
# 通过cvtColor()方法转换颜色空间
img_hsv = cv2.cvtColor(img_bgr,cv2.COLOR_BGR2HSV)
img_h = img_hsv[...,0]
img_s = img_hsv[...,1]
img_v = img_hsv[...,2]
'''分通道显示图片'''
fig = plt.gcf()
fig.set_size_inches(10,10)
plt.subplot(221)
plt.imshow(img_hsv)
plt.axis('off')
plt.title('HSV')
plt.subplot(222)
plt.imshow(img_h,cmap='gray')
plt.axis('off')
plt.title('H')
plt.subplot(223)
plt.imshow(img_s,cmap='gray')
plt.axis('off')
plt.title('S')
plt.subplot(224)
plt.imshow(img_v,cmap='gray')
plt.axis('off')
plt.title('V')
plt.show()
3.3 L*a*b*颜色空间
img_lab = cv2.cvtColor(img_bgr,cv2.COLOR_BGR2LAB)
img_ls = img_lab[...,0]
img_as = img_lab[...,1]
img_bs = img_lab[...,2]
'''分通道显示图片'''
fig = plt.gcf()
fig.set_size_inches(10,12)
plt.subplot(221)
plt.imshow(img_lab)
plt.axis('off')
plt.title('L*a*b')
plt.subplot(222)
plt.imshow(img_ls,cmap='gray')
plt.axis('off')
plt.title('L*')
plt.subplot(223)
plt.imshow(img_ls,cmap='gray')
plt.axis('off')
plt.title('a*')
plt.subplot(224)
plt.imshow(img_ls,cmap='gray')
plt.axis('off')
plt.title('b*')
plt.show()
3.4 灰度图
img_gray = cv2.cvtColor(img_bgr,cv2.COLOR_BGR2GRAY)
fig = plt.gcf()
fig.set_size_inches(5,5)
plt.imshow(img_gray,cmap='gray')
plt.axis('off')
plt.title('Gray')
plt.show()
4 颜色直方图
4.1 灰度图的颜色直方图
'''
cv2.calcHist(images,channels,mask,histSize,ranges[,hist[,accumulate]]) -> hist
参数说明:
images:图片列表
channels:需要计算直方图的通道,[0]表示计算通道0的直方图,[0,1,2]表示计算0,1,2所表示颜色的直方图
mask:蒙版,只计算>0的位置上像素的颜色直方图,取None表示无蒙版
histSize:每个维度上直方图的大小,[8]表示把通道0的颜色取值等分为8份后计算直方图
ranges:每个维度的取值方位,[lower0,upper0,lower1,upper1,…],lower可以取到,upper取不到
hist:保存结果的ndarray对象
accumulate:是否积累,如果设置了这个值,hist不会被清零,直方图结果直接积累到hist中
'''
img_gray_hist = cv2.calcHist([img_gray],[0],None,[256],[0,256])
plt.imshow(img_gray,cmap='gray')
plt.axis('off')
plt.title('Grayscale Image')
plt.show()
plt.plot(img_gray_hist)
plt.title('Grauscale Histogram')
plt.xlabel('Bins')
plt.ylabel('# of Pixels')
plt.show()
4.2 带蒙版的颜色直方图
'''读取模板'''
mask_file = 'data/masks/mask_crocus_0001.png'
mask = cv2.imread(mask_file,cv2.IMREAD_UNCHANGED)
img_gray_hist_with_mask = cv2.calcHist([img_gray],[0],mask,[256],[0,256])
'''
图片按位与
cv2.bitwise_and(src1,src2[,dst[,mask]) -> dst
参数说明:
src1:图片1
src2:图片2
dst:保存结果的ndarray对象
mask:蒙版
'''
img_masked = cv2.bitwise_and(img_gray,img_gray,mask=mask) # 使得蒙版与原图重合
plt.imshow(img_masked,cmap='gray')
plt.axis('off')
plt.title('Image with mask')
ple.show()
4.3 多个颜色直方图
'''按R、G、B三个通道分别计算颜色直方图'''
b_hist = cv2.calcHist([img_bgr],[0],None,[256],[0,256])
g_hist = cv2.calcHist([img_bgr],[1],None,[256],[0,256])
r_hist = cv2.calcHist([img_bgr],[2],None,[256],[0,256])
'''显示三个通道的颜色直方图'''
plt.plot(b_hist,label='B',color='blue')
plt.plot(g_hist,label='G',color='green')
plt.plot(r_hist,label='R',color='red')
plt.legend(loc='best')
plt.xlim([0,256])
plt.show()
4.4 按多个通道计算颜色直方图
'''把一个像素的多个通道合在一起看作一个值'''
hist = cv2.calcHist([img_bgr],[0,1,2],None,[8,8,8],[0,256,0,256,0,256])
hist.shape >> (8,8,8) 三维8*8*8的空间
'''含义:B、G、R取值分别在(0,32)的像素的个数 --> [0,0,0]对应21044个像素'''
hist[0,0,0] >> 21044.0
5 分别使用随机森林、逻辑回归构建图像分类模型
5.1 图片预处理(读取、贴标签、数据拆分、数据标准化)
# 导入模块
import numpy as np
import cv2
import os
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix,precision_recall_fscore_support
import pandas as pd
import pickle
# 创建模型输出文件夹
output_dir='output'
if not os.path.exists(output_dir):
os.mkdir(output_dir)
# 数据准备 - 读取图片并贴标签
img_dir = './data/images'
images=[]
labels=[]
for fname in os.listdir(img_dir):
'''跳过不是目标图片的文件'''
if not fname.startswith('image'):continue
'''
合并目录:os.path.join
import os
os.path.join('/hello/','good/boy/','doiido')
>>> '/hello/good/boy/doiido'
'''
fpath = os.path.join(img_dir,fname)
'''根据文件名,提取图片分类'''
lab = fpath.split('_')[1]
'''RGB模式读取图片,读入的图片得到的是ndarray对象,0~255的整数'''
img = cv2.imread(fpath,cv2.IMREAD_COLOR)
images.append(img)
labels.append(lab)
'''将图片标签ID化,输出可由计算机处理的数据,四种花,四种标签,此处相当于多分类问题'''
label_encoder = LabelEncoder()
y = label_encoder.fit_transform(labels)
'''训练集、测试集划分,stratify = y,表示根据图片类别进行分类,确保每个类别图片的均衡性'''
train_idx,test_idx = train_test_split(range(len(y)),test_size=0.2,stratify = y, random_state = 1234) # 返回拆分后的索引
train_y = y[train_idx]
test_y = y[test_idx]
5.2 使用RGB颜色直方图做特征训练分类器
5.2.1 获取每个图片的RGB特征数据
# 计算RGB颜色直方图
'''定义特征转化函数'''
def transform(img):
'''每个通道等分为8组后计算直方图'''
hist = cv2.calcHist([img],[0,1,2],None,[8]*3,[0,256]*3)
'''将8*8*8的多维数组拉平'''
return hist.ravel()
'''提取每个图像的直方图特征,按行合并成一个大的矩阵,每一行即一张图片的长度为512特征'''
x_rgb = np.row_stack([transform(img) for img in images])
'''
根据索引取得处理后的图片分别存入训练集和测试集
x[[],:]:取得对应行数的所有列数据
'''
train_x = x_rgb[train_idx,:]
test_x = x_rgb[test_idx,:]
5.2.2 使用随机森林分类器
model_rgb_rf = RandomForestClassifier(n_estimators =15, max_depth =3, random_state=1234) # 1234随机初始化的种子
model_rgb_rf.fit(train_x,train_y) # 训练数据集
5.2.3 定义模型保存
'''
保存模型:
通常情况下在一个地方训练的模型都会在另外一个地方被使用,因此每次训练的模型都需要保存成一个可调用的对象
本例中采用pickle.dump()方法将训练的模型保存,然后可以实现在其他地方调用,以此方便模型的使用
'''
def save_model(model,label_encoder,output_file):
try:
with open(output_file,'wb') as outfile:
pickle.dump({
'model':model,
'label_encoder':label_encoder
},outfile)
return True
except:
return False
# 保存模型
save_model(model_rgb_rf,label_encoder,os.path.join(output_dir,'model_rgb_rf.pkl'))
5.2.4 随机森林模型评估
'''计算各项评价指标'''
def eval_model(y_true,y_pred,labels):
'''计算每个分类器的Precision,Recall,f1,support'''
P,r,f1,s =precision_recall_fscore_support(y_true,y_pred)
'''计算总体平均的Precision,Recall,f1,support'''
tot_P = np.average(P,weights =s)
tot_r = np.average(r,weights =s)
tot_f1 = np.average(f1,weights =s)
tot_s = np.sum(s)
res1 = pd.DataFrame({
'Label':labels,
'Precision':P,
'Reacll':r,
'F1':f1,
'Support':s
})
res2 = pd.DataFrame({
'Label':['总体'],
'Precision':[tot_P],
'Recall':[tot_r],
'F1':[tot_f1],
'Support':[tot_s]
})
res2.index=[999]
res = pd.concat([res1,res2])
'''计算混淆矩阵'''
conf_mat = pd.DataFrame(confusion_matrix(y_true,y_pred),columns=labels,index=labels)
return conf_mat,res[['Label','Precision','Recall','F1','Support']]
'''在测试集上计算每个图片的预测分类'''
y_pred_rgb_rf = model_rgb_rf.predict(test_x)
'''评估模型'''
conf_mat_lab_rf,evalues_rf = eval_model(test_y,y_pred_rgb_rf,label_encoder.classes_)
5.2.5 使用逻辑回归训练分类器
'''l2正则惩罚项,惩罚力度为1'''
model_rgb_lr = LogisticRegression(penalty ='l2',C=1,random_state=1234)
model_rgb_lr.fit(train_x,train_y)
# 保存模型
save_model(model_rgb_lr,label_encoder,os.path.join(output_dir,'model_rgb_lr.pkl'))
5.2.6 逻辑回归模型评估
'''在测试集上计算每个图片的预测分类'''
y_pred_rgb_lr = model_rgb_lr.predict(test_x)
'''评估模型'''
conf_mat_rgb_lr,evalues_rgb_lr = eval_model(test_y,y_pred_rgb_lr,label_encoder.classes_)
5.3 使用L*a*b*颜色直方图做特征训练分类器
5.3.1 计算L*a*b*颜色直方图
'''定义特征转化函数'''
def lab_transform(img):
'''将图片从bgr模式转为Lab模式'''
img = cv2.cvtColor(img,cv2.COLOR_BGR2LAB)
'''每个通道等分8组后计算直方图'''
hist = cv2.calcHist([img],[0,1,2],None,[8]*3,[0,256]*3)
'''将8*8*8的多维数组拉平'''
return hist.ravel()
'''提取每个图像的直方图特征'''
x_lab = np.row_stack([lab_transform(img) for img in images])
train_x = x_lab[train_idx,:]
test_x = x_lab[test_idx,:]
5.3.2 使用随机森林训练分类器
model_lab_rf = RandomForestClassifier(n_estimators=15,max_depth=3,random_state=1234)
model_lab_rf.fit(train_x,train_y)
save_model(model_lab_rf,label_encoder,os.path.join(output_dir,'model_lab_rf.pkl'))
5.3.3 评估Lab模式下随机森林分类器
'''在测试集上计算每个图片的预测分类'''
y_pred_lab_rf = model_lab_rf.predict(test_x)
'''评估模型'''
conf_mat_lab_rf ,evalues_lab_rf = eval_model(test_y,y_pred_lab_rf,label_encoder.classes_)
5.3.4 使用逻辑回归分类器
model_lab_lr = LogisticRegression(penalty='l2',C=1,random_state=1234)
model_lab_lr.fit(train_x,train_y)
save_model(model_lab_lr,label_encoder,os.path.join(output_dir,'model_lab_lr.pkl'))
5.3.5 评估Lab模式下逻辑回归分类器
'''在测试集上计算每个图片的预测分类'''
y_pred_lab_lr = model_lab_lr.predict(test_x)
'''评估模型'''
conf_mat_lab_lr ,evalues_lab_lr = eval_model(test_y,y_pred_lab_lr,label_encoder.classes_)
6 使用分类器对新图像分类
'''定义一个用于分类的Predictor'''
class Predictor(object):
def __init__(self,model_file):
with open(model_file,'rb') as infile:
self.loaded = pickle.load(infile)
self.model = self.loaded['model']
self.label_encoder = self.loaded['label_encoder']
'''实现分类逻辑'''
def predict(self,img_file):
'''读取图像文件'''
img = cv2.imread(img_file,cv2.IMREAD_COLOR)
'''颜色空间转为Lab'''
img = cv2.cvtColor(img,cv2.COLOR_BGR2LAB)
'''计算颜色直方图,返回8*8*8的nadarray'''
x = cv2.calcHist([img],[0,1,2],None,[8]*3,[0,256]*3)
'''变形为(1,512)的ndarray'''
x = x.reshape((1,-1))
'''预测分类'''
y = self.model.predict(x)
'''转化为原始标签'''
label = self.label_encoder.inverse_transform(y)
return label
predictor = Predictor(os.path.join(output_dir,'model_lab_rf.pkl'))
'''对新图片进行分类'''
predictor.predict('data/images/image_crocus_0001.png')
>> array(['crocus'], dtype='<U9')