图像分类之图像数据

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

  在现实生活中,我们人类分辨事物的习惯通常是从事物的轮廓、颜色以及表面细节入手。比方说下面这张图。
在这里插入图片描述
  有不少人把小浣熊和小熊猫认错了,因为很多人对小浣熊和小熊猫的特征认知并不全面,只是获取到这两种生物的一小部分特征,比如鼻子、胡子、大概的样貌,但并没有抓取重要的区分信息,例如耳朵、皮毛颜色、尾巴等信息。
  所以,大家在识别事物的时候,一定要注意事物的详细信息。
  那么,机器识别事物的时候是不是也是如此呢?
  接下来我们逐一解读机器眼里的图片是什么样子的。


一、图像即数据

  小时候看过一个非常魔性的电视剧《魔幻手机》,那时候就好奇为啥傻妞每次变身或者穿越时都会出现一堆数字。自从接触了C语言,我对数字化开始有了新的认知。
在这里插入图片描述


1.一维数据

  我们都知道底层硬件能够认知的信息是0和1,当然这是底层逻辑涉及到的知识点了。既然可以用二进制的形式表达信息,那么我们自然可以通过编程语言将其他进制的数据格式传到计算机硬件当中。比如数字9,传给计算机内存就是1001。
  显然,数字9只是没有维度的数据,它代表的信息还是很少的。
  我们现在来利用numpy画一个函数图。

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0, 11)
y = x * x
plt.plot(y)
plt.show()

  代码利用matplotlib画出的曲线
在这里插入图片描述
  我们会发现,无论是x,还是y,都是连续的数,x是从0到11之间(不包括11),y是x的平方。这里的x和y可以看成是一个有范围的线,x对应函数 y = x^2的定义域,y对应的是值域。既然x和y都是有范围的,我们可以把这个范围看成是长度,因此x和y都可以看成是一维的数据。
  正如高中几何知识所描述的,线是一维的,面是二维的,立方体是三维的。而人类肉眼所能感受到的物体最高维度是三,这三个维度分别是长、宽、高。

2.图像的维度

  那么,机器眼中的图片是不是也是二维的数据呢?我们先通过numpy将一张图片转化为数据看看。
在这里插入图片描述

from PIL import Image
import numpy as np

# 加载图像
img = Image.open('./img_cd_data/you.jpg')

# 将图像转化为NumPy数组
img_array = np.array(img)
print(type(img_array))

# 输出数组信息
print(img_array.shape)
print(img_array)

  运行结果如下:

'''
<class 'numpy.ndarray'>
(500, 889, 3)
[[[ 14  14  14]
  [ 14  14  14]
  [ 15  15  15]
  ...
  [ 49  52  64]
  [ 49  52  64]
  [ 49  52  64]]

 [[ 14  14  14]
  [ 14  14  14]
  [ 15  15  15]
  ...
  [ 49  52  64]
  [ 49  52  64]
  [ 49  52  64]]

 [[ 15  15  15]
  [ 15  15  15]
  [ 15  15  15]
  ...
  [ 49  52  64]
  [ 49  52  64]
  [ 49  52  64]]

 ...

 [[  0   0   4]
  [  0   0   4]
  [  0   0   4]
  ...
  [234 227 239]
  [234 227 239]
  [234 227 239]]

 [[  0   0   4]
  [  0   0   4]
  [  0   0   4]
  ...
  [234 227 239]
  [234 227 239]
  [234 227 239]]

 [[  0   0   4]
  [  0   0   4]
  [  0   0   4]
  ...
  [234 227 239]
  [234 227 239]
  [234 227 239]]]
  '''

  其中img_array.shape的值是图像的形状,结果是:(500, 889, 3)。另外,最下面的输出“print(img_array)”打印的是一个三维的矩阵数据。学过滤波算法或者图像处理的同学肯定知道为啥一个图片的数据是三维的。
  在numpy处理的图像数据的时候,图像的信息就是由一堆像素点构成的,此处引用的图片像素值是889 × 500,其中宽的像素为889,高的像素为500,总共有889 × 500个像素点信息。
  根据img_array.shape的返回结果可知,最后输出的矩阵,最外面的维度对应的数据大小是500,中间的维度对应的数据大小是889,最里面的维度对应的数据大小就是3。这里的3指的就是RGB(RED,GREEN,BLUE)三原色,我们会发现输出的矩阵第一个中括号内部数据是:[ 14 14 14],最后一个是:[234 227 239],这两处分别对应的是对应图片的左上角和右下角,而左下角接近黑色,右下角接近白色。
  这里需要引述一个前提,numpy将图片转化为数据的时候,是考虑了图片的宽、高还有颜色的,也就是图片是三维的,这与我们常规认知当中的二维平面似乎有所冲突。我们可以联系现实生活中的某个例子,比如订酒店的时候我们关注酒店的很多方面,像隔音、卫生、气味、空间大小、空间布局、床的舒适度、有无窗户等等,这些都是我们作为确定酒店的依据,这些方面就可以看成是酒店的不同维度。同样的,我们的图像也是如此,有长和宽(图像处理领域当中我们应该把长叫作宽,把宽叫作高),还有颜色。
  接下来,我们尝试修改一下原图左上角和右下角的数值,看看有什么变化。

