【AI Studio】飞桨图像分类零基础训练营 - 01 - 图像处理基本概念

前言:这里是图像分类的第一课笔记,如题,第一节可主要讲述了图像处理的操作,也就是数据集处理。根据之前学过的知识理解,训练集往往是有限的,为了扩大训练集,总会人为的制造数据。这时数据处理就体现了。而且实践证明,这很高效有用。
经过上次“LHT”笔记的上传失败,这次我想采用缩写,希望能通过。没想到这次课内容那么多,我笔记也“复制粘贴”了很多……

———————————————

【AI Studio】飞桨图像分类零基础训练营 - 01 - 图像处理基本概念

课程项目:图像处理入门基础(一)
https://aistudio.baidu.com/aistudio/projectdetail/1608721
课程项目:图像处理入门基础(二)
https://aistudio.baidu.com/aistudio/projectdetail/1606500

一、图像处理的概念与基本操作

  • 1)介绍图像的类型,三维数组的彩色和二维数组的灰度。之后还有特殊的四维,是包含了透明度通道。
  • 2)介绍图像数据集一般以字典方式读取和存储(字典又是用列表存储的),字典第一个元素就是图像数据,第二便是图像标签。相当于对应训练中的输入x与输出y。
# 看不清?不妨把上面的矩阵保存成txt,但是如果直接保存,会变成科学计数法,查看一下np.savetxt()的用法
?np.savetxt		# 插曲,可以使用这种方式查看函数功能,和help类似?
# 将矩阵保存成文本,数字格式为整数
np.savetxt('7.txt', np.array(mnist[0][0]), fmt='%4d')

①PIL 读取图像

from PIL import Image
# 打开图片,返回一个图片对象
img = Image.open('lena.jpg')

在这里插入图片描述

②PIL 分离颜色通道

from PIL import Image
# 使用PIL分离颜色通道
r,g,b = img.split()
# 获取第一个通道转的灰度图,和上一个函数功能类似
img.getchannel(0)# 参数012对应不同通道,即RGB

在这里插入图片描述

③PIL 截图一部分图像

from PIL import Image
# 不断调试,找到猫的边缘,裁剪出一小块交接区
img.crop((1450,1450,1500,1500))

在这里插入图片描述

二、OpenCV库基本操作

①CV2 加载图片

  • 大部分人可能都知道电脑上的彩色图是以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,后面处理才会报错,算是个小坑。

%matplotlib inline  #这个是给Notebook用的,用于plt的显示
import cv2
import matplotlib.pyplot as plt
# 加载彩色图
img = cv2.imread('lena.jpg', 1)
# 将彩色图的BGR通道顺序转成RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 显示图片
plt.imshow(img)

②CV2 显示图片(BGR转RGB、灰度)

  • 特点,在PIL中,不能直接使用img.shape的方式返回维度大小。但是cv2下可以。
import cv2
import matplotlib.pyplot as plt
# 查看图片的形状
img.shape

# 将彩色图的BGR通道顺序转成RGB,注意,在这一步直接丢掉了alpha通道
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 显示图片
plt.imshow(img)

# 将彩色图的BGR通道直接转为灰度图
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 显示这张灰度图
plt.imshow(img,'gray')

③CV2 保存图片

import cv2
# 保存图片,成功的话会返回一个True
cv2.imwrite('lena-grey.jpg',img)

④CV2 通道分割与合并

  • cv2和PIL都有split方法,调用方式不太一样,注意
# 创建一副图片
img = cv2.imread('lena.jpg')
# 通道分割
b, g, r = cv2.split(img)
# 通道合并
img = cv2.merge((b, g, r))  
# 将彩色图的BGR通道顺序转成RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img)
  • plt小知识
plt.subplot(141)  # 分割显示,一个窗口显示1*4,当前是第1幅图像
# 显示图像,对应上面的设定位置
plt.imshow(RGB_Image,'gray')
# 图像的标题,一个图像一个名字
plt.title('RGB_Image')

三、OpenCV库进阶操作

1.专业名词:ROI

  • ROI:Region of Interest,感兴趣区域。
  • 截取ROI非常简单,指定图片的范围即可
# 截取猫脸ROI
face = img[0:740, 400:1000]  # 切片操作
plt.imshow(face)

2.专业操作:颜色空间转换

