pytorch、jupyter初识

前言

  如何逐渐掌握深度学习呢,是一个难题,网上参差不齐没有一个非常好非常全面的教程,只能一点一点自己累计,寻找例程来模仿主要理解他是怎么一步步完成任务的、思考一个普遍性的解决问题的过程,如何搭建深度学习的神经网络框架,现在暂时选择pytorch
  然后,目前推荐搭建框架过程使用jupyter notebook,所以也要学习如何使用它,那么我想在每一阶段都解决和思考一些问题记录下来,来一步步稳扎稳打的学习深度学习

个人思路

  其实,深度学习说难不难,说简单肯定不简单,你真的可以甚至1天快速入门、上手,只要你懂一些python语法,稍微看看比较通俗的神经网络的介绍文章,学一种搭建框架的方式就能让你用上你设计的网络结构,大批量的规则化的图像信息甚至也可以从网上获取,然后非常方便的使用、训练、测试。
  但是你想真正用神经网络来解决问题你自己的实际问题,那么这些将远远不够,至少我是这样认为的,所以我寻找了很多比较有顺序的深度学习教程(pytorch的),都需要一篇篇的仔细阅读,深化概念,让之后的进阶操作能更快理解。


学习阶段————初次接触

思考:

  • 如何将一大批尺寸不一、随机的图像统一标准化的作为深度学习的输入、以及同时集合这些输入的label–对应的解,即真实值(来和输出层的计算值比较损失)?
  • 神经网络的框架如何根据自己的要求、向适合解决问题的网络结构改进?
  • 学习如何使用GPU来训练模型?
  • jupyter notebook 中如何正常运行pytorch网络?

一、在Morvan莫烦的pytorch教程中,再次理解神经网络

1.1 神经网络是什么

  • 之前虽然理解了一种说法,好像没提出来,这里讲一讲,但不讲太多,没啥意义
  • 首先,人们希望模仿人类的大脑,即生物神经网络,人类如何形成条件反射?如何学习?如何记忆?大脑内神经元如何工作?神经元之间有什么样的联系?
  • 人工神经网络相比,相似,又不相似,所有神经元的连接在同一时间都是固定的,不会凭空产生新联结,它是一种逐次接近正确答案的训练手段,其中“误差反向传递”起到关键作用

1.2 神经网络的简单结构概念

  • Gradient Descent:梯度下降    Cost Function:损失函数
  • 输入层、隐含层(第1层。。。第n层)、输出层就对应着特征–>代表特征1–>…–>代表特征n–>输出,这些代表特征是越来越抽象的,人越来越无法理解,但计算机就对他比较敏感了
  • 迁移学习:即在当你现有的一般神经网络足够处理比如图片内容种类识别、字符识别的任务后。如果我们在输出层前再加入几层神经网络来训练,就能来完成进阶的任务,比如不仅识别数字,还能知道数字的书写字体。。等等

2.1 选择Pytorch

  • 好用、使用广泛、新颖、直观、动态建立框架
  • torchvision里已经有一些它搭建好的网络,比如resnet(可以可以,我知道例程是这样用的了),然后你用这个已有的网络来训练自己的任务

jupyter notebook工作目录更改为自定义,即在属性栏启动目录将userprofile/..双引号内的内容改为自己希望前往的目录地址,重启即可

2.2 什么是Variable

  •   它在神经网络的训练中是一个关键,为什么这么说?思考一下,神经网络的反向传播算法常用梯度下降法,那其中如何求梯度呢?
      大家都知道就是求偏导数,导数就是梯度,torch里求导数的小帮手就是variable,将tensor张量放进variable中,开启自动求梯度的配置

    1
    variable = Variable(tensor,requires_grad=True) #requires_grad = True

    那么,你以后含variable的运算就都和variable本身建立了某种联系,可以快速得到以后的结果相对variable的梯度

    1
    2
    v_out = torch.mean(variable*variable)  #例如求个平均数的运算
    v_out.backward() #求梯度
  • variable变量不同于tensor,但是variable.data可以得到tensor,如果想进行tensor和numpy_array的转换,那么就要xx.data先得到tensor

  • variable的运算和tensor相似,但不完全相同

