百度领航团图像分类课程学习笔记
发布于2021-03 浏览:583 回复:2
0
收藏

最近几天学习了领航团的图像分类课程,感觉是一个很不错的课程,附上课程链接https://aistudio.baidu.com/aistudio/education/group/info/11939

2. OpenCV库进阶操作


In [3]
import math
import random
import numpy as np
%matplotlib inline
import cv2
import matplotlib.pyplot as plt
In [4]
# 创建一副图片
img = cv2.imread('cat.png')
# 转换颜色通道
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
图像基本操作
学习ROI感兴趣区域,通道分离合并等基本操作。

ROI
ROI:Region of Interest,感兴趣区域。。

截取ROI非常简单,指定图片的范围即可
In [5]
# 截取猫脸ROI
face = img[0:740, 400:1000]
plt.imshow(face)


通道分割与合并
彩色图的BGR三个通道是可以分开单独访问的,也可以将单独的三个通道合并成一副图像。分别使用cv2.split()和cv2.merge():
In [6]
# 创建一副图片
img = cv2.imread('lena.jpg')
In [7]
# 通道分割
b, g, r = cv2.split(img)
In [8]
# 通道合并
img = cv2.merge((b, g, r))
In [9]
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img)


In [10]
RGB_Image=cv2.merge([b,g,r])
RGB_Image = cv2.cvtColor(RGB_Image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(12,12))
#显示各通道信息
plt.subplot(141)
plt.imshow(RGB_Image,'gray')
plt.title('RGB_Image')
plt.subplot(142)
plt.imshow(r,'gray')
plt.title('R_Channel')
plt.subplot(143)
plt.imshow(g,'gray')
plt.title('G_Channel')
plt.subplot(144)
plt.imshow(b,'gray')
plt.title('B_Channel')
Text(0.5,1,'B_Channel')

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

RGB或BGR到灰度(COLOR_RGB2GRAY,COLOR_BGR2GRAY)
RGB或BGR到YcrCb(或YCC)(COLOR_RGB2YCrCb,COLOR_BGR2YCrCb)
RGB或BGR到HSV(COLOR_RGB2HSV,COLOR_BGR2HSV)
RGB或BGR到Luv(COLOR_RGB2Luv,COLOR_BGR2Luv)
灰度到RGB或BGR(COLOR_GRAY2RGB,COLOR_GRAY2BGR)
经验之谈:颜色转换其实是数学运算,如灰度化最常用的是:gray=R*0.299+G*0.587+B*0.114。
参考资料:OpenCV中的颜色空间
In [11]
img = cv2.imread('lena.jpg')
# 转换为灰度图
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 保存灰度图
cv2.imwrite('img_gray.jpg', img_gray)
True
特定颜色物体追踪
HSV是一个常用于颜色识别的模型,相比BGR更易区分颜色,转换模式用COLOR_BGR2HSV表示。

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

RGB、HSV和HSL颜色空间

现在,我们实现一个使用HSV来只显示视频中蓝色物体的例子,步骤如下:

捕获视频中的一帧
从BGR转换到HSV
提取蓝色范围的物体
只显示蓝色物体
In [12]
# 加载一张有天空的图片
sky = cv2.imread('sky.jpg')
In [13]
# 蓝色的范围,不同光照条件下不一样,可灵活调整
lower_blue = np.array([15, 60, 60])
upper_blue = np.array([130, 255, 255])
In [14]
# 从BGR转换到HSV
hsv = cv2.cvtColor(sky, cv2.COLOR_BGR2HSV)
# inRange():介于lower/upper之间的为白色,其余黑色
mask = cv2.inRange(sky, lower_blue, upper_blue)
# 只保留原图中的蓝色部分
res = cv2.bitwise_and(sky, sky, mask=mask)
In [15]
# 保存颜色分割结果
cv2.imwrite('res.jpg', res)
True
In [16]
res = cv2.imread('res.jpg')
res = cv2.cvtColor(res, cv2.COLOR_BGR2RGB)
plt.imshow(res)


