Python - 深度学习系列5-图像的一些基础处理

说明

使用深度模型大概一半时间都在处理图片?
本文介绍使用yolo时的一些图片处理方法。

将base64传递的图片进行处理后再进行识别和输出,yolo返回的结果改为:

  • 1 返回坐标和类别
  • 2 返回根据坐标切割的图片和类别
  • 3 返回在原图上进行划线和标注的图片

Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。可查看RFC2045~RFC2049,上面有MIME的详细规范。
Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。采用Base64编码具有不可读性,需要解码后才能阅读。
Base64由于以上优点被广泛应用于计算机的各个领域,然而由于输出内容中包括两个以上“符号类”字符(+, /, =),不同的应用场景又分别研制了Base64的各种“变种”。为统一和规范化Base64的输出,Base62x被视为无符号化的改进版本。

1 Base64处理

1.1 将一张图片读入后转为字符串

A1: 一个文件打开后返回的是什么? 例如, f = open(filename)的f是什么?

文件句柄。在文件I/O中,要从一个文件读取数据,应用程序首先要调用操作系统函数并传送文件名,并选一个到该文件的路径来打开文件。该函数取回一个顺序号,即文件句柄(file handle),该文件句柄对于打开的文件是唯一的识别依据。要从文件中读取一块数据,应用程序需要调用函数ReadFile,并将文件句柄在内存中的地址和要拷贝的字节数传送给操作系统。当完成任务后,再通过调用系统函数来关闭该文件。

linux下文件句柄是有限制的,默认并不会太高,一般都是1024。linux指令ulimit -n可以查看文件句柄限制(可以参考)。因此当我们打开了太多的文件没有关闭时,系统会报错。Python提供了一种比较优雅的写法:

with open(filename,mode) as f:
	f的各种操作

这样一个文件处理完就自动关闭句柄了,特别是在做深度学习时,有时会打开数万张图片,最好用这种方式,避免不必要的麻烦。
A2: 打开文件的格式,编码或者不编码(rb:read bytes),参考,关于python的一些文件操作,还可以参考

如果文件的结果是给人看的,例如是一个日志,那么就编码打开;open(filename, ‘r’, encoding=‘utf-8’)
如果文件只是给机器看的,那么就不编码打开 open(filename,‘rb’)

为了展示/验证图片字符串的操作过程,这里选一张很小的图片进行操作(否则字符串太长)。
在这里插入图片描述
读入过程如下:

# 打开一个文件
f = open('1.jpg', 'rb')

In [2]: f
Out[2]: <_io.BufferedReader name='1.jpg'>

可以看到,这是一个IO的缓存,Python提供了一个包来处理io, 可以参考,因为这样打开的是”给机器读的“模式,所以我们直接用Image包来读入这个f(句柄),这个直接使用Image的open函数是一样的。

In [4]: Image.open(f)
Out[4]: <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=60x20 at 0x10C7C9748>

可以看到,读取是成功的,为了更直观一点:

# 读入句柄
img = Image.open(f)
img.show()

可以看到,Image读到了图片。
在这里插入图片描述
试着读了了读f的内容

In [6]: f_content = f.read()
In [7]: f_content
Out[7]: b''

结果为空, 猜测大约是这个io_buffer的机制问题。

with open('1.jpg', 'rb') as f:
    f_content = f.read()