2.3 激励函数(AF–activation function)?

  •   激励函数广泛应用在各种层,各种用,可是为什么要激励函数呢?因为复杂问题中,输入输出往往都不是线性关系,不是线性,你就不能简单在他们中间乘个系数。
      进阶的操作就是,在输入做完简单线性处理(乘k)的结果代入一个非线性函数
  •   常用的非线性激活函数由relusigmoidtanh方程,甚至可以自己创造激励函数来求解,不过要注意,这些激励函数必须都是可以微分的,因为需要求梯度求导
  •   当隐藏层不多时,可以尝试任意一个激励函数;不过当隐藏层中含有非常多个layer,就有慎重选择,可能出现梯度爆炸梯度消失(我也不太懂,再学学)。其次,具体案例中,少量隐藏层可以有多种选择,卷积神经网络首选relu循环神经网络首选relutanh

如何应用activation function

  • 理解代码然后实验即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import torch
    import torch.nn.functional as F
    from torch.autograd import Variable
    import matplotlib.pyplot as plt

    # fake data

    x = torch.linspace(-5, 5, 200) # x data (tensor), shape=(100, 1)
    x = Variable(x)
    x_np = x.data.numpy() # numpy array for plotting

    # following are popular activation functions

    y_relu = torch.relu(x).data.numpy()
    y_sigmoid = torch.sigmoid(x).data.numpy()
    y_tanh = torch.tanh(x).data.numpy()
    y_softplus = F.softplus(x).data.numpy() # there's no softplus in torch

3.1 构建网络:回归

  • 利用Pytorch建立一个简单的神经网络

    1
    2
    3
    4
    5
    6
    7
    8
    class Net(torch.nn.Module):
    def __init__(self, xxx, xxx, xxx, ...):
    super(Net, self).__init__()
    # xxxxxxxx

    def forward(self, x):
    # xxxxxxxx
    return x
  • 定义一个实际的神经网络

    1
    net = Net(xxx=1, xxx=10, xxx=1, ...)
  • 定义优化器optimizer和损失函数loss function

    1
    2
    optimizer = torch.optim.SGD(net.parameters(), lr=0.2)  #net.parameters():包含了神经网络的所有参数  lr:学习率(步长)
    loss_func = torch.nn.MSELoss() # this is for regression mean squared loss
  • 那么在训练的时候

    • 首先定义网络的输出位置prediction,真正计算损失loss
    • 每次利用优化器清除掉网络中之前计算的梯度
    • 然后开始运行反向传播算法
    • 最后向正确方向迈出一步,用计算出的梯度再计算出新一轮的网络参数
    1
    2
    3
    4
    5
    prediction = net(x)     # input x and predict based on x
    loss = loss_func(prediction, y) # must be (1. nn output, 2. target)
    optimizer.zero_grad() # clear gradients for next train
    loss.backward() # backpropagation, compute gradients
    optimizer.step() # apply gradients

    当然本节是对于简单的回归问题,所以定义输出量的变量简单,损失计算也是简单的均方差算法

3.2 构建网络:分类

  • 与简单的回归神经网络不同的是,分类神经网络的输入量和输出量都不只有一个
  • 输入量可以是一组坐标,甚至可以是一张图片的一个三维张量,而输出则可以是一个输入张量对所有类别的概率判断
  • 分类的损失函数选择和回归是不一样的,教程中选择了loss_func = torch.nn.CrossEntropyLoss()
  • 当然,对于我要解决的字符识别问题而言,输入就要复杂得多,远不是一对坐标那么简单,输出的类别也会有很多,所以更透彻的学习将在第二阶段进行

python 之 opencv 阶段总结篇

前言

  • 没想到吧,这就总结了。没错我学习的三分钟热度真的太强了,这几天作业都没好好做,全在看这一套opencv的教程。那么就在大概第四天的时候把这一套教程给全部实验了一遍。
  • 不管怎么说,这一套教程呢,我作为小白学者觉得非常不错,讲的不深也有一些小挑战(略微简单,不过需要细品)。其实可以通过这些知识和技巧来做自己的延申,我还没有多尝试,唯一一次尝试使用对比字母轮廓形状的方法找到正确的字母,用他给的‘A’成功了,自己在网上找了个‘C’,预处理还做了闭运算轮廓只有一条,结果算法结果是像A我真的吐了。
  • 那么鉴于我一下子就学完了,而且每一章都像之前那样复制性的总结其实没多大意义,教程我电脑里也有,想复习了再去看看就可,所以这次来一个总结,目的就是略微的提到写我觉得的重点。

知识点回忆


1. 摄像头 or 视频
2. matplotlib的使用
3. 图像基本操作
4. 颜色空间转换
5. 阈值分割
6. 图形几何变换
7. 绘图功能
8. 图像混合