其中,bitwise_and()函数暂时不用管,后面会讲到。那蓝色的HSV值的上下限lower和upper范围是怎么得到的呢?其实很简单,我们先把标准蓝色的BGR值用cvtColor()转换下:
In [17]
blue = np.uint8([[[255, 0, 0]]])
hsv_blue = cv2.cvtColor(blue, cv2.COLOR_BGR2HSV)
print(hsv_blue)
[[[120 255 255]]]

结果是[120, 255, 255],所以,我们把蓝色的范围调整成了上面代码那样。

经验之谈:Lab颜色空间也经常用来做颜色识别,有兴趣的同学可以了解下。
阈值分割
使用固定阈值、自适应阈值和Otsu阈值法"二值化"图像
OpenCV函数:cv2.threshold(), cv2.adaptiveThreshold()
固定阈值分割
固定阈值分割很直接,一句话说就是像素点值大于阈值变成一类值,小于阈值变成另一类值。

cv2.threshold()用来实现阈值分割,ret是return value缩写,代表当前的阈值。函数有4个参数:

参数1:要处理的原图,一般是灰度图
参数2:设定的阈值
参数3:最大阈值,一般为255
参数4:阈值的方式,主要有5种,详情:ThresholdTypes0: THRESH_BINARY  当前点值大于阈值时,取Maxval,也就是第四个参数,否则设置为0
1: THRESH_BINARY_INV 当前点值大于阈值时,设置为0,否则设置为Maxval
2: THRESH_TRUNC 当前点值大于阈值时,设置为阈值,否则不改变
3: THRESH_TOZERO 当前点值大于阈值时,不改变,否则设置为0
4:THRESH_TOZERO_INV  当前点值大于阈值时,设置为0,否则不改变
参考资料:基于opencv的固定阈值分割_自适应阈值分割
In [18]
import cv2

# 灰度图读入
img = cv2.imread('lena.jpg', 0)
# 颜色通道转换
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 阈值分割
ret, th = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)

plt.imshow(th)


In [19]
th[100]
array([[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 0, 0],
...,
[255, 255, 255],
[255, 255, 255],
[255, 255, 255]], dtype=uint8)
In [20]
# 应用5种不同的阈值方法
# THRESH_BINARY 当前点值大于阈值时,取Maxval,否则设置为0
ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# THRESH_BINARY_INV 当前点值大于阈值时,设置为0,否则设置为Maxval
ret, th2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
# THRESH_TRUNC 当前点值大于阈值时,设置为阈值,否则不改变
ret, th3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
# THRESH_TOZERO 当前点值大于阈值时,不改变,否则设置为0
ret, th4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
# THRESH_TOZERO_INV 当前点值大于阈值时,设置为0,否则不改变
ret, th5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)

titles = ['Original', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, th1, th2, th3, th4, th5]
In [21]
plt.figure(figsize=(12,12))
for i in range(6):
plt.subplot(2, 3, i + 1)
plt.imshow(images[i], 'gray')
plt.title(titles[i], fontsize=8)
plt.xticks([]), plt.yticks([])


经验之谈:很多人误以为阈值分割就是二值化。从上图中可以发现,两者并不等同,阈值分割结果是两类值,而不是两个值。
自适应阈值
看得出来固定阈值是在整幅图片上应用一个阈值进行分割,它并不适用于明暗分布不均的图片。 cv2.adaptiveThreshold()自适应阈值会每次取图片的一小部分计算阈值,这样图片不同区域的阈值就不尽相同。它有5个参数,其实很好理解,先看下效果:

参数1:要处理的原图
参数2:最大阈值,一般为255
参数3:小区域阈值的计算方式ADAPTIVE_THRESH_MEAN_C:小区域内取均值
ADAPTIVE_THRESH_GAUSSIAN_C:小区域内加权求和,权重是个高斯核
参数4:阈值方式(跟前面讲的那5种相同)
参数5:小区域的面积,如11就是11*11的小块
参数6:最终阈值等于小区域计算出的阈值再减去此值
建议读者调整下参数看看不同的结果。
In [22]
# 自适应阈值对比固定阈值
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阈值法就提供了一种自动高效的二值化方法。