最常用的颜色空间转换如下:

  • RGB或BGR到灰度(COLOR_RGB2GRAYCOLOR_BGR2GRAY
  • RGB或BGR到YcrCb(或YCC)(COLOR_RGB2YCrCbCOLOR_BGR2YCrCb
  • RGB或BGR到HSV(COLOR_RGB2HSVCOLOR_BGR2HSV
  • RGB或BGR到Luv(COLOR_RGB2LuvCOLOR_BGR2Luv
  • 灰度到RGB或BGR(COLOR_GRAY2RGBCOLOR_GRAY2BGR

经验之谈:颜色转换其实是数学运算,如灰度化最常用的是:gray=R*0.299+G*0.587+B*0.114
参考资料:OpenCV中的颜色空间

3.专业概念:特定颜色物体追踪

  • HSV是一个常用于颜色识别的模型,相比BGR更易区分颜色,转换模式用COLOR_BGR2HSV表示。

经验之谈:OpenCV中色调H范围为[0,179],饱和度S是[0,255],明度V是[0,255]。虽然H的理论数值是0°~360°,但8位图像像素点的最大值是255,所以OpenCV中除以了2,某些软件可能使用不同的尺度表示,所以同其他软件混用时,记得归一化。
相关参考知识:RGB、HSV和HSL颜色空间
在这里插入图片描述

# 加载一张有天空的图片
sky = cv2.imread('sky.jpg')
# 蓝色的范围,不同光照条件下不一样,可灵活调整(手动设置)
lower_blue = np.array([15, 60, 60])
upper_blue = np.array([130, 255, 255])
# 从BGR转换到HSV
hsv = cv2.cvtColor(sky, cv2.COLOR_BGR2HSV)
# inRange():介于lower/upper之间的为白色,其余黑色,
# (类似阈值二值化,高于或低于都为0,中间的都变位255)
mask = cv2.inRange(sky, lower_blue, upper_blue)
# 只保留原图中的蓝色部分,图像的“与”操作
res = cv2.bitwise_and(sky, sky, mask=mask)
  • 这里没看懂蓝色的HSV值的上下限lower和upper范围是怎么得到的,虽然课程项目有解释,但是也没理解……之后感兴趣再百度。

经验之谈:Lab颜色空间也经常用来做颜色识别,有兴趣的同学可以了解下。

4.阈值分割

个人内心:在智能车制作中,处理图像时也有计算阈值、二值化操作,其中Otsu阈值法也算用c自己实现过了。这里感觉无比熟系。不过后文提到,“阈值分割不同二值化”,二者是两个意思,长知识。

  • 使用固定阈值、自适应阈值和Otsu阈值法"二值化"图像
  • OpenCV函数:cv2.threshold(), cv2.adaptiveThreshold()

①自适应阈值

  • 看得出来固定阈值是在整幅图片上应用一个阈值进行分割,它并不适用于明暗分布不均的图片。 cv2.adaptiveThreshold()自适应阈值会每次取图片的一小部分计算阈值,这样图片不同区域的阈值就不尽相同。它有5个参数,其实很好理解,先看下效果:

cv2.adaptiveThreshold()函数参数:
参数1:要处理的原图
参数2:最大阈值,一般为255
参数3:小区域阈值的计算方式
ADAPTIVE_THRESH_MEAN_C:小区域内取均值
ADAPTIVE_THRESH_GAUSSIAN_C:小区域内加权求和,权重是个高斯核
参数4:阈值方式(跟前面讲的那5种相同)
参数5:小区域的面积,如11就是11*11的小块
参数6:最终阈值等于小区域计算出的阈值再减去此值

  • 建议读者调整下参数看看不同的结果。
# 自适应阈值对比固定阈值
img = cv2.imread('lena.jpg', 0)

# 固定阈值
ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 自适应阈值, ADAPTIVE_THRESH_MEAN_C:小区域内取均值
th2 = cv2.adaptiveThreshold(
    img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 4)
# 自适应阈值, ADAPTIVE_THRESH_GAUSSIAN_C:小区域内加权求和,权重是个高斯核
th3 = cv2.adaptiveThreshold(
    img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 17, 6)

titles = ['Original', 'Global(v = 127)', 'Adaptive Mean', 'Adaptive Gaussian']
images = [img, th1, th2, th3]
plt.figure(figsize=(12,12))
for i in range(4):
    plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i], fontsize=8)
    plt.xticks([]), plt.yticks([])

在这里插入图片描述

②Otsu阈值

在前面固定阈值中,我们是随便选了一个阈值如127,那如何知道我们选的这个阈值效果好不好呢?答案是:不断尝试,所以这种方法在很多文献中都被称为经验阈值。Otsu阈值法就提供了一种自动高效的二值化方法。

  • 迭代阈值法和Otsu阈值都假设阈值,然后计算衡量指标,把所有可能都计算一遍后得到算法认为的最佳阈值。
  • cv2.threshold()用来进行固定阈值分割。固定阈值不适用于光线不均匀的图片,所以用 cv2.adaptiveThreshold()进行自适应阈值分割。
  • 二值化跟阈值分割并不等同。针对不同的图片,可以采用不同的阈值方法。

5.图像几何变换

  • 实现旋转、平移和缩放图片
  • OpenCV函数:cv2.resize(), cv2.flip(), cv2.warpAffine()

①缩放图片

  • 缩放就是调整图片的大小,使用cv2.resize()函数实现缩放。可以按照比例缩放,也可以按照指定的大小缩放: 我们也可以指定缩放方法interpolation,更专业点叫插值方法,默认是INTER_LINEAR,全部可以参考:InterpolationFlags

缩放过程中有五种插值方式:

  • cv2.INTER_NEAREST 最近邻插值
  • cv2.INTER_LINEAR 线性插值
  • cv2.INTER_AREA 基于局部像素的重采样,区域插值
  • cv2.INTER_CUBIC 基于邻域4x4像素的三次插值
  • cv2.INTER_LANCZOS4 基于8x8像素邻域的Lanczos插值
img = cv2.imread('cat.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 按照指定的宽度、高度缩放图片
res = cv2.resize(img, (400, 500))
# 按照比例缩放,如x,y轴均放大一倍
res2 = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_LINEAR)
plt.subplot(121)
plt.imshow(res)
plt.subplot(122)
plt.imshow(res2)

在这里插入图片描述

②翻转图片

  • 镜像翻转图片,可以用cv2.flip()函数: 其中,参数2 = 0:垂直翻转(沿x轴),参数2 > 0: 水平翻转(沿y轴),参数2 < 0: 水平垂直翻转。
dst = cv2.flip(img, -1)
plt.imshow(dst)
# 省略排版,为了方便偷懒,之后几个类似操作不放图片了。

③平移图片

  • 要平移图片,我们需要定义下面这样一个矩阵,tx,ty是向x和y方向平移的距离:

  • 平移是用仿射变换函数cv2.warpAffine()实现的:

# 平移图片
import numpy as np
# 获得图片的高、宽
rows, cols = img.shape[:2]

# 定义平移矩阵,需要是numpy的float32类型
# x轴平移200,y轴平移500
M = np.float32([[1, 0, 200], [0, 1, 500]])
# 用仿射变换实现平移
dst = cv2.warpAffine(img, M, (cols, rows))

plt.imshow(dst)

6.绘图功能

  • 绘制各种几何形状、添加文字
  • OpenCV函数:cv2.line(), cv2.circle(), cv2.rectangle(), cv2.ellipse(), cv2.putText()

绘制形状的函数有一些共同的参数,提前在此说明一下:

  • img:要绘制形状的图片
    color:绘制的颜色
    — 彩色图就传入BGR的一组值,如蓝色就是(255,0,0)
    — 灰度图,传入一个灰度值就行
    thickness:线宽,默认为1;对于矩形/圆之类的封闭形状而言,传入-1表示填充形状
    lineType:线的类型。默认情况下,它是8连接的。cv2.LINE_AA 是适合曲线的抗锯齿线。

①画线

  • 画直线只需指定起点和终点的坐标就行:
img = cv2.imread('lena.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 画一条线宽为5的红色直线,参数2:起点,参数3:终点
cv2.line(img, (0, 350), (800, 5), (255, 0, 0), 10)
plt.imshow(img)

②画矩形

  • 画矩形需要知道左上角和右下角的坐标:
# 画一个矩形,左上角坐标(40, 40),右下角坐标(80, 80),框颜色为绿色
img = cv2.rectangle(img, (40, 40), (80, 80), (0, 255, 0),2) 
# 画一个矩形,左上角坐标(40, 40),右下角坐标(80, 80),框颜色为绿色,填充这个矩形
img = cv2.rectangle(img, (40, 40), (80, 80), (0, 255, 0),-1) 
plt.imshow(img)

③添加文字

使用cv2.putText()添加文字,它的参数也比较多,同样请对照后面的代码理解这几个参数:

  • 参数2:要添加的文本
  • 参数3:文字的起始坐标(左下角为起点)
  • 参数4:字体
  • 参数5:文字大小(缩放比例)
# 添加文字,加载字体
font = cv2.FONT_HERSHEY_SIMPLEX
# 添加文字hello
cv2.putText(img, 'hello', (10, 200), font,
            4, (255, 255, 255), 2, lineType=cv2.LINE_AA)

在这里插入图片描述

  • 如果要添加中文,就比较麻烦。
# 参考资料 https://blog.csdn.net/qq_41895190/article/details/90301459
# 引入PIL的相关包
from PIL import Image, ImageFont,ImageDraw
from numpy import unicode

def paint_chinese_opencv(im,chinese,pos,color):
    img_PIL = Image.fromarray(cv2.cvtColor(im,cv2.COLOR_BGR2RGB))
    # 加载中文字体
    font = ImageFont.truetype('NotoSansCJKsc-Medium.otf',25)
    # 设置颜色
    fillColor = color
    # 定义左上角坐标
    position = pos
    # 判断是否中文字符
    if not isinstance(chinese,unicode):
        # 解析中文字符
        chinese = chinese.decode('utf-8')
    # 画图
    draw = ImageDraw.Draw(img_PIL)
    # 画文字
    draw.text(position,chinese,font=font,fill=fillColor)
    # 颜色通道转换
    img = cv2.cvtColor(np.asarray(img_PIL),cv2.COLOR_RGB2BGR)
    return img

plt.imshow(paint_chinese_opencv(img,'中文',(100,100),(255,255,0)))

小结
cv2.line()画直线,cv2.circle()画圆,cv2.rectangle()画矩形,cv2.ellipse()画椭圆,cv2.polylines()画多边形,cv2.putText()添加文字。
画多条直线时,cv2.polylines()要比cv2.line()高效很多。
要在图像中打上中文,可以用PIL库结合OpenCV实现。

7.图像间数学运算

  • 图片间的数学运算,如相加、按位运算等
  • OpenCV函数:cv2.add(), cv2.addWeighted(), cv2.bitwise_and()

①图片相加

  • 要叠加两张图片,可以用cv2.add()函数,相加两幅图片的形状(高度/宽度/通道数)必须相同。numpy中可以直接用res = img + img1相加,但这两者的结果并不相同:
x = np.uint8([250])
y = np.uint8([10])
print(cv2.add(x, y))  # 250+10 = 260 => [[255]]
print(x + y)  # 250+10 = 260 % 256 = [4]
# 如果是二值化图片(只有0和255两种值),两者结果是一样的(用numpy的方式更简便一些)。

②图像混合

  • 图像混合cv2.addWeighted()也是一种图片相加的操作,只不过两幅图片的权重不一样,γ相当于一个修正值:在这里插入图片描述
img1 = cv2.imread('lena.jpg')
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
img2 = cv2.imread('cat.png')
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
img2 = cv2.resize(img2, (350, 350))
# 两张图片相加
res = cv2.addWeighted(img1, 0.6, img2, 0.4, 0)

plt.imshow(res)

在这里插入图片描述

③按位操作

  • 按位操作包括按位与/或/非/异或操作,有什么用途呢?
  • 如果将两幅图片直接相加会改变图片的颜色,如果用图像混合,则会改变图片的透明度,所以我们需要用按位操作。首先来了解一下 掩膜(mask)的概念:掩膜是用一副二值化图片对另外一幅图片进行局部的遮挡
  • 其实有点类似ps中的布尔操作,可以做抠图用
img1 = cv2.imread('lena.jpg')
img2 = cv2.imread('logo.jpg')
img2 = cv2.resize(img2, (350, 350))
# 把logo放在左上角,所以我们只关心这一块区域
rows, cols = img2.shape[:2]
roi = img1[:rows, :cols]

# 创建掩膜
img2gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)

# 保留除logo外的背景
img1_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)
dst = cv2.add(img1_bg, img2)  # 进行融合
img1[:rows, :cols] = dst  # 融合后放在原图上

plt.imshow(dst)

在这里插入图片描述

小结
cv2.add()用来叠加两幅图片,cv2.addWeighted()也是叠加两幅图片,但两幅图片的权重不一样。
cv2.bitwise_and(), cv2.bitwise_not(), cv2.bitwise_or(), cv2.bitwise_xor()分别执行按位与/或/非/异或运算。掩膜就是用来对图片进行全局或局部的遮挡。

8.平滑图像

  • 模糊/平滑图片来消除图片噪声
  • OpenCV函数:cv2.blur(), cv2.GaussianBlur(), cv2.medianBlur(), cv2.bilateralFilter()

①滤波与模糊

关于滤波和模糊:

  • 它们都属于卷积,不同滤波方法之间只是卷积核不同(对线性滤波而言)
  • 低通滤波器是模糊,高通滤波器是锐化
  • 低通滤波器就是允许低频信号通过,在图像中边缘和噪点都相当于高频部分,所以低通滤波器用于去除噪点、平滑和模糊图像。高通滤波器则反之,用来增强图像边缘,进行锐化处理。
    常见噪声有椒盐噪声高斯噪声,椒盐噪声可以理解为斑点,随机出现在图像中的黑点或白点;高斯噪声可以理解为拍摄图片时由于光照等原因造成的噪声。

②均值滤波

  • 均值滤波是一种最简单的滤波处理,它取的是卷积核区域内元素的均值,用cv2.blur()实现,如3×3的卷积核:kernel=1/9*[[1,1,1],[1,1,1],[1,1,1]]
img = cv2.imread('lena.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
blur = cv2.blur(img, (10, 10))  # 均值模糊
plt.imshow(blur)
# 就是模糊了,我也不知道不同模糊有什么区别,只有很糊和一般糊。还有比较糊……

③方框滤波

  • 方框滤波跟均值滤波很像,如3×3的滤波核如下:k=a*[[1,1,1],[1,1,1],[1,1,1]]
  • cv2.boxFilter()函数实现,当可选参数normalizeTrue的时候,方框滤波就是均值滤波,上式中的a就等于1/9;normalizeFalse的时候,a=1,相当于求区域内的像素和。
# 前面的均值滤波也可以用方框滤波实现:normalize=True
blur = cv2.boxFilter(img, -1, (9, 9), normalize=True)
plt.imshow(blur)

在这里插入图片描述

④高斯滤波

  • 前面两种滤波方式,卷积核内的每个值都一样,也就是说图像区域中每个像素的权重也就一样。高斯滤波的卷积核权重并不相同:中间像素点权重最高,越远离中心的像素权重越小。
  • 显然这种处理元素间权值的方式更加合理一些。图像是2维的,所以我们需要使用2维的高斯函数,比如OpenCV中默认的3×3的高斯卷积核:k=[[0.0625,0.125,0.0625],[0.125,0.25,0.0625],[0.0625,0.125,0.0625]]
  • OpenCV中对应函数为cv2.GaussianBlur(src,ksize,sigmaX): 参数3 σx值越大,模糊效果越明显。高斯滤波相比均值滤波效率要慢,但可以有效消除高斯噪声,能保留更多的图像细节,所以经常被称为最有用的滤波器。均值滤波与高斯滤波的对比结果如下(均值滤波丢失的细节更多)
# 均值滤波vs高斯滤波
gaussian = cv2.GaussianBlur(img, (9, 9), 1)  # 高斯滤波
plt.imshow(gaussian)

⑤中值滤波

  • 中值又叫中位数,是所有数排序后取中间的值。中值滤波就是用区域内的中值来代替本像素值,所以那种孤立的斑点,如0或255很容易消除掉,适用于去除椒盐噪声和斑点噪声。中值是一种非线性操作,效率相比前面几种线性滤波要慢。
median = cv2.medianBlur(img, 9)  # 中值滤波
plt.imshow(median)

⑥双边滤波

  • 模糊操作基本都会损失掉图像细节信息,尤其前面介绍的线性滤波器,图像的边缘信息很难保留下来。然而,边缘(edge)信息是图像中很重要的一个特征,所以这才有了双边滤波。用cv2.bilateralFilter()函数实现:可以看到,双边滤波明显保留了更多边缘信息。
blur = cv2.bilateralFilter(img, 9, 75, 75)  # 双边滤波
plt.imshow(blur)

⑦图像锐化

kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32) #定义一个核
dst = cv2.filter2D(img, -1, kernel=kernel)
plt.imshow(dst)

9.边缘检测

Canny J . A Computational Approach To Edge Detection[J]. IEEE Transactions on Pattern Analysis and Machine Intelligence, 1986, PAMI-8(6):679-698.

  • Canny边缘检测的简单概念、OpenCV函数:cv2.Canny()
  • Canny边缘检测方法常被誉为边缘检测的最优方法:cv2.Canny()进行边缘检测,参数2、3表示最低、高阈值,下面来解释下具体原理。

经验之谈:之前我们用低通滤波的方式模糊了图片,那反过来,想得到物体的边缘,就需要用到高通滤波。

Canny边缘检测

Canny边缘提取的具体步骤如下:

  1. 使用5×5高斯滤波消除噪声:边缘检测本身属于锐化操作,对噪点比较敏感,所以需要进行平滑处理。
  2. 计算图像梯度的方向:
    首先使用Sobel算子计算两个方向上的梯度$ G_x 和和和 G_y $,然后算出梯度的方向:
    保留这四个方向的梯度:0°/45°/90°/135°,有什么用呢?我们接着看。
  3. 取局部极大值:
    梯度其实已经表示了轮廓,但为了进一步筛选,可以在上面的四个角度方向上再取局部极大值
  4. 滞后阈值:
    经过前面三步,就只剩下0和可能的边缘梯度值了,为了最终确定下来,需要设定高低阈值:
    1.像素点的值大于最高阈值,那肯定是边缘
    2.同理像素值小于最低阈值,那肯定不是边缘
    3.像素值介于两者之间,如果与高于最高阈值的点连接,也算边缘,所以上图中C算,B不算
  • Canny推荐的高低阈值比在2:1到3:1之间。
img = cv2.imread('lena.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
edges = cv2.Canny(img, 30, 70)  # canny边缘检测
plt.imshow(edges)

在这里插入图片描述

先阈值分割后检测

  • 其实很多情况下,阈值分割后再检测边缘,效果会更好。
_, thresh = cv2.threshold(img, 124, 255, cv2.THRESH_BINARY)
edges = cv2.Canny(thresh, 30, 70)
plt.imshow(edges)

在这里插入图片描述

小结
Canny是用的最多的边缘检测算法,用cv2.Canny()实现。

10.腐蚀与膨胀

  • 了解形态学操作的概念
  • 学习膨胀、腐蚀、开运算和闭运算等形态学操作
  • OpenCV函数:cv2.erode(), cv2.dilate(), cv2.morphologyEx()

啥叫形态学操作
形态学操作其实就是改变物体的形状,比如腐蚀就是"变瘦",膨胀就是"变胖"。

经验之谈:形态学操作一般作用于二值化图,来连接相邻的元素或分离成独立的元素。腐蚀和膨胀是针对图片中的白色部分!

①腐蚀

  • 腐蚀的效果是把图片"变瘦",其原理是在原图的小区域内取局部最小值。因为是二值化图,只有0和255,所以小区域内有一个是0该像素点就为0。
  • 这样原图中边缘地方就会变成0,达到了瘦身目的
  • OpenCV中用cv2.erode()函数进行腐蚀,只需要指定核的大小就行:
img = cv2.imread('lena.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
kernel = np.ones((5, 5), np.uint8)
erosion = cv2.erode(img, kernel)  # 腐蚀
plt.imshow(erosion)

这个核也叫结构元素,因为形态学操作其实也是应用卷积来实现的。结构元素可以是矩形/椭圆/十字形,可以用cv2.getStructuringElement()来生成不同形状的结构元素,比如:

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))  # 矩形结构
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))  # 椭圆结构
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))  # 十字形结构