In [17]: f_content
Out[17]: b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x02\x00\x00\x01\x00\x01\x00\x00\xff\xdb\x00C\x00\x08\x06\x06\x07\x06\x05\x08\x07\x07\x07\t\t\x08\n\x0c\x14\r\x0c\x0b\x0b\x0c\x19\x12\x13\x0f\x14\x1d\x1a\x1f\x1e\x1d\x1a\x1c\x1c $.\' ",#\x1c\x1c(7),01444\x1f\'9=82<.342\xff\xdb\x00C\x01\t\t\t\x0c\x0b\x0c\x18\r\r\x182!\x1c!22222222222222222222222222222222222222222222222222\xff\xc0\x00\x11\x08\x00\x14\x00<\x03\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00\x00\x01}\x01\x02\x03\x00\x04\x11\x05\x12!1A\x06\x13Qa\x07"q\x142\x81\x91\xa1\x08#B\xb1\xc1\x15R\xd1\xf0$3br\x82\t\n\x16\x17\x18\x19\x1a%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xc4\x00\x1f\x01\x00\x03\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4\x00\xb5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04\x04\x00\x01\x02w\x00\x01\x02\x03\x11\x04\x05!1\x06\x12AQ\x07aq\x13"2\x81\x08\x14B\x91\xa1\xb1\xc1\t#3R\xf0\x15br\xd1\n\x16$4\xe1%\xf1\x17\x18\x19\x1a&\'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\xf5\x9dR\xeeKEybK\x871\xaa\x97X\x94>Al\x0f\x94\x91\xc7\x07$\x11\x81\xeb\xdb&\xd3\xc4\xd7\x0f\xacOe5\xa0\x0c\x02\xcc\xbeT\x86P\xc8v\xaf\xcap0s\xd8\x8erG\x07\xae\xd5\xed\xe5\xb6\x9d\x0b\xeaW\x13*\xdb\x05Q#\xe0\xb6\x06~R0\x0eyo\xd7\xdb\x9eKM\xb6W\xf1>\xa5\xfd\x97td\xd3\xe5\x87\xfd!\x95\xfc\xc1\xbf<\x05\xc1\xc9\xc2\x9c\xf0O\\\x1e\xbbj\xd5\x87&\xd3Ve\xfdC]\xbe\xfbD\x87O\x81\xa7+\x19 o\xf2\xc7L\x80r\x18\x120z\x01\xeeFFmhz\xf8\xd5\xec\xad59a\xf2\x91\xe1\x93;2\xd8!\x80 \x81\xd3\x18\xeb\xe8GN\x95\x93\xaekVzd\xc6+\xab\xbf\xb1\xdc\xddE\xb40\xcb\xf9-\xbc\x93 \x00\x13\x80\xcax\xee\xdcg\x19`xh\xe92,q\xd8\x17\x92\xd9\x18\xc5\x03H\x0c[\x88\xe4>x\xceO\x1d\x07;\xb8\xe0d\xbar\xb5\xca\xd6\xd7/I\xe2I\xb4\xddV\xca94\xe7\xfe\xce\xd4\xee\x15 \x9b\xcc\xc3\xa3?\x03(G\x00\x9f\x9b\x19\xc8\x07\x90\x0f\xcbZ:\xed\xfd\x9d\x8e\x91qy\xa8\x05\x88\xda\xa1\x90\x07*w\xe3\xf8S<\x12z\x00{\x91\xc7J\xe2\xaf\xbcI\xa0O\xae\xda\xa7\xe1\x1b\xd6\xd3\xdd\x9dg\x8c;\xa8\xc9\x0eU\x95\x80\x00\x03\xf3\x1d\xb8\xfa\x80;\xd1N\xce[\x891tk\xdb\xc1{-\x85\xccP\xe9\x9a\x82\xc0\xb7+\x0b\xca\xb2o\x89\x81\xe7~3\x95`U\x97h\x1cd\x13\x9c\xd6\xcc\xb7\x9a\xac\x12\x10-K\x97\x01\x8f\xee\xc9\nq\x8c\x0c1\xe3\x8f\xd6\xb8\xeb\x0b\xb7\xf1\x07\x8b\xe6\xd5\xf4\xf2\x97V\xf6\xfajZ\x99Wvw\x99L\x80\xa6pr\xa3n{\x8c\x8c\xf55\xd5A}\x12E\xb2\\\xa3\xa9\xc3)\x91\xe3\xc1\xef\xc2\x0c\x7f/\xeat\x92\x02h\x9c\xc3\xaf\xbd\x9a\xff\x00\xc7\xb3\x82<\xa2IQ\x95\xdcp:u\xfeuVUH5\xd4\x83\xcbY\x10:(\x12\x0c\xe1H\xe9\xef\xd7\x8c\xfa\n(\xa4\x06\xca[C\xf6\x8b\x98\nf\'\x8d\tBI\x19\xcb\x0e=8\x03\xa7\xa0\xac[\xf4_"\x19\xd8or\xb13\x16\xfe,\xa9\x04\x13\xd7\xf8\x07\xe6h\xa2\x94w\tlhCh&\xbc\x9e\xdaYdx\xad\xdd\x1dU\xb0rH\xe7\xa1\xd0\xff\xd9'