import numpy as np
from PIL import Image

# 加载图像
img = Image.open('./img_cd_data/you.jpg')

# 将图像转化为NumPy数组
img_arr = np.array(img)

img_arr[0][0][0] = 255        #第一个通道是红色
img_arr[0][0][1] = 255        #第二个通道是绿色
img_arr[0][0][2] = 255      #第三个通道是蓝色

img_arr[499][888][0] = 0       #第一个通道是红色
img_arr[499][888][1] = 0        #第二个通道是绿色
img_arr[499][888][2] = 0      #第三个通道是蓝色

img_arr = img_arr.astype(np.uint8)

print(type(img_arr))

# 将numpy数组转换为PIL图像
img = Image.fromarray(img_arr)

img.show()

  左上角已被修改
在这里插入图片描述
  右下角已被修改
在这里插入图片描述
  当我们把图片当中某一个像素点的三个信息(RGB)都改成255的时候,这个像素点就是白色的,当我们将三个信息改成0的时候,这个像素点就是黑色的。
  RGB是红绿蓝三种颜色对应的三个通道,数值范围都是0到255,正好是8位。0代表颜色不存在,255代表当前颜色最纯。数值越接近255,当前颜色比重越大;数值越接近0,当前颜色比重越小。如果你还记得中学时期接触光学时,老师提到过三原色的概念,当我们把红绿蓝组合在一起的时候,会变成白色。
  正如代码中,最后一个像素点的第一、二、三个通道的数值都是255,也就是说我们在这个像素点将红绿蓝都设置成最纯,那么结果自然就是白色了。相反,数值都是0的话,说明颜色消失了,可以理解成颜色被吸收了,也就是黑色。
  我们也可以通过numpy创建图片来达到我们想要的颜色。

import numpy as np
from PIL import Image

img_arr = np.zeros((100,100,3))
for i in range(100):
    for j in range(100):
        img_arr[i][j][0] = 255        #第一个通道是红色
        img_arr[i][j][1] = 255        #第二个通道是绿色
        img_arr[i][j][2] = 0      #第三个通道是蓝色

img_arr = img_arr.astype(np.uint8)

print(type(img_arr))

# 将numpy数组转换为PIL图像
img = Image.fromarray(img_arr)

img.show()

  生成了宽高比是100:100的图片,有美术功底的同学应该知道红绿结合就是黄色,结果正确。
在这里插入图片描述
  综上所述,我们知道在机器的眼里,图片是由三个维度的,分别是宽、高、RGB。机器存储图片中各个颜色分布的位置,逐渐掌握图片中事物的形状规律,这和我们记住现实生活中某个事物的轮廓、颜色分布是异曲同工的。