在这里插入图片描述

②膨胀

  • 膨胀与腐蚀相反,取的是局部最大值,效果是把图片"变胖":
dilation = cv2.dilate(img, kernel)  # 膨胀
plt.imshow(dilation)

在这里插入图片描述

11.开/闭运算

  • 先腐蚀后膨胀叫开运算(因为先腐蚀会分开物体,这样容易记住),其作用是:分离物体,消除小区域。这类形态学操作用cv2.morphologyEx()函数实现:
  • 闭运算则相反:先膨胀后腐蚀(先膨胀会使白色的部分扩张,以至于消除/"闭合"物体里面的小黑洞,所以叫闭运算)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))  # 定义结构元素
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)  # 开运算
plt.subplot(121)
plt.imshow(opening)

closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)  # 闭运算
plt.subplot(122)
plt.imshow(closing)

在这里插入图片描述

经验之谈:很多人对开闭运算的作用不是很清楚,但看上图↑,不用怕:如果我们的目标物体外面有很多无关的小区域,就用开运算去除掉;如果物体内部有很多小黑洞,就用闭运算填充掉。

五、使用OpenCV摄像头与加载视频

  • 学习打开摄像头捕获照片、播放本地视频、录制视频等。打开摄像头并捕获照片;播放本地视频,录制视频;
  • OpenCV函数:cv2.VideoCapture(), cv2.VideoWriter()
