小杰机械视觉(one day)——基础阶段结束,进入机械学习阶段。
核心:保留图片特征,将图片数据减少,进行细节化处理。
1. 无
2. 三通道RGB OpenCV使用的是BGR通道
OpenCV官方文档可以查询:
OpenCV: OpenCV moduleshttps://docs.opencv.org/4.11.0/
3. 灰度化
加权平均法最常用:
RGB的权重系数:
0.299,0.587,0.114
4.二值化
核心:
二值化:
阈值法 THRESH_BINARY
反阈值法 THRESH_BINARY_INV
OTSU阈值法 仅用于找到阈值 OTSU算法公式使用的是类间方差公式(公式内容不用记)
不经常使用,了解为主:截断阈值法(THRESH_TRUNC)
低阈值零处理(THRESH_TOZERO)
超阈值零处理(THRESH_TOZERO_INV)
5.
重点:
- blockSize:小区域的面积,通常取奇数,例如7表示7*7,后期称之为核(各种滤波和卷积等都用的到),计算的方式为滑动(从左到右,从上到下)计算
- c:最终阈值等于计算的阈值减去此值
自定义二值化
高斯分布,不做要求(但是加分项)
1.课程介绍
机器视觉本质上使用OpenCV库来解决图像问题,虽然后面深度学习也可以解决一些图像算法问题,但是CV更侧重于程序员理解图像本质进行处理,属于传统计算机视觉的范畴。
注意点:
1.数学理论:
计算其次,解决什么问题,其工程思维是重点。
2.图像理论:
目标图像的指向性,只有清楚图像理论,才能让代码的产出符合预期。
3.代码编程:
基础科目编程是手搓代码,这门课重点是理解代码的步骤和含义,以及参数如何调节能达到预期的效果。
2.编程环境
(1).本人使用的是华清远见-人工智能虚拟仿真本地服务管理平台
直接跳转:
华清远见元宇宙实验中心https://www.yyzlab.com.cn/exper/1666633925655027713
(2)Jupyter Notebook
1.介绍
是一个基于Web浏览器的Python 集成开发环境IDE,与PyCharm类似,可以编写并运行Python代码,相比于PyCharm具有以下优缺点:
优点
- 支持MarkDown笔记+代码
- 支持单独运行一个Cell的代码,可以把代码分成不同的代码块(Cell),单独运行Cell时,可以基于之前的Cell数据运行。
缺点:
- 自动补全不如PyCharm好用,需要手动点击Tab键
- 代码格式化不佳
本次教学中建议二者结合,但是不强行要求使用哪一个。
Jupyter的文件存储在华清远见的本地环境下:
2.使用
新建一个Jupyter文件,
实际上Jupyter Notebook的文件管理也是在Windows中的,因为安装在华清远见虚拟环境下,所以可以找到所有Jupyter文件。
3.python 解释器
Python基础+数据结构使用的是Python3.8,因为之前的华清远见人工智能虚拟仿真本地服务管理平台使用的3.8,后来更新到3.11,因此后续建议使用3.11。
关于使用的各种库,分为以下三种情况(任选其一):
1. 直接使用Jupyter Notebook,所有的库都已经安装好,可以直接使用,这些安装好的库,也是版本的参照:
在华清远见命令行中执行:
2. 使用PyCharm+自己的解释器,需要手动安装学过程中的每个库。
3. 使用PyCharm+华清解释器(与Jupyter共用),直接用。
2.计算机眼中的图象
(1)像素
像素是图像的基本单元,每个像素存储着图像的颜色、亮度和其他特征。一系列像素组合到一起就形成了完整的图像,在计算机中,图像以像素的形式存在并采用二进制格式进行存储。根据图像的颜色不同,每个像素可以用不同的二进制数表示。
日常生活中常见的图像是RGB三原色图。RGB图上的每个点都是由红(R)、绿(G)、蓝(B)三个颜色按照一定比例混合而成的,几乎所有颜色都可以通过这三种颜色按照不同比例调配而成。在计算机中,RGB三种颜色被称为RGB三通道,根据这三个通道存储的像素值,来对应不同的颜色。
RGB每个通道的范围是0-255,8位(每个通道256颜色,即2的8次方),255表示颜色打满,0表示黑色,(255,0,0)表示纯红色,(0,0,0)表示纯黑色。
配色网站:
在线颜色选择器 | RGB颜色查询对照表https://tools.jb51.net/static/colorpicker/
QQ截图可以直接看到RGB色值
(2)计算机中图像的存储
在本次学习的过程中,图像处理使用OpenCV库实现,OpenCV使用一个三维数组(矩阵)存储一张图片的数据。
一个RGB图像放在内存中是一个三维数组,其中第一维存储的是图像的高度height(行数rows),第二维存储的是图像的宽度width(列数cols),第三维存储的是每个像素点RGB的数值,计算机中处理图像的本质就是对三维数组进行计算。
(3)虚拟仿真平台
针对图象操作的一些实验
(4)代码
OpenCV官方文档可以查询:
OpenCV: OpenCV moduleshttps://docs.opencv.org/4.11.0/
OpenCV的源代码是C++实现的,Python可以调用C++源代码。基础使用库Python语言比较方便,但是做到行业顶尖需要C++。
手搓创建图象代码
import cv2 # OpenCV库
import numpy as np
import matplotlib.pyplot as plt# 返回值:全0(纯黑)的图像
image = np.zeros((700, 700, 3), # 高度700,宽度700的3维数组,用于存储图像数据dtype=np.uint8) # 8位无符号整型:0-255# print(image)# 小矩形块的边长
block_size = 100# 遍历全图的像素点
for i in range(0, 700, block_size):for j in range(0, 700, block_size):# 每个小矩形块的左上角top_left = (j, i)# 根据左上角计算小矩形块的右下角bottom_right = (j + block_size - 1, i + block_size - 1)# print(bottom_right)# 绘制红色的X,将两个对角线的小矩形块填充为红色# i//block_size == j // block_size \对角线# i // block_size + j // block_size == 6 /对角线if (i // block_size == j // block_size) or (i // block_size + j // block_size == 6):# 拿掉四个角if i == 0 or i == 600:continue# 筛选之后的左上角点# print(top_left)# 画矩形cv2.rectangle(image, # 在哪个图像上画top_left, # 左上角bottom_right, # 右下角(255, 128, 0), # OpenCV使用的是BGR通道,-1 # 填充)else:# 画白线cv2.rectangle(image, # 在哪个图像上画top_left, # 左上角bottom_right, # 右下角(255, 255, 255), # OpenCV使用的是BGR通道2 # 线宽)# 将OpenCV的BGR转换成RBG以便于matplotlib显示
# 返回值是转换之后的RGB图像
image_rgb = cv2.cvtColor(image, # 原始图像cv2.COLOR_BGR2RGB # BGR到RGB
)# 在matplotlib中显示
plt.imshow(image_rgb)
plt.title('Original Image')
plt.axis('off') # 取消坐标轴显示
plt.show()# 拆分通道
b, g, r = cv2.split(image)
print(b) # 拆分出来的数据不能直接用,因为少了一个维度# 创建三个全0图像
blue_channel = np.zeros((700, 700, 3), dtype=np.uint8)
# 补充拆分出来的蓝色数据
blue_channel[:, :, 0] = b
print(blue_channel)green_channel = np.zeros((700, 700, 3), dtype=np.uint8)
green_channel[:, :, 1] = gred_channel = np.zeros((700, 700, 3), dtype=np.uint8)
red_channel[:, :, 2] = r# 把BGR转换为RGB
blue_channel_rgb = cv2.cvtColor(blue_channel, cv2.COLOR_BGR2RGB)
green_channel_rgb = cv2.cvtColor(green_channel, cv2.COLOR_BGR2RGB)
red_channel_rgb = cv2.cvtColor(red_channel, cv2.COLOR_BGR2RGB)# 显示
plt.subplot(131) # 一行三列的第一张图
plt.imshow(blue_channel_rgb)
plt.title('Blue Channel')
plt.axis('off')plt.subplot(132) # 一行三列的第2张图
plt.imshow(green_channel_rgb)
plt.title('Green Channel')
plt.axis('off')plt.subplot(133) # 一行三列的第3张图
plt.imshow(red_channel_rgb)
plt.title('Red Channel')
plt.axis('off')
plt.show()
3.灰度实验
彩色图是由RGB三个通道组成,灰度图只有一个通道,也成为单通道图像,所以彩色图转换为灰度图的过程本质就是将RGB三通道合并成一个通道的过程。
实验中介绍三种合并方法:
- 最大值法
- 平均值法
- 加权平均值法
(1)虚拟仿真实验
图片输入节点:
建议少折腾,直接用自带图像
三通道图节点:
灰度节点:
本实验主要讲解上面的三种方法的差别。
1.最大值法
最大值法就是从三个通道的值中选择最大值作为灰度后的数值,因此最大值法生成的灰度图会偏亮,适合给较暗的图像使用。
2. 平均值法
平均值法使用三个通道的平均值作为灰度化后的数值,对结果需要进行取整。
3.加权平均法
使用RGB的加权平均数作为灰度化的数值,RGB的权重系数分别为0.299,0.587,0.114,这个数值是根据人类生物学实验得来的。
4.代码
手搓算法第一次需要编写并理解,后续真正使用调用接口即可。
import cv2
import numpy as np
import matplotlib.pyplot as pltif __name__ == '__main__':# 1. 图片输入path = 'lena.png'image_np = cv2.imread(path)image_shape = image_np.shapeprint(image_shape) # (512, 512, 3)# print(image_np)# 2. 三通道图 + 灰度化# 创建一个纯黑图,分辨率相同,用于后续存储灰度后的数据image_np_gray = np.zeros((image_shape[0], image_shape[1], 3), dtype=np.uint8)print(image_np_gray.shape)# 数据拷贝image_np_gray = image_np.copy()# print(image_np_gray)# 三个权重wr = 0.299wg = 0.587wb = 0.114# 遍历全局像素for i in range(image_shape[0]):for j in range(image_shape[1]):# print(i,j)# 加权平均法avg = image_np[i, j][2] * wr + image_np[i, j][1] * wg + image_np[i, j][0] * wb# print(avg)avg = int(avg)# print(avg)# 存储到image_np_gray中image_np_gray[i, j] = avgprint(image_np_gray.shape)print(image_np_gray) # 如果BGR三通道的数值相同,表示灰度,但真正的灰度只需要一个通道# 4. 图片输出# plt.imshow(image_np_gray)# plt.title('image_np_gray')# plt.axis('off')# plt.show()# 后续彩图可以直接使用OpenCV展示,CV展示图片会收到系统缩放的影响cv2.imshow('image_np_gray', # 必须:titleimage_np_gray) # 展示的图像cv2.waitKey(0) # 等待按下任意按键关闭弹窗
4.二值化
(1)概念
二值化就是将某张图片的像素改成只有两种值(最常见的是纯黑和纯白),二值化操作的图像必须是灰度图,二值化的过程实际上是将一张灰度图上的像素根据某种规则修改为两种数值(0和maxval),使图像呈现出更为强烈的黑白效果,可以帮助后续处理图像轮廓或边缘等特征。
(2)阈值法(THRESH_BINARY)
阈值法就是通过设定一个阈值thresh,将灰度图的每一个像素与该阈值进行比较,小于等于阈值的像素被设置为0(黑),大于阈值的像素被设置为maxval。
第一次使用手搓,后续直接调用接口。
import cv2
import numpy as npif __name__ == '__main__':# 1. 图片输入path = 'lena.png'image_np = cv2.imread(path)image_shape = image_np.shape# 2. 灰度化(为了手搓二值化,暂时手搓灰度化)image_np_gray = np.zeros((image_shape[0], image_shape[1], 3), dtype=np.uint8)print(image_np_gray.shape)# 数据拷贝image_np_gray = image_np.copy()# 三个权重wr = 0.299wg = 0.587wb = 0.114# 遍历全局像素for i in range(image_shape[0]):for j in range(image_shape[1]):# 加权平均法avg = image_np[i, j][2] * wr + image_np[i, j][1] * wg + image_np[i, j][0] * wbavg = int(avg)# 存储到image_np_gray中image_np_gray[i, j] = avgprint(image_np_gray)# 3. 二值化thresh = 127 # 阈值maxval = 255 # 最大值# 遍历全图像素点for i in range(image_shape[0]):for j in range(image_shape[1]):# 如果像素值小于等于阈值,则设定为0if image_np_gray[i,j][0] <= thresh:image_np_gray[i,j] = 0else: # 其他情况下为最大值image_np_gray[i,j] = maxval# 4. 图片输出cv2.imshow('image_np_gray',image_np_gray)cv2.waitKey(0)
(3)反阈值法(THRESH_BINARY_INV)
反阈值法与阈值法相反,当灰度图的像素值大于阈值时,像素值会被设置为0;当灰度图的像素值小于等于阈值时,像素值会被设置为最大值。
直接调用OpenCV接口
import cv2
import numpy as npif __name__ == '__main__':# 1. 图片输入path = 'lena.png'image_np = cv2.imread(path)# 2. 灰度化(为了手搓二值化,暂时手搓灰度化)image_np_gray = cv2.cvtColor(image_np,cv2.COLOR_BGR2GRAY) # BGR→灰度# 3. 二值化thresh = 127 # 阈值maxval = 255 # 最大值# 二值化# 返回值1:阈值(thresh的值,部分算法是自动计算的,本例没用)# 返回值2:二值化之后的图片ret, image_np_gray = cv2.threshold(image_np_gray, # 灰度图thresh, # 阈值maxval, # 最大值cv2.THRESH_BINARY_INV # 二值化类型)# 4. 图片输出cv2.imshow('image_np_gray', image_np_gray)cv2.waitKey(0)
通常让处理的主体为最大值,背景为黑色。
(4) 截断阈值法(THRESH_TRUNC)
截断阈值法是将灰度图中所有像素值与阈值比较,当像素值大于阈值时被修改为为阈值,小于等于阈值的部分不变。
(5) 低阈值零处理(THRESH_TOZERO)
低阈值零处理表示像素值小于等于阈值的部分被设置为0,大于阈值的部分不变。
(6) 超阈值零处理(THRESH_TOZERO_INV)
超阈值零处理表示像素值大于阈值的部分被设置为0,小于等于阈值的部分不变。
(7) OTSU阈值法
以上的二值化方法都需要手动设置阈值,但是在不同的环境下,摄像头拍摄的图像可能存在差异,导致手动设置的阈值并不适用于所有图像,这可能会使二值化的效果不理想。因此需要一种自动计算图片阈值的二值化方法,可以提高图像处理的准确性和鲁棒性。
使用OTSU阈值法可以找到一张图片的阈值,需要注意的是,这种方法仅用于找到阈值,并不能二值化,是在二值化之前增加的算法。
双峰图片就是指灰度图的直方图上有两个峰值,直方图是对灰度图的每个像素值点的个数的统计图(横轴是灰度从0到255,纵轴是像素点的频数)。
OTSU算法就是通过一个值把图片分为前景色和背景色,通过统计学的方法来验证该值的合理性:最大类间方差。
根据上述计算的结果套用OTSU算法公式:
g是前景与背景的类方差,这个值越大说明前景与背景的差别越大,效果就越好。OTSU算法就是在灰度图的像素值范围内,遍历所有像素值(潜在的阈值T),算出每个像素值对应的g值,找到最大的g值对应的像素值(最终的T),这个点在直方图上基本上就是双峰之间的谷底。
通过OTSU算法得到阈值T之后,就可以结合之前的任意一种二值化方法进行操作,在虚拟仿真实验中只提供了两种二值化与OTSU结合的方式:
在使用OTSU算法时,虚拟仿真实验的thresh参数将失效,通过函数接口送入的thresh值也会失效,之前的第一个返回值会生效。
import cv2
import numpy as npif __name__ == '__main__':# 1. 图片输入path = 'lena.png'image_np = cv2.imread(path)# 2. 灰度化(为了手搓二值化,暂时手搓灰度化)image_np_gray = cv2.cvtColor(image_np,cv2.COLOR_BGR2GRAY) # BGR→灰度# 3. 二值化thresh = 145 # 阈值maxval = 255 # 最大值# 二值化# 返回值1:OTSU计算的阈值# 返回值2:二值化之后的图片ret, image_np_gray = cv2.threshold(image_np_gray, # 灰度图thresh, # 阈值maxval, # 最大值cv2.THRESH_BINARY + cv2.THRESH_OTSU # OTSU+二值化)print('OTSU:', ret)# 4. 图片输出cv2.imshow('image_np_gray', image_np_gray)cv2.waitKey(0)
5.自适应二值化
(1)概念
与上一章的二值化算法相比,自适应二值化更加适合明暗分布不均匀的图片,导致图片上每个小部分都有自己的前景和背景,此时可以使用自适应二值化,自适应二值化会对图像的所有像素点单独计算阈值,这样更好的针对每个像素点判断是前景还是背景。
自适应二值化可以更好的保留图片的信息。
参数:
- maxval:最大值,通常为255
- adaptiveMethod:小区域阈值的计算方式
- ADAPTIVE_THRESH_MEAN_C:小区域内取均值
- ADAPTIVE_THRESH_GAUSSIAN_C:小区域内加权求和,权重是个高斯核
- thresholdType:二值化方法
- THRESH_BINARY 阈值法
- THRESH_BINARY_INV 反阈值法
- blockSize:小区域的面积,通常取奇数,例如7表示7*7,后期称之为核(各种滤波和卷积等都用的到),计算的方式为滑动(从左到右,从上到下)计算
- c:最终阈值等于计算的阈值减去此值
(2)取均值 ADAPTIVE_THRESH_MEAN_C
取blockSize=3,因此小区域面积为3*3,从原图的左上角像素点(162)开始计算其邻域的平均值,如果处于边缘区域就会进行边缘填充,填充值就是边界的像素点,如下所示。
计算九个点的平均值后减去c,就是最终当前像素点的阈值。
可以手动调节C值和BlockSize等值观察二值化结果:
- blockSize分别为7和3的二值化效果
- C值越大前景色越多
(3) 高斯加权求和 ADAPTIVE_THRESH_GAUSSIAN_C
1.一维高斯分布
高斯分布,通过概率密度函数进行定义,一维的高斯概率分布函数为:
通过改变函数中和的值,我们可以得到如下图像,其中均值为μ,标准差为σ。
2.二维高速分布
根据一维高斯分布,拓展到二维高斯分布的表达式为:
高斯概率函数相对二维坐标产生,其中(x,y)为点坐标,以3*3的核为例,其点坐标分布如下:
把上面的九个点坐标带入到公式中,就可以得到一个高斯权重核
3.二维高斯分布权重计算规则
把4.2节的九个点坐标分别计算权重并不是简单的套用公式,而是要分为以下几种情况:
用户设置了σ
此时把σ和x,y分别带入高斯函数计算权重。
用户没有设置σ
此时要看blockSize的大小,即核的边长。
- 如果blockSize小于等于7
此时直接套用固定系数
例如核的blockSize=3时,使用转置矩阵进行乘法,得到九个点的权重值:
- blockSize大于等于7
直接套用下面的公式:
size表示kernel(核)的尺寸,即blockSize,可以计算出σ的值,再结合(x,y)的坐标值带入到高斯密度函数中计算出权重。
4.代码
import cv2
import numpy as npif __name__ == '__main__':# 1. 图片输入path = 'lena.png'image_np = cv2.imread(path)# 2. 灰度化(为了手搓二值化,暂时手搓灰度化)image_np_gray = cv2.cvtColor(image_np,cv2.COLOR_BGR2GRAY) # BGR→灰度# 3. 自适应二值化maxval = 255 # 最大值# 自适应二值化# 返回值:自适应二值化之后的图像image_np_adaptive = cv2.adaptiveThreshold(image_np_gray, # 要处理的灰度化图像maxval, # 最大值cv2.ADAPTIVE_THRESH_MEAN_C, # 阈值的权重计算方式cv2.THRESH_BINARY_INV, # 阈值法还是反阈值法7, # 核大小10, # C值大小)# 4. 图片输出cv2.imshow('image_np_adaptive', image_np_adaptive)cv2.waitKey(0)