二、对图像数据的处理

  对数据的处理简单来说就是让数据适合当前任务,并且去除原始数据里面不达标的部分。
  我们可以尝试回忆一下自己第一次学习数学或者一门外语的时候,是什么情形。我们首先是接触到了书本中的数字、符号或者字母,然后通过不断地朗读、背诵、书写、测验,才让我们有了对数学或者外语的认知。对于机器来说,也是如此,它对这个世界一无所知,我们只能把很多数据往机器的“脑子里”塞,迫使它学会。
  问题来了,如果我们儿时刚开始接触的基础知识当中有错误信息怎么办,又或者是明明是学习0到9的数字,突然冒出一个10,这个时候初识数字的我们是不是就会学错或者脑子凌乱了。
  对于机器视觉识别事物来说,如果我们一开始没有把错误数据剔除掉,或者没有把不符合当前机器学习事物范围的其他数据抹掉,那最终机器识别的精度就会受到很大影响。接下来我们看看图像数据处理可以考虑的几个方面。

1.数据格式(存储数据的方式)

  每个人接触一种新鲜事物的时候,接受程度是不一样的,有的人天生对数字敏感,一学就会,有的人对数字无感但对形象化地描述很在行。所以幼儿园到小学的数学课本上会出现很多有趣的图案,以帮助小朋友们学会数字。
  对于机器来说,识别一个新事物时,这些事物的图像数据也要适合机器,而且要让机器学得更快。因此,这一节引入张量(tensor)这个概念。

(1)向量

  学过线性代数的同学,对标量、向量、矩阵这些概念肯定不陌生了。比如一个学生在期中考试的时候考了80分,这里的80是一个数值,也就可以称之为标量;再比如这个同学在期末考试拿到了90分,我们把80到90中间连起一根线,这根线此时就是有向线段,方向是80到90,这个有向线段有可以看成是一个向量。
在这里插入图片描述

(2)矩阵

  既然提到了线段,那上图这个向量在空间里面就可以描述是一维的数据。现在我们数值变化的方向再多一个,如下图所示。
在这里插入图片描述
  此时得到的形状和我们熟悉的x-y坐标系很像了。在空间里,线是有方向的,面也是有方向的,两根交叉的线我们就可以确定一个平面。线上的数据我们可以看成是一组向量,是一维的数据;面上的数据是二维的,我们就可以用矩阵来表示(矩阵对应的坐标系,其原点不是固定的,视具体情况而定)。

(3)Numpy和Tensor

  依此类推,更高维度的数据是否也可以理解为空间的某些部分呢?答案是肯定的,在我们的认知里,三维的就是立体的。那么空间里的四维是什么呢?这个问题就已经超越了人类的大脑局限了。但在数学的范畴里,空间的维度有很多种,这是生活在三维空间里的地球人没法感受到的。
在这里插入图片描述

  但是高维度数据确实是存在的,正如前面的例子,一个酒店的特点有很多方面的,每个方面就有一堆数据。
  在编程语言中,标量(单个数据),我们可以用一个变量存储,向量我们可以用一维数组表示,矩阵我们可以用二维数组表示······等等,不知道大家还是否记得对数组的运算只是在CPU内存中顺序执行的,面对大量的数据,顺序执行的效率太低了,我们希望运算数据是可以并行执行的,也就是同时计算多个数据。
  因此,我们有必要认识两种支持并行运算的数据类型——numpy的ndarray和tensor。numpy里的ndarray和tensor都可以理解为高维数组,但tensor支持GPU加速数值计算以及自动微分,但是numpy提供的数据对象ndarray仅支持CPU计算,且不能自动求导求梯度。
numpy与tensor的区别
  上一段提到了两点,第一点是GPU和CPU,GPU与CPU在运算方面的区别主要是前者具有高并发的运算能力(图形处理能力强),后者浮点运算能力强但并行运算能力很有限。比如我们现在需要让计算机同时处理64张32323大小的图片(像素宽高比是32:32的彩色图),每张图片的数据量就有3072个,那么计算机就要在同一时刻处理64*3072个数据了,这个数据量是很庞大的了,此时GPU的优势就占上风了。关于第二点,自动微分,我会在后续的博客中予以讲解。
  接下来,我们通过一些代码的案例感受一下numpy和tensor。
  首先是numpy

#Numpy
import numpy as np
 