1. 摄像头 or 视频

  • 我看了下,之前讲完了前三个部分,调用电脑摄像头,可以用来截取一帧图像、可以将摄像头的连续若干帧图像保存成视频的形式
  • 可以打开一个视频,如果你利用cv2来创建一个滑动条cv2.createTrackbar()(滑动就会对应有回调函数)还能实现拖动视频播放进度,借助cv2.setTrackbarPos()cv2.getTrackbarPos()其实看函数名也知道是干什么的,这里不多讲了
  • cv2.VideoCapture()可以打开摄像头或者视频文件cap.get(propId)cap.set(propId,value)得到和修改摄像头的很多属性
  • cv2.imwrite()保存图片或者视频,保存视频的时候要注意提前设置好视频编码方式,我看有挺多的可能根据需要到时候就会了解吧、当然读取图片还是得cv2.imread()

  • 捕获视频中的一帧ret, frame = capture.read()


2. matplotlib的使用

  • 其实这整套教程也没咋用到,他和matlab里的plot用法有点像,都是subplot。不过要注意,他来显示图像时,plt.imshow()中后一个参数cmap的参数选择,灰度图就是cmap = 'gray',而且imshow之后必须要加plt.show(),要不然图片就不显示,可以看一下老师怎么写的
1
2
3
4
5
6
7
8
9
10
11
titles = ['Original', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, th1, th2, th3, th4, th5]

# 使用Matplotlib显示
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([]) # 隐藏坐标轴

plt.show()
  • matplotlib中图像显示RGB顺序,opencv是BGR,显示时需要通道倒置一下

3. 图像基本操作

  • 读取像素点的值直接img[100, 90],修改也是这样。
  • img.shape形状中包括行数、列数和通道数
  • 截取ROI,非常简单一看就懂,face = img[100:200, 115:188],这也只能是个矩形,不过得到轮廓或者利用掩码方式得到一块想要的区域也行
  • 彩色图的BGR三个通道是可以分开单独访问的,b, g, r = cv2.split(img);也可以合并组成图像img = cv2.merge((b, g, r))

4. 颜色空间转换

  • img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)图像形式转换,教程中用他来转换成HSV颜色模型,改成cv2.COLOR_BGR2HSV就行了
  • mask = cv2.inRange(hsv, lower_blue, upper_blue),其实inRange这个函数我感觉不太重要,因为后面有阈值分割,但可能这个是针对真彩图像,与阈值分割多是用于灰度图
  • 然后很重要的一个res = cv2.bitwise_and(frame, frame, mask=mask),这个位操作是图像只存在0和255也就是二值图像的关键操作,可以在以后的二值图像处理里起到作用,特别是应用在掩码图的得到

5. 阈值分割

  • 这一段是之后的基础,全都讲起来有点麻烦了,我就不多说。额,先说一句,这个应该是用在处理灰度图上的
  • 固定阈值ret, th = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY),第二个参数是设定的阈值,第三个是最大值,最后一个阈值的方式,种类主要5种
  • 自适应阈值th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 4),不同之处就在第三个参数,小区域阈值的计算方式;最后一个参数:最终阈值等于小区域计算出的阈值再减去此值(光看看不懂,想试试就自己试试)
  • Otsu阈值:看代码就知道怎么用了,原理自便

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 固定阈值法
    ret1, th1 = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY)

    # Otsu阈值法
    ret2, th2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # 先进行高斯滤波,再使用Otsu阈值法
    blur = cv2.GaussianBlur(img, (5, 5), 0)
    ret3, th3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
  • 光线不均匀的图片就用自适应阈值就完事了


6. 图形几何变换

  • 其实我觉得这一章是很重要的,因为图像处理应该会有很多时候待处理的目标是需要几何变换特别是立体上的仿射变换
  • cv2.resize()可以自己定义大小,也可以按比例缩放,用处会很多,我不知道和下面这个有什么关系,用处大不大,或者说在哪里使用
1
2
lower = cv2.pyrDown(img)  # 向下采样一级
higher = cv2.pyrUp(img) # 向上采样一级
  • cv2.flip(img, 1)第二个参数决定是镜像、垂直、镜像垂直翻转
  • 平移图片,需要用到矩阵,用仿射变换函数cv2.warpAffine()实现;旋转也是,而且用这个函数前,还要加个得到旋转对应的矩阵的函数cv2.getRotationMatrix2D(),你看这还只是2D的

$$
M = \left[
\begin{matrix}
1 & 0 & t_x \newline
0 & 1 & t_y
\end{matrix}
\right]
$$

