课程链接:https://aistudio.baidu.com/aistudio/course/introduce/11939?directly=1&shared=1
一.OpenCV图像数据增强代码
左右翻转(上下翻转对于许多目标并不常用),随机裁剪,变换颜色(亮度,对比度,饱和度和色调)等等,我们拟用opencv-python实现部分数据增强方法
- 结构
class FunctionClass:
def __init__(self, parameter):
self.parameter=parameter
def __call__(self, img):
- 图片缩放
class Resize:
def __init__(self, size):
if size is not None:
self.w = size if type(size) is int else size[0]
self.h = size if type(size) is int else size[1]
else:
raise OperatorParamError("invalid params for ReisizeImage for 'size' is None")
def __call__(self, img):
# TODO
return cv2.resize(img, (self.w, self.h), interpolation=cv2.INTER_LINEAR)
- 图片翻转
class Flip:
"""
random flip image 随机翻转图片
mode:
1: Flipped Horizontally 水平翻转
0: Flipped Vertically 上下翻转
-1: Flipped Horizontally & Vertically 水平、上下翻转
"""
def __init__(self, mode=1):
# 设置一个翻转参数,1、0或-1
assert mode in [-1, 0, 1], "flip_code should be a value in [-1, 0, 1]"
self.mode = mode
def __call__(self, img):
# 此处插入代码
if np.random.randint(0, 2) == 1:
return cv2.flip(img, self.mode)
else:
return img
flip = Flip(mode=0)
img2 = flip(img)
plt.imshow(img2)
- 图片旋转
class Rotate:
def __init__(self, degree, size):
self.degree=degree
self.size=size
def __call__(self, img):
# 此处插入代码
h, w = img.shape[:2]
matRotate = cv2.getRotationMatrix2D((w*0.5, h*0.5), self.degree, self.size) # 旋转变化矩阵
'''
参数1 必选参数。用于设置旋转中心点,点坐标为OpenCV图像坐标系下的坐标。
参数2 必选参数。用于设置旋转的角度,单位为度。
参数3 必选参数。用于设置缩放系数,即对旋转的图像进行缩放。
'''
dst = cv2.warpAffine(img, matRotate, (w, h)) #旋转
return dst
- 图片亮度调节
亮度调整是将图像像素的强度整体变大/变小,对比度调整指的是图像暗处的像素强度变低,亮出的变高,从而拓宽某个区域内的显示精度。
OpenCV中亮度和对比度应用这个公式来计算:g(x)=αf(x)+β,其中:α(>0)、β常称为增益与偏置值,分别控制图片的对比度和亮度。
class Brightness:
'''
亮度调整是将图像像素的强度整体变大/变小,对比度调整指的是图像暗处的像素强度变低,亮出的变高,从而拓宽某个区域内的显示精度。
OpenCV中亮度和对比度应用这个公式来计算:g</span>(x)=αf</span>(x)+β</span></span>,其中:α(>0)、β常称为增益与偏置值,分别控制图片的对比度和亮度。
'''
def __init__(self,brightness_factor):
self.brightness_factor=brightness_factor
def __call__(self, img):
return np.uint8(np.clip((self.brightness_factor* img), 0, 255))
brightness=Brightness(0.6)
img2=brightness(img)
plt.imshow(img2)
- 图片随机擦除
import random
import math
class RandomErasing(object):
def __init__(self, EPSILON=0.5, sl=0.02, sh=0.1, r1=0.5,
mean=[0., 0., 0.]):
self.EPSILON = EPSILON
self.mean = mean
self.sl = sl
self.sh = sh
self.r1 = r1
def __call__(self, img):
if random.uniform(0, 1) > self.EPSILON:
return img
for attempt in range(100):
area = img.shape[0] * img.shape[1]
target_area = random.uniform(self.sl, self.sh) * area # [sl, sh]*area
aspect_ratio = random.uniform(self.r1, 1 / self.r1) # [r1, 1/r1] for h/w
h = int(round(math.sqrt(target_area * aspect_ratio)))
w = int(round(math.sqrt(target_area / aspect_ratio)))
# TODO
if w < img.shape[0] and h < img.shape[1]:
x1 = random.randint(0, img.shape[1] - h)
y1 = random.randint(0, img.shape[0] - w)
img2 = img.copy()
if img.shape[2] == 3:
img2[ x1:x1 + h, y1:y1 + w, 0] = self.mean[0]
img2[ x1:x1 + h, y1:y1 + w, 1] = self.mean[1]
img2[ x1:x1 + h, y1:y1 + w, 2] = self.mean[2]
else:
img2[x1:x1 + h, y1:y1 + w,0] = self.mean[0]
return img2
return img2
img = cv2.imread('lena.jpg', 1)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
erase = RandomErasing()
img2 = erase(img)
plt.imshow(img2)
- 图片随机裁剪
class RandomResizeCrop(object):
""" random crop image """
""" 随机裁剪图片 """
def __init__(self, size, scale=None, ratio=None, interpolation=-1):
self.interpolation = interpolation if interpolation >= 0 else None
if type(size) is int:
self.size = (size, size) # (h, w)
else:
self.size = size
self.scale = [0.08, 1.0] if scale is None else scale
self.ratio = [3. / 4., 4. / 3.] if ratio is None else ratio
def __call__(self, img):
size = self.size
scale = self.scale
ratio = self.ratio
aspect_ratio = math.sqrt(random.uniform(*ratio))
w = 1. * aspect_ratio
h = 1. / aspect_ratio
img_h, img_w = img.shape[:2]
bound = min((float(img_w) / img_h) / (w**2),
(float(img_h) / img_w) / (h**2))
scale_max = min(scale[1], bound)
scale_min = min(scale[0], bound)
target_area = img_w * img_h * random.uniform(scale_min, scale_max)
target_size = math.sqrt(target_area)
w = int(target_size * w)
h = int(target_size * h)
i = random.randint(0, img_w - w)
j = random.randint(0, img_h - h)
img = img[j:j + h, i:i + w, :]
if self.interpolation is None:
return cv2.resize(img, size)
else:
return cv2.resize(img, size, interpolation=self.interpolation)
crop = RandomResizeCrop(224)
plt.imshow(crop(img))
**二.**图像处理入门基础
- 分辨率=画面水平方向的像素值 * 画面垂直方向的像素值
屏幕分辨率
例如,屏幕分辨率是1024×768,也就是说设备屏幕的水平方向上有1024个像素点,垂直方向上有768个像素点。像素的大小是没有固定长度的,不同设备上一个单位像素色块的大小是不一样的。
例如,尺寸面积大小相同的两块屏幕,分辨率大小可以是不一样的,分辨率高的屏幕上面像素点(色块)就多,所以屏幕内可以展示的画面就更细致,单个色块面积更小。而分辨率低的屏幕上像素点(色块)更少,单个像素面积更大,可以显示的画面就没那么细致。
图像分辨率
例如,一张图片分辨率是500x200,也就是说这张图片在屏幕上按1:1放大时,水平方向有500个像素点(色块),垂直方向有200个像素点(色块)。
在同一台设备上,图片分辨率越高,这张图片1:1放大时,图片面积越大;图片分辨率越低,这张图片1:1缩放时,图片面积越小。(可以理解为图片的像素点和屏幕的像素点是一个一个对应的)。
但是,在屏幕上把图片超过100%放大时,为什么图片上像素色块也变的越大,其实是设备通过算法对图像进行了像素补足,我们把图片放的很大后看到的一块一块的方格子,虽然理解为一个图像像素,但是其实是已经补充了很多个屏幕像素;同理,把图片小于100%缩小时,也是通过算法将图片像素进行减少。
使用OpenCV加载并保存图片
加载图片,显示图片,保存图片
OpenCV函数:cv2.imread(), cv2.imshow(), cv2.imwrite()
说明
大部分人可能都知道电脑上的彩色图是以RGB(红-绿-蓝,Red-Green-Blue)颜色模式显示的,但OpenCV中彩色图是以B-G-R通道顺序存储的,灰度图只有一个通道。
OpenCV默认使用BGR格式,而RGB和BGR的颜色转换不同,即使转换为灰度也是如此。一些开发人员认为R+G+B/3对于灰度是正确的,但最佳灰度值称为亮度(luminosity),并且具有公式:0.21R+0.72G+0.07*B
图像坐标的起始点是在左上角,所以行对应的是y,列对应的是x。
加载图片
使用cv2.imread()来读入一张图片:
参数1:图片的文件名
如果图片放在当前文件夹下,直接写文件名就行了,如’lena.jpg’
否则需要给出绝对路径,如’D:\OpenCVSamples\lena.jpg’
参数2:读入方式,省略即采用默认值
cv2.IMREAD_COLOR:彩色图,默认值(1)
cv2.IMREAD_GRAYSCALE:灰度图(0)
cv2.IMREAD_UNCHANGED:包含透明通道的彩色图(-1)
经验之谈:路径中不能有中文噢,并且没有加载成功的话是不会报错的,print(img)的结果为None,后面处理才会报错,算是个小坑。
阈值分割
使用固定阈值、自适应阈值和Otsu阈值法"二值化"图像
OpenCV函数:cv2.threshold(), cv2.adaptiveThreshold()
三.基于PaddlePaddle2.0的蝴蝶图像识别分类——利用预训练残差网络ResNet101模型
https://aistudio.baidu.com/aistudio/projectdetail/1617500
准备数据
#以下代码用于建立样本数据读取路径与样本标签之间的关系
import os
import random
data_list = [] #用个列表保存每个样本的读取路径、标签
#由于属种名称本身是字符串,而输入模型的是数字。需要构造一个字典,把某个数字代表该属种名称。键是属种名称,值是整数。
label_list=[]
with open("/home/aistudio/data/species.txt") as f:
for line in f:
a,b = line.strip("\n").split(" ")
label_list.append([b, int(a)-1])
label_dic = dict(label_list)
#获取Butterfly20目录下的所有子目录名称,保存进一个列表之中
class_list = os.listdir("/home/aistudio/data/Butterfly20")
class_list.remove('.DS_Store') #删掉列表中名为.DS_Store的元素,因为.DS_Store并没有样本。
for each in class_list:
for f in os.listdir("/home/aistudio/data/Butterfly20/"+each):
data_list.append(["/home/aistudio/data/Butterfly20/"+each+'/'+f,label_dic[each]])
#按文件顺序读取,可能造成很多属种图片存在序列相关,用random.shuffle方法把样本顺序彻底打乱。
random.shuffle(data_list)
#打印前十个,可以看出data_list列表中的每个元素是[样本读取路径, 样本标签]。
print(data_list[0:10])
#打印样本数量,一共有1866个样本。
print("样本数量是:{}".format(len(data_list)))
#以下代码用于构造读取器与数据预处理
#首先需要导入相关的模块
import paddle
from paddle.vision.transforms import Compose, ColorJitter, Resize,Transpose, Normalize
import cv2
import numpy as np
from PIL import Image
from paddle.io import Dataset
#自定义的数据预处理函数,输入原始图像,输出处理后的图像,可以借用paddle.vision.transforms的数据处理功能
def preprocess(img):
transform = Compose([
Resize(size=(224, 224)), #把数据长宽像素调成224*224
Normalize(mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], data_format='HWC'), #标准化
Transpose(), #原始数据形状维度是HWC格式,经过Transpose,转换为CHW格式
])
img = transform(img).astype("float32")
return img
#自定义数据读取器
class Reader(Dataset):
def __init__(self, data, is_val=False):
super().__init__()
#在初始化阶段,把数据集划分训练集和测试集。由于在读取前样本已经被打乱顺序,取20%的样本作为测试集,80%的样本作为训练集。
self.samples = data[-int(len(data)*0.2):] if is_val else data[:-int(len(data)*0.2)]
def __getitem__(self, idx):
#处理图像
img_path = self.samples[idx][0] #得到某样本的路径
img = Image.open(img_path)
if img.mode != 'RGB':
img = img.convert('RGB')
img = preprocess(img) #数据预处理--这里仅包括简单数据预处理,没有用到数据增强
#处理标签
label = self.samples[idx][1] #得到某样本的标签
label = np.array([label], dtype="int64") #把标签数据类型转成int64
return img, label
def __len__(self):
#返回每个Epoch中图片数量
return len(self.samples)
#生成训练数据集实例
train_dataset = Reader(data_list, is_val=False)
#生成测试数据集实例
eval_dataset = Reader(data_list, is_val=True)
#打印一个训练样本
#print(train_dataset[1136][0])
print(train_dataset[1136][0].shape)
print(train_dataset[1136][1])
建立模型
#定义模型
class MyNet(paddle.nn.Layer):
def __init__(self):
super(MyNet,self).__init__()
self.layer = paddle.vision.models.resnet50(pretrained=True)
self.fc = paddle.nn.Linear(1000, 512)
self.fc2 = paddle.nn.Linear(512, 20)
self.flatten = paddle.nn.Flatten()
self.relu = paddle.nn.ReLU()
self.dropout = paddle.nn.Dropout(0.2)
#网络的前向计算过程
def forward(self,x):
x = self.layer(x)
x = self.flatten(x)
x = self.fc(x)
x = self.relu(x)
x = self.dropout(x)
x = self.fc2(x)
return x
应用高阶API训练模型
#定义输入
input_define = paddle.static.InputSpec(shape=[-1,3,224,224], dtype="float32", name="img")
label_define = paddle.static.InputSpec(shape=[-1,1], dtype="int64", name="label")
#实例化网络对象并定义优化器等训练逻辑
model = MyNet()
model = paddle.Model(model,inputs=input_define,labels=label_define) #用Paddle.Model()对模型进行封装
optimizer = paddle.optimizer.Adam(learning_rate=0.0001, parameters=model.parameters())
#上述优化器中的学习率(learning_rate)参数很重要。要是训练过程中得到的准确率呈震荡状态,忽大忽小,可以试试进一步把学习率调低。
model.prepare(optimizer=optimizer, #指定优化器
loss=paddle.nn.CrossEntropyLoss(), #指定损失函数
metrics=paddle.metric.Accuracy()) #指定评估方法
model.fit(train_data=train_dataset, #训练数据集
eval_data=eval_dataset, #测试数据集
batch_size=64, #一个批次的样本数量
epochs=70, #迭代轮次
save_dir="/home/aistudio/iterhui", #把模型参数、优化器参数保存至自定义的文件夹
save_freq=1, #设定每隔多少个epoch保存模型参数及优化器参数
log_freq=100 #打印日志的频率
)
result = model.evaluate(eval_dataset,batch_size=64,log_freq=100, verbose=2, num_workers=0, callbacks=None)
print(result)