CV Entry-Level
- Recurrent Neural Networks
- 序列数据sequence data
- 语言模型languag model
- 循环神经网络recurrent neural networks
- 门控循环单元gated recurrent unit
- 长短期记忆网络long short-term memory
- OpenCV 图形图像操作
- 文档矫正
- Gamma变化
- 开运算
- 传统图像分割
- 分水岭算法
Recurrent Neural Networks
序列数据sequence data
前后数据通常具有关联性,
比如:语音识别、音乐生成、视频行为识别、机器翻译、DNA序列分析。
语言模型languag model
语言模型是自然语言处理(NLP)的重要技术。NLP中常把文本看作是离散时间序列,一段长度为T的文本的词依次为
w
1
w_1
w1、
w
2
w_2
w2、…、
w
t
w_t
wt,其中
w
t
w_t
wt是时间步(Time Step)t 的输出或标签。
统计语料库(corpus)中的词频,语言模型将会计算该序列的概率P(
w
1
,
w
2
,
.
.
.
,
w
t
w_1,w_2,...,w_t
w1,w2,...,wt)
缺点:时间步t的词需要考虑t-1步的词,计算量随t呈指数增长。
循环神经网络recurrent neural networks
RNN是针对序列数据而生的神经网络结构,核心在于循环使用网络层参数,避免时间步增大带来的参数激增。并且,引入隐藏状态(Hidden State)用于记录历史信息,有效的处理数据的前后关联性。
激活函数为tanh,值域为(-1,1)
RNN的隐藏状态可以捕捉截至当前时间步的序列的历史信息。
但是梯度会随时间t呈指数变化,易引发梯度变化或梯度爆炸。
门控循环单元gated recurrent unit
GRU
缓解RNN梯度消失带来的问题,引入门概念,来控制信息流动,使模型更好记住长远时期的信息,并缓解梯度消失。
重置门
R
t
R_t
Rt:哪些信息需要遗忘
更新门
Z
t
Z_t
Zt:哪些信息需要注意
激活函数为Sigmoid,值域为(0,1),0表示遗忘,1表示保留。
更新门一直保持为1的话,信息可有效传递到当前步。
长短期记忆网络long short-term memory
一种带门控机制的循环神经网络LSTM,
引入3个门和记忆细胞(特殊的隐藏状态,记忆历史信息)
遗忘门:哪些信息需要遗忘
输入门:哪些信息需要流入当前记忆细胞
输出门:哪些记忆信息流入隐藏状态
OpenCV 图形图像操作
环境安装:
python.exe -m pip install --upgrade pip
pip install jupyter
pip install opencv-contrib-python
-
读入图像
cv2.imread()
读取为B-G-R的通道顺序
使用cv2.cvtColor(src, cv2.COLOR_BGR2RGB)
即可转换为RGB顺序图像 -
通道分离
cv2.split(img) -
添加文字
cv2.putText()
import numpy as np
import cv2
# 创建一个黑色的背景图
img = np.zeros((512,512,3),np.uint8)
# 添加文字
font = cv2.FONT_HERSHEY_SIMPLEX # 自带的字体
cv2.putText(img, 'caiman', (20, 200), font, 3, (0,255,0), 5)
# 背景,‘文字内容’,左下角起点坐标,字体,比例(越大越大),颜色,线条粗细
# 创建窗口
winname = 'like'
cv2.namedWindow(winname)
# 显示图像
cv.imshow(winname, img)
# 等待键盘'ESC'命令退出窗口
cv2.waitKey(0)
cv2.destroyWindow(winname)
文档矫正
放射变换、透视变化
#encoding:utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
src = cv2.imread('paper.png')
# 获取图像大小
rows, cols = src.shape[:2]
# 将源图像高斯模糊(去噪)
img = cv2.GaussianBlur(src, (3, 3), 0)
# 灰度化(方便边缘检测)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 边缘检测
edges = cv2.Canny(gray, 50, 250, apertureSize = 3)
cv2.imwrite("canny.jpg", edges)
cv2.imshow("canny", edges)
# 通过霍夫变换得到A4纸边缘(点集合)
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 50, minLineLength = 90, maxLineGap = 10)
print(lines)
# 下面输出的点为四个顶点
for x1,y1,x2,y2 in lines[0]:
print(x1,y1)
print(x2,y2)
for x3,y3,x4,y4 in lines[1]:
print(x3,y3)
print(x4,y4)
# 绘制边缘
for x1,y1,x2,y2 in lines[0]:
cv2.lines(gray, (x1, y1), (x2, y2), (0, 0, 255), 1)
# 根据四个顶点设置图像透视变换矩阵
pos1 = np.float32([[x2,y2], [x4,y4], [x1,y1], [x3,y3]])
pos2 = np.float32([[0, 0], [188, 0], [0, 262], [188, 262]])
M = cv2.getPerspectiveTransform(pos1, pos2)
print(M)
# 图像透视变换
result = cv2.warpPerspective(src, M, (190, 272))
# 显示图像
cv2.imshow("original", src)
cv2.imshow("result",result)
cv2.imshow("gray",gray)
# 等待显示
cv2.waitKey(0)
cv2.distroyAllWindows()
Gamma变化
非线性操作
gamma值调整的是图像中的亮度部分,而不会改变过暗或过亮的部分:
当gamma>1时,校正会降低图像亮度,使亮部细节更明显;
当gamma<1时,校正会增加图像亮度,使暗部细节更明显。
import cv2
import numpy as np
img = cv2.imread('img1.png')
def adjust_gamma(image, gamma = 1.0)
invGamma = 1.0/gamma
table = []
for i in range(256):
table.append(((i / 255.0) ** invGamma) * 255)
table = np.array(table).astype("uint8")
print(table)
return cv2.LUT(image, table)
img_gamma = adjust_gamma(img, 0.8)
cv2.imshow("img", img)
cv2.imshow("img_gamma", img_gamma)
cv2.waitKey(0)
cv2.destoryAllWindows()
开运算
先腐蚀(erode)
操作,后膨胀(dilate)
操作
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('img.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 设置结构元素的形状和尺寸
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
#121:1行2列的第1个图像,122:1行2列的第2个图像
plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(openning), plt.title('opening')
plt.xticks([]), plt.yticks([])
plt.show()
对于消除噪音效果很好,
调整结构元素的大小和形状有不同效果。
这是5*5刷子的结果:
传统图像分割
图像分割是指将图像分成若干具有相似性质的区域的过程
- 自适应阈值与固定阈值对比
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('img.png', 0) # 默认为1,0作为灰度图读入
# 固定阈值
ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESHOLD_BINARY)
# 自适应阈值(原图,最大阈值、一般为255,小区域阈值的计算方法,阈值方式,小区域面积,最终阈值等于小区域计算出的阈值再减去此值)
th2 = cv2.adaptiveThreshold(
img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY,11, 4)
#全局阈值,均值自适应,高斯加权自适应对比
titles = ['Original', 'Global(v = 127)', 'Adaptive Mean(11,4)', 'Adaptive Mean(55,4)', 'Adaptive Gaussian(17,6)', 'Adaptive Gaussian(11,4)']
images = [img, th1, th2, th3, th4, th5]
for i in range(6):
plt.subplot(3, 2, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i], fontsize=8)
plt.xticks([]), plt.yticks([])
plt.show()
- 边缘检测算子
Canny边缘检测算法:
- 彩色图像转换为灰度图像
- 对图像进行高斯模糊(去噪)
- 计算图像梯度,根据梯度计算图像边缘幅值与角度
- 沿梯度方向进行非极大值抑制(边缘细化)
- 双阈值边缘连接处理
- 二值化图像输出结果
import cv2
import numpy as np
# 以灰度图单通道图读入(彩色图像转换为灰度图像)
img = cv2.imread('lyx.png', 0)
# Canny(源图像,下阈值,上阈值,kernel_size)
v1 = cv2.Canny(img, 80, 200, (3, 3))
v2 = cv2.Canny(img, 50, 100, (5, 5))
# np.vstack():在竖直方向上堆叠
# np.hstack():在水平方向上堆叠
ret = np.hstack((v1, v2)) #拼接这样可以对比来看参数不同的效果不同
cv.imshow('caiman', ret)
cv2.waitKey(0)
cv2.destroyAllWindows()
分水岭算法
- 加载原始图像
- 阈值分割,将图像分割为黑白两个部分
- 对图像进行开运算
- 对开运算结果再进行膨胀,获取大部分背景区域
- 通过距离变换DistanceTransform获取大部分前景区域
- 背景区域和前景区域相减abstrac,得到二者重合区域
- 连通区域处理
- 最后使用分水岭算法
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Step1.加载图像
img = cv2.imread('image/yezi.jpg')
# 转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Step2.固定阈值分割,将图像分为黑白部分
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# Step3.对图像进行‘开运算’(先腐蚀后膨胀)
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# Step4.对‘开运算’的结果进行膨胀,得到基本就是背景的区域
sure_bg = cv2.dilate(opening, kernel, iterations=3)
# Step5.通过distanceTransform获取基本就是前景的区域sure_fg
# DIST_L1 DIST_C 只能对应掩膜为3, DIST_L2 可以为3或者5
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
print(dist_transform.max())
ret, sure_fg = cv2.threshold(dist_transform, 0.1 * dist_transform.max(), 255,0)
# Step6.sure_bg和sure_fg相减,得到背景和前景的重合区域
# 此区域和轮廓区域的关系位未知
sure_fg = np.uint8(sure_fg)
unknow = cv2.subtract(sure_bg, sure_fg)
# 连通区域处理
ret, markers = cv2.connectedComponents(sure_fg, connectivity=8) # 对连通区域标号,序号为0~N-1
print("原始markers")
print(markers) # markers相当于对前景目标都做了标记
# OpenCV分水岭算法对物体做的标注都必须大于1,背景标号为0,因此需要加1
markers = markers + 1
# 去掉属于背景区域的部分(让其变为0,成为背景)
markers[unknow==255] = 0
# Step8.分水岭算法
markers = cv2.watershed(img, markers) # 分水岭算法后,所有轮廓的像素点被标注为 -1
print("分水岭的markers")
print(markers)
# 标注为-1的像素点标红,OpenCV中颜色通道顺序为BGR
img[markers == -1] = [0, 0, 255]
# *************************打印******************************
titles = ['thresh', 'sure_bg', 'dist_transform', 'sure_fg', 'unknow','dst']
sure_bg2 = cv2.cvtColor(sure_bg, cv2.COLOR_BGR2RGB)
dist_transform2 = cv2.cvtColor(dist_transform, cv2.COLOR_BGR2RGB)
unknow2 = cv2.cvtColor(unknow, cv2.COLOR_BGR2RGB)
img2 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
images = [cv2.cvtColor(thresh, cv2.COLOR_BGR2RGB),sure_bg2, dist_transform2, cv2.cvtColor(sure_fg, cv2.COLOR_BGR2RGB),
unknow2,img2]
for i in range(6):
plt.subplot(3, 2, i+1), plt.imshow(images[i],)
plt.title(titles[i], fontsize = 8)
plt.xticks([]), plt.yticks([])
plt.show()
145.3628
原始markers
[[0 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]
...
[0 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]]
分水岭的markers
[[-1 -1 -1 ... -1 -1 -1]
[-1 1 1 ... 1 1 -1]
[-1 1 1 ... 1 1 -1]
...
[-1 1 1 ... 1 1 -1]
[-1 1 1 ... 1 1 -1]
[-1 -1 -1 ... -1 -1 -1]]
处理结果放大:
可以实现像素级别的分割,传统方法的精度要比深度学习方法更高