仿射变换

  • 二维的,懂了吗,基本的图像变换就是二维坐标的变换:从一种二维坐标(x,y)到另一种二维坐标(u,v)的线性变换,你得先得到一个矩阵,那么就可以转换了,矩阵T(2×3)就称为仿射变换的变换矩阵

$$
\begin{matrix}
u=a_1x+b_1y+c_1 \newline
v=a_2x+b_2y+c_2
\end{matrix}
$$


$$
\left[
\begin{matrix}
u \newline
v
\end{matrix}
\right] = \left[
\begin{matrix}
a_1 & b_1 \newline
a_2 & b_2
\end{matrix}
\right] \left[
\begin{matrix}
x \newline
y
\end{matrix}
\right]+\left[
\begin{matrix}
c_1 \newline
c_2
\end{matrix}
\right]
$$


$$
R=\left[
\begin{matrix}
a_1 & b_1 \newline
a_2 & b_2
\end{matrix}
\right], t=\left[
\begin{matrix}
c_1 \newline
c_2
\end{matrix}
\right],T=\left[
\begin{matrix}
R & t
\end{matrix}
\right]
$$


  • 然后几行代码看懂,现根据三个点和对应的三个点得到变换矩阵,然后直接转整个图像
1
2
3
4
5
6
7
8
# 变换前的三个点
pts1 = np.float32([[50, 65], [150, 65], [210, 210]])
# 变换后的三个点
pts2 = np.float32([[50, 100], [150, 65], [100, 250]])

# 生成变换矩阵
M = cv2.getAffineTransform(pts1, pts2)
dst = cv2.warpAffine(img, M, (cols, rows))

透视变换

  • 三维的,就是立体上如何把一个目标看得顺眼一点,一个身份证你斜着拍的,那我想让他能像平着拍的一样舒服,那就用这个方法

$$
\begin{matrix}
X=a_1 x + b_1 y + c_1 \newline
Y=a_2 x + b_2 y + c_2 \newline
Z=a_3 x + b_3 y + c_3
\end{matrix}
$$


$$
\left[
\begin{matrix}
X \newline
Y \newline
Z
\end{matrix}
\right] = \left[
\begin{matrix}
a_1 & b_1 & c_1 \newline
a_2 & b_2 & c_2 \newline
a_3 & b_3 & c_3
\end{matrix}
\right] \left[
\begin{matrix}
x \newline
y \newline
1
\end{matrix}
\right]
$$

  • OpenCV中首先根据变换前后的四个点用cv2.getPerspectiveTransform()生成3×3的变换矩阵,然后再用cv2.warpPerspective()进行透视变换
1
2
3
4
5
6
7
8
9
# 原图中卡片的四个角点
pts1 = np.float32([[148, 80], [437, 114], [94, 247], [423, 288]])
# 变换后分别在左上、右上、左下、右下四个点
pts2 = np.float32([[0, 0], [320, 0], [0, 178], [320, 178]])

# 生成透视变换矩阵
M = cv2.getPerspectiveTransform(pts1, pts2)
# 进行透视变换,参数3是目标图像大小
dst = cv2.warpPerspective(img, M, (320, 178))
变换 矩阵 自由度 保持性质
平移 [I, t](2×3) 2 方向/长度/夹角/平行性/直线性
刚体 [R, t](2×3) 3 长度/夹角/平行性/直线性
相似 [sR, t](2×3) 4 夹角/平行性/直线性
仿射 [T](2×3) 6 平行性/直线性
透视 [T](3×3) 8 直线性

7. 绘图功能

  • 感觉画图功能不太用得到,不过也是用起来很方便,绘制形状的函数有一些共同的参数

    • color:绘制的颜色,是彩色图就估计是BGR,就输一个三维元组ok;灰度图就一个255以内的数
    • thickness:线宽,默认为1;对于矩形/圆之类的封闭形状而言,传入-1表示填充形状
    • 建议画什么线型都加一个lineType=cv2.LINE_AA比较好看
  • img = np.zeros((512, 512, 3), np.uint8)可以直接用这样一句话来创建一个纯色图

  • cv2.line(img, (0, 0), (512, 512), (255, 0, 0), 5)起点终点
  • cv2.rectangle(img, (384, 0), (510, 128), (0, 255, 0), 3)左上右下
  • cv2.circle(img, (447, 63), 63, (0, 0, 255), -1)圆心半径
  • cv2.ellipse(img, (256, 256), (100, 50), 0, 0, 180, (255, 0, 0), -1)椭圆中心x/y轴长度旋转角度起始角度结束角度,具体自己试试