a = np.arange(6).reshape(2,3)
#np.arange(n),数字序列0,1,2,3,4,5。
#reshape()改变形状,此处是改成一个二维数据,第一维数值2,第二维数值3。
b = np.arange(6)
print ('a原始数组是:')
print (a)
print ('迭代输出元素:')
for x in np.nditer(a):
    print (x, end=", " )
    
print('\n')
print ('b原始数组是:')
print (b)
print ('迭代输出元素:')
for x in np.nditer(b):
    print (x, end=", " )
        
print('a和b的数据类型分别是: ')
print(type(a),type(b))

输出显示

'''
a原始数组是:
[[0 1 2]
 [3 4 5]]
迭代输出元素:
0, 1, 2, 3, 4, 5, 

b原始数组是:
[0 1 2 3 4 5]
迭代输出元素:
0, 1, 2, 3, 4, 5, 
<class 'numpy.ndarray'> <class 'numpy.ndarray'>
'''

  再看看tensor

#Tensor
import torch

x = torch.arange(12).reshape(3,4)
y = torch.Tensor([0,1,2,3,4,5,6,7,8,9,10,11])
print('x: ',x)
print('y: ',y)
print('type(x): ',x.size() )  # 查看tensor的维度是什么样,查看tensor的shape
print('x.dtype: ',x.dtype)  # 这个才是查看tensor中数据的具体类型是什么
print('type(y): ',y.size() )  # 查看tensor的维度是什么样,查看tensor的shape
print('y.dtype: ',y.dtype)  # 这个才是查看tensor中数据的具体类型是什么

  输出显示

'''
x:  tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])
y:  tensor([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11.])
type(x):  torch.Size([3, 4])
x.dtype:  torch.int64
type(y):  torch.Size([12])
y.dtype:  torch.float32
'''

numpy与tensor的转换
  其实Tensor与Numpy很相似,二者可以共享内存,且之间的转换非常方便和高效。
  numpy转为tensor

#numpy转为tensor
import numpy as np
import torch

x = np.ones(5)
print(type(x))

x = torch.tensor(x)
'''也可以用x = torch.from_numpy(x)
'''
print(type(x))

  输出显示

'''
<class 'numpy.ndarray'>
<class 'torch.Tensor'>
'''

  tensor转为numpy

#tensor转为numpy
import torch
x = torch.ones(5)
print('x: ',x)
print(type(x))

y= x.detach().numpy() 
print('y: ',y)
print(type(y))

  输出显示

'''
x:  tensor([1., 1., 1., 1., 1.])
<class 'torch.Tensor'>
y:  [1. 1. 1. 1. 1.]
<class 'numpy.ndarray'>
'''

  回到本节的主题——数据格式,作为人工智能研究范围内的一支,图像分类任务的实验效果如果要达到更高,势必要运用深度学习的范畴,而深度学习框架下的数据类型基本采用张量(tensor)。因此在图像分类任务一开始,我们就需要把数据格式转为张量类型。

2.数据的大小

  现实生活中,我们接触到的图像像素信息一般都是成百上千的,比如240p、480p、720p、1080p等分辨率的图片。但是不同的人工智能算法模型所能承受的数据大小是不一样,简单的算法能接受的输入数据偏小,复杂的算法则能接受尺寸较大的输入数据。
  另外,输入的图像数据中可能存在尺寸不统一的情况,因此我们需要把所有图片的大小统一化。

3.数据分批处理

  对数据进行分组,然后一组一组地运算,这种方式我们称之为分批处理。这么做的主要目的在于加快运算速度,包括数据输入、神经网络算法运算、优化算法的参数这几点的执行速度。关于数据分批处理在网络模型和优化算法中的作用,我将在后续的博客中提到。


总结

  没接触过深度学习,甚至没接触过机器学习的同学可能有些疑惑了,刚刚是线性代数,现在怎么还有高等数学了?为了让没有基础的同学能够以一个更舒适的方式慢慢认识图像识别(图像分类)的领域,我想通过贴近生活的例子带大家一起探索神奇的人工智能领域。
  本文提到的Numpy,如果有同学还没接触过,可以参考后续的Numpy相关博客。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cherry Yuan

再多的奖励也换不回失去的头发

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值