import IPython
# 加载视频
IPython.display.Video('demo_video.mp4')

更多的内容不详解了,先弄懂图片操作。视频操作的介绍在课程项目里有介绍。

六、 图像分类任务概念导入

  • 我早期入门时就是从吴恩达的教程入门的,现在回想总结,想起其实学的就是图像分类,而且应该是二分类。通过线性网络(?),输入得到0或1的输出。

图源《如何创造可信的AI》在这里插入图片描述

专业名词 / 概念:

  • Image Classification: 图像分类,用于识别图像中物体的类别(如:bottle、cup、cube)。
  • Object Localization: 目标检测,用于检测图像中每个物体的类别,并准确标出它们的位置。
  • Semantic Segmentation: 图像语义分割,用于标出图像中每个像素点所属的类别,属于同一类别的像素点用一个颜色标识。
  • Instance Segmentation: 实例分割,值得注意的是,目标检测任务只需要标注出物体位置,实例分割任务不仅要标注出物体位置,还需要标注出物体的外形轮廓。
  • 之前在学吴恩达的深度学习的,讲的也是特征提取。经过几次入门神经网络后,至于可以看懂下图了。是指输入图像经过五次卷积操作,然后再经过2次线性网络后输出三个类别的分类。(其实是第二天上课讲的就是这个网络,现在第三天还在写第一天笔记的我才看懂了
    在这里插入图片描述

1.图像分类问题的经典数据集

①MNIST手写数字识别

MNIST是一个手写体数字的图片数据集,该数据集来由美国国家标准与技术研究所(National Institute of Standards and Technology (NIST))发起整理,一共统计了来自250个不同的人手写数字图片,其中50%是高中生,50%来自人口普查局的工作人员。该数据集的收集目的是希望通过算法,实现对手写数字的识别。

  • 这个手写笔迹的数据集见得很多,paddle的入门教程也讲这个。第一节课开头也举了个展示内容的例子。
    在这里插入图片描述
from paddle.vision.datasets import MNIST

mnist = MNIST(mode='test')

for i in range(len(mnist)):
    if i == 0:
        sample = mnist[i]
        print(sample[0].size, sample[1])

# 查看测试集第一个数字
plt.imshow(mnist[0][0])
print('手写数字是:', mnist[0][1])

②Cifar数据集

CIFAR-10
CIFAR-10数据集由10个类的60000个32x32彩色图像组成,每个类有6000个图像。有50000个训练图像和10000个测试图像。
数据集分为五个训练批次和一个测试批次,每个批次有10000个图像。测试批次包含来自每个类别的恰好1000个随机选择的图像。训练批次以随机顺序包含剩余图像,但一些训练批次可能包含来自一个类别的图像比另一个更多。总体来说,五个训练集之和包含来自每个类的正好5000张图像。

  • 以下是数据集中的类,以及来自每个类的10个随机图像:(这些类完全相互排斥。汽车和卡车之间没有重叠。“汽车”包括轿车,SUV,这类东西。“卡车”只包括大卡车。都不包括皮卡车。)
    在这里插入图片描述

CIFAR-100
CIFAR-100数据集就像CIFAR-10,除了它有100个类,每个类包含600个图像。,每类各有500个训练图像和100个测试图像。CIFAR-100中的100个类被分成20个超类。每个图像都带有一个“精细”标签(它所属的类)和一个“粗糙”标签(它所属的超类)

  • 以下是CIFAR-100中的类别列表:(略,和CIFAR-10差不多,只是更加多了)
from paddle.vision.datasets import Cifar10
cifar10 = Cifar10(mode='test')
# 查看测试集第2张图
plt.imshow(cifar10[1][0])
print('图片类别编码是:', cifar10[1][1])

③ImageNet数据集

ImageNet数据集是一个计算机视觉数据集,是由斯坦福大学的李飞飞教授带领创建。该数据集包合 14,197,122张图片和21,841个Synset索引。Synset是WordNet层次结构中的一个节点,它又是一组同义词集合。ImageNet数据集一直是评估图像分类算法性能的基准。

ImageNet 数据集是为了促进计算机图像识别技术的发展而设立的一个大型图像数据集。2016 年ImageNet 数据集中已经超过干万张图片,每一张图片都被手工标定好类别。ImageNet 数据集中的图片涵盖了大部分生活中会看到的图片类别。ImageNet最初是拥有超过100万张图像的数据集。如图下图所示,它包含了各种各样的图像,并且每张图像都被关联了标签(类别名)。每年都会举办使用这个巨大数据集的ILSVRC图像识别大赛。

2.paddle图像分类开发套件

PaddleClas图像分类开发套件

3.飞桨产品全景

飞桨产品全景

  • 有很多内容,大概知道paddle都有些什么东西了。一开始稀里糊涂,不知道paddlepaddle下为什么还有其他工具。

七、PaddleClas数据增强代码解析

  • 这一部分的内容,是讲课老师讲解paddleclas包中,数据处理部分的代码实现。打开原码一看,和并不复杂。只是几个库函数被打包了一下,把类当函数用的操作……用来入门和讲解很合适。

1.随机翻转图片

class RandFlipImage(object):
    """ random flip image 随机翻转图片
        flip_code:
            1: Flipped Horizontally 水平翻转
            0: Flipped Vertically 上下翻转
            -1: Flipped Horizontally & Vertically 水平、上下翻转
    """

    def __init__(self, flip_code=1):
        # 设置一个翻转参数,1、0或-1
        assert flip_code in [-1, 0, 1
                             ], "flip_code should be a value in [-1, 0, 1]"
        self.flip_code = flip_code

    def __call__(self, img):
        # 随机生成0或1(即是否翻转)
        if random.randint(0, 1) == 1:
            return cv2.flip(img, self.flip_code)
        else:
            return img

2.随机裁剪图片(然后缩放大小)

class RandCropImage(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)

3.随机擦除图片(挖孔)

  • 老师还吐槽了这部分的源码是错的,所以老师就随便讲了一下。
class RandomErasing(object):
    def __init__(self, EPSILON=0.5, sl=0.02, sh=0.4, r1=0.3,
                 mean=[0., 0., 0.]):
        self.EPSILON = EPSILON
        self.mean = mean
        self.sl = sl
        self.sh = sh
        self.r1 = r1

    def __call__(self, img):
    # random.uniform(x, y)方法将随机生成一个实数,它在 [x,y] 范围内。
        if random.uniform(0, 1) > self.EPSILON:
        # 表示该操作不是百分百执行,而是概率性,有0.5的可能性。
            return img

        for attempt in range(100):
            area = img.shape[0] * img.shape[1]

            target_area = random.uniform(self.sl, self.sh) * area
            aspect_ratio = random.uniform(self.r1, 1 / self.r1)

            h = int(round(math.sqrt(target_area * aspect_ratio)))
            w = int(round(math.sqrt(target_area / aspect_ratio)))

            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)
                if img.shape[2] == 3:
                    img[ x1:x1 + h, y1:y1 + w, 0] = self.mean[0]
                    img[ x1:x1 + h, y1:y1 + w, 1] = self.mean[1]
                    img[ x1:x1 + h, y1:y1 + w, 2] = self.mean[2]
                else:
                    img[x1:x1 + h, y1:y1 + w,0] = self.mean[0]
                return img
        return img

八、参考资料

猜你喜欢

转载自blog.csdn.net/Lovely_him/article/details/114355234