1
2
3
4
5
# 使用cv2.polylines()画多条直线
line1 = np.array([[100, 20], [300, 20]], np.int32).reshape((-1, 1, 2))
line2 = np.array([[100, 60], [300, 60]], np.int32).reshape((-1, 1, 2))
line3 = np.array([[100, 100], [300, 100]], np.int32).reshape((-1, 1, 2))
cv2.polylines(img, [line1, line2, line3], True, (0, 255, 255))
  • 添加文字cv2.putText(img, 'ex2tron', (10, 500), font,4, (255, 255, 255), 2, lineType=cv2.LINE_AA)

8. 图像混合

👴乏了,找时间再写吧

python 之 opencv-摄像头采集和视频播放

打开摄像头


要使用摄像头,需要使用cv2.VideoCapture(0)创建VideoCapture对象,参数0指的是摄像头的编号,如果你电脑上有两个摄像头的话,访问第2个摄像头就可以传入1,依此类推。

capture.read

函数返回的第1个参数ret(return value缩写)是一个布尔值,表示当前这一帧是否获取正确

cv2.cvtColor

用来转换颜色,这里将彩色图转成灰度图

cap.get(propId)cap.set(propId,value)

通过cap.get(propId)可以获取摄像头的一些属性,比如捕获的分辨率,亮度和对比度等。propId是从0~18的数字,代表不同的属性,完整的属性列表可以参考:VideoCaptureProperties。也可以使用cap.set(propId,value)来修改属性值

经验之谈:某些摄像头设定分辨率等参数时会无效,因为它有固定的分辨率大小支持,一般可在摄像头的资料页中找到。

播放本地视频


跟打开摄像头一样,如果把摄像头的编号换成视频的路径就可以播放本地视频了。回想一下cv2.waitKey(),它的参数表示暂停时间,所以这个值越大,视频播放速度越慢,反之,播放速度越快,通常设置为25或30。

1
2
3
4
5
6
7
8
9
10
# 播放本地视频
capture = cv2.VideoCapture('demo_video.mp4')

while(capture.isOpened()):
ret, frame = capture.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

cv2.imshow('frame', gray)
if cv2.waitKey(30) == ord('q'):
break

录制视频


之前我们保存图片用的是cv2.imwrite(),要保存视频,我们需要创建一个VideoWriter的对象,需要给它传入四个参数:

  • 输出的文件名,如’output.avi’
  • 编码方式FourCC
  • 帧率FPS
  • 要保存的分辨率大小

FourCC是用来指定视频编码方式的四字节码,所有的编码可参考Video Codecs。如MJPG编码可以这样写: cv2.VideoWriter_fourcc(*'MJPG')cv2.VideoWriter_fourcc('M','J','P','G')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
capture = cv2.VideoCapture(0)

# 定义编码方式并创建VideoWriter对象
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
outfile = cv2.VideoWriter('output.avi', fourcc, 25., (640, 480))

while(capture.isOpened()):
ret, frame = capture.read()

if ret:
outfile.write(frame) # 写入文件
cv2.imshow('frame', frame)
if cv2.waitKey(1) == ord('q'):
break
else:
break

OpenCV VideoCapture.get()参数中文详解,不太准确供参考

练习


  • 实现一个可以拖动滑块播放视频的功能。(提示:需要用到 cv2.CAP_PROP_FRAME_COUNTcv2.CAP_PROP_POS_FRAMES两个属性)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import cv2


def changetime(x):
return capture.set(cv2.CAP_PROP_POS_FRAMES, x)

# 播放本地视频
capture = cv2.VideoCapture('demo_video.mp4')
#获取视频帧长度
frame_count = capture.get(cv2.CAP_PROP_FRAME_COUNT)
#创建一个窗口
cv2.namedWindow('video')
# 创建视频滑动条
cv2.createTrackbar('video', 'video', 0, int(frame_count), changetime)

height = capture.get(cv2.CAP_PROP_FRAME_HEIGHT)
width = capture.get(cv2.CAP_PROP_FRAME_WIDTH)
print(capture.set(cv2.CAP_PROP_FRAME_HEIGHT, height * 2))
print(capture.set(cv2.CAP_PROP_FRAME_WIDTH, width * 2))

while(capture.isOpened()):
ret, frame = capture.read()

if ret == True:
cv2.setTrackbarPos('video','video',int(capture.get(cv2.CAP_PROP_POS_FRAMES)))
cv2.imshow('video', frame)
if cv2.waitKey(30) == ord('q'):
break
else:
break
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×