小结
cv2.threshold()用来进行固定阈值分割。固定阈值不适用于光线不均匀的图片,所以用 cv2.adaptiveThreshold()进行自适应阈值分割。
二值化跟阈值分割并不等同。针对不同的图片,可以采用不同的阈值方法。
图像几何变换
实现旋转、平移和缩放图片
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插值
In [23]
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.imshow(res)


In [24]
plt.imshow(res2)


翻转图片
镜像翻转图片,可以用cv2.flip()函数: 其中,参数2 = 0:垂直翻转(沿x轴),参数2 > 0: 水平翻转(沿y轴),参数2 < 0: 水平垂直翻转。
In [25]
dst = cv2.flip(img, 1)
In [26]
plt.imshow(dst)


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

M=[10tx01ty] M = \left[ \begin{matrix} 1 & 0 & t_x \newline 0 & 1 & t_y \end{matrix} \right]M=[1​0​tx​0​1​ty​​]

平移是用仿射变换函数cv2.warpAffine()实现的:
In [27]
# 平移图片
import numpy as np
# 获得图片的高、宽
rows, cols = img.shape[:2]
In [28]
# 定义平移矩阵,需要是numpy的float32类型
# x轴平移200,y轴平移500
M = np.float32([[1, 0, 100], [0, 1, 500]])
# 用仿射变换实现平移
dst = cv2.warpAffine(img, M, (cols, rows))
In [29]
plt.imshow(dst)


绘图功能
绘制各种几何形状、添加文字
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 是适合曲线的抗锯齿线。
画线
画直线只需指定起点和终点的坐标就行:
In [30]
img = cv2.imread('lena.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
In [31]
# 画一条线宽为5的红色直线,参数2:起点,参数3:终点
cv2.line(img, (0, 0), (800, 512), (255, 0, 0), 5)
plt.imshow(img)


画矩形
画矩形需要知道左上角和右下角的坐标:
In [32]
# 画一个矩形,左上角坐标(40, 40),右下角坐标(80, 80),框颜色为绿色
img = cv2.rectangle(img, (40, 40), (80, 80), (0, 255, 0),2)
In [33]
plt.imshow(img)


In [34]
# 画一个矩形,左上角坐标(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:文字大小(缩放比例)
In [35]
# 添加文字,加载字体
font = cv2.FONT_HERSHEY_SIMPLEX
# 添加文字hello
cv2.putText(img, 'hello', (10, 200), font,
4, (255, 255, 255), 2, lineType=cv2.LINE_AA)
array([[[255, 0, 0],
[255, 0, 0],
[255, 0, 0],
...,
[237, 148, 130],
[232, 143, 125],
[196, 107, 89]],

[[255, 0, 0],
[255, 0, 0],
[255, 0, 0],
...,
[233, 145, 125],
[231, 144, 124],
[195, 108, 88]],

[[255, 0, 0],
[255, 0, 0],
[255, 0, 0],
...,
[239, 151, 129],
[234, 150, 126],
[195, 113, 89]],

...,

[[ 87, 26, 60],
[ 92, 28, 62],
[ 91, 24, 57],
...,
[160, 68, 83],
[165, 67, 82],
[163, 63, 75]],

[[ 80, 18, 55],
[ 90, 26, 61],
[ 93, 26, 59],
...,
[167, 71, 85],
[174, 72, 86],
[173, 69, 80]],

[[ 79, 17, 54],
[ 91, 27, 62],
[ 96, 29, 64],
...,
[169, 73, 87],
[178, 73, 87],
[180, 74, 86]]], dtype=uint8)
In [36]
plt.imshow(img)


In [37]
# 参考资料 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

收藏
点赞
0
个赞
TOP
切换版块