嗯,这下对了,的确是二进制字符串。我们试着把这个二进制字符串写入一个新的图片文件。

with open('2.jpg', 'wb') as f:
    f.write(f_content)

好的,产生了一毛一样的图片。接下来我们看看Image是如何读入二进制字符串的。

In [19]: img = Image.open(f_content)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-19-20dfbbdbc1cc> in <module>
----> 1 img = Image.open(f_content)

~/env/yoloenv/lib/python3.6/site-packages/PIL/Image.py in open(fp, mode)
   2807 
   2808     if filename:
-> 2809         fp = builtins.open(filename, "rb")
   2810         exclusive_fp = True
   2811 

ValueError: embedded null byte

直接把二进制字符串当成一个句柄是不行的,要使用一些方法把这些二进制字符串变得像个句柄。

In [20]: from io import BytesIO
    ...: 
    ...: f_content_like_file = BytesIO(f_content)
    ...: img = Image.open(f_content_like_file)
In [21]: img
Out[21]: <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=60x20 at 0x10C7A5898>

补充二进制字符串和通用字符串的转换

有时候我们需要还原成通用字符串,例如提取摘要。

In [57]: a = 'astring'                                                                                                            

In [58]: a.encode('utf-8')                                                                                                        
Out[58]: b'astring'

In [59]: a1 = a.encode('utf-8')                                                                                                   

In [60]: a2 = a1.decode('utf-8')                                                                                                  

In [61]: a2                                                                                                                       
Out[61]: 'astring'


1.2 图片从A到B的传输和读取

A3.base64有什么作用?为什么要用base64?

我们知道在计算机中任何数据都是按ascii码存储的,而ascii码的128~255之间的值是不可见字符。
而在网络上交换数据时,比如说从A地传到B地,往往要经过多个路由设备,
由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的。
所以就先把数据先做一个Base64编码,统统变成可见字符,这样出错的可能性就大降低了。

简单来说就是为了传输过程的规范化和方便,所以需要用base64来进行编码和反编码。

from io import BytesIO
import base64

# 从A处读取了一个文件
with open('1.jpg', 'rb') as f:
    f_content_bytes = f.read()

# A打算发给B
f_content_str = base64.b64encode(f_content_bytes)

# B收到了字符串,重新转为二进制
f_content_bytes_received = base64.b64decode(f_content_str)

# B把这些二进制字符串变得像个文件
f_content_bytes_received_like_a_file = BytesIO(f_content_bytes_received)

# 现在是图片,读进来试试
img = Image.open(f_content_bytes_received_like_a_file)
img

<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=60x20 at 0x10C91D9B0>

如果要使用json在网上传递字符串的话,要先进行utf-8编码。如下,使用b64encode对图片的二进制字节进行了编码, 在传输时必须进行utf8编码。

# request 端
def get_base64(img_str_or_path, imode='str'):
    if imode.lower() == 'str':
        return img_str_or_path
    # 如果是地址
    else:
        # 打开文件,读取二进制文件后使用base64加密返回
        with open(img_str_or_path, 'rb') as f:
            f_content_bytes = f.read()
        return base64.b64encode(f_content_bytes)


img_str_list = []
for f in sample_files:
    cur_f = get_base64(fpath+f, imode='path')
    # 将二进制字符转为utf-8 否则无法发送
    img_str_list.append(str(cur_f,'utf8'))

data = {
     'img_list':img_str_list[:1]}
url = 'YourURL'
resp = req.post(url, json=data).text
resp1 = json.loads(resp)
print(resp1)
# 服务端(应该是使用utf-8对字符进行了解码,还原为b64encode的二进制字符串)
input_data = request.get_json()
img_list = [base64.b64decode(x) for x in input_data['img_list']]
pic = Image.open(io.BytesIO(img_list[0]))
>>> <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=344x384 at 0x7F90E25B3198>

另外关于Base64图片和服务器,有以下三点Tips:

  • 1 编码。二进制文件需要通过编码(utf8)才能通过json序列化传输,但是到了服务端解码后似乎会变成unicode。产生的副作用是base64_str.decode(‘utf8’)会出错。
  • 2 io.BytesIO。 对于服务端解码(unicode)的二进制流,BytesIO是可以正常处理的,这样图片/文件仍然可以正常打开。
  • 3 md5。 如果需要对文件生成摘要的话,不一定非得是通用字符串,二进制字符串同样可以。md5.update(bin_str)->md5.hexdigest()

2 图片读入(尺寸缩放、灰度转换)

每太细研究,但似乎python numpy元素的最小单元是字节(1 Byte)而不是(1 bit)。所以哪怕是邻接矩阵的连接关系,也不能用bit(所以一整个矩阵的处理最好不要超过1万 * 1万 >=100M,如果是10万 * 10万>=10G)

bytearray()方法是python默认方法,返回一个新字节数组。这个数组里的元素是可变的,并且每个元素的值范围: 0 <= x < 256。

2.1 使用PIL.Image缩放(Python的基本图像包)

# 假设收到了base64的字符串
def resize_byPIL(img_base64, tosize=(100,100)):
    # 将字符转为二进制
    img_base64_bytes= base64.b64decode(img_base64)
    # 读取为Image图片
    img =  Image.open(BytesIO(img_base64_bytes))
    # 缩放后返回
    img_reszie = img.resize(tosize, Image.ANTIALIAS)
    return img_reszie
# 调用
img_resize1 = resize_byPIL(f_content_str)

Python - 深度学习系列5-图像的一些基础处理_第1张图片

2.2 使用cv2缩放(OpenCV)

numpy.ndarray中的每个元素的dtype应该为numpy.uint8
OpenCV的基础数据类型为numpy.ndarray。

cv2读取文件和二进制流的方法不同(cv2真是满奇怪的一个包)。
如果是二进制流,那么先要进行np的读取(不能使用BytesIO),再由cv2.imdecode进行图片读取。

# 假设a2是使用base64.b64decode还原过来的二进制字符
# a4是通过np读进来的np对象
a4 = np.frombuffer(a2,np.uint8) 
# 使用灰度方式读入图片
cv2.imdecode(a4, cv2.IMREAD_GRAYSCALE)  

如果是文件,那么就使用cv2.imread进行读取。

import numpy as np 
import cv2
# 使用cv2缩放
def resize_bycv2(img_base64, tosize=(100,100)):
    # 将字符转为二进制
    img_base64_bytes = base64.b64decode(img_base64)
    # 二进制转为矩阵
    img_arr = np.asarray(bytearray(img_base64_bytes), dtype='uint8')
    # 矩阵转为图片
    img = cv2.imdecode(img_arr, cv2.IMREAD_COLOR)
    return cv2.resize(img, tosize)

img_resize2 = resize_bycv2(f_content_str)

import matplotlib.pyplot as plt 
plt.imshow(img_resize2)

Python - 深度学习系列5-图像的一些基础处理_第2张图片
Note: opencv读取进来的是bgr顺序呢的,而imshow需要的是rgb顺序。如果是由cv2直接读的图片,那么就要把轴换一下。(这次不用)

3 保持宽高比的图片缩放

图片要可以被模型训练和预测,就要保持固定的形状。而直接resize会使图片被拉伸变形,因此需要进行保持宽高比+填充的方法变换图片。

https://www.cnblogs.com/sdu20112013/p/11949748.html

https://www.jianshu.com/p/f9bf949dc54a

Letterbox

Numpy中的ascontiguousarray使得向量存储在连续的存储地址里参考

未完待续

1


1


你可能感兴趣的:(深度学习,python)