图像数据类型转换、图像拉伸、波段判断
走过的坑。。。就不提了。之前做深度学习的训练,在数据集都快完成之际,也就是下载,做标签,裁剪,筛选都弄完了。送到网络里面去训练,由于数据类型的不符,导致出来的预测结果,都是黑色的。查阅了许多资料,可能存在的情况有很多。(下图截图自别人的博客)
然后我就找到了数据类型的原因,准备尝试这个。因为确实,人家的数据能直接点开,咱的只能在ArcGIS里点开。对于一个搞png这种搞惯了的,确实看着很扎眼啊。。。然后右键查看了图像和mask的属性,确实是不一样。uint8类型的是能够打开的,所以我向师兄讨了个代码,能够进行数据类型转换。
import os
import numpy as np
from PIL import Image
from osgeo import gdal
import cv2
# 将图像类型转换为uint8类型(图像拉伸)
def get_uint8_image(image, vmin, vmax, pmin, pmax):
''' Scale image from float (or any) input array to uint8
Parameters
----------
image : 2D matrix # 传入的是二维矩阵,多维的需要多次调用该函数
vmin : float - minimum value # 通常设置为None。4个参数,要么设置前两个,要么设置后两个
vmax : float - maximum value # 通常设置为None。这两个参数描述的是image中的最大值和最小值的大小。
pmin : 下边界,取百分比,即,百分之pmin以下的舍弃,重点拉伸中间部分
pmax : 上边界,取百分比,即,百分之pmax以上的舍弃,重点拉伸中间部分
Returns
-------
2D matrix # 返回的是二维矩阵
'''
if vmin is None:
vmin = np.nanpercentile(image, pmin)
if vmax is None:
vmax = np.nanpercentile(image, pmax)
# redistribute into range [0,255]
# uint8Image = 1 + 254 * (image - vmin) / (vmax - vmin) # 图像拉伸 1-255
uint8Image = (255-0) * (image - vmin) / (vmax - vmin) # 图像拉伸 0-255
uint8Image[uint8Image < 1] = 0 # 二维矩阵中,所有小于1的部分,拉伸成0
uint8Image[uint8Image > 255] = 255 # 二维矩阵中,所有大于255的部分,拉伸成255
uint8Image[~np.isfinite(image)] = 0 # ~是取反,将二维矩阵中的所有无穷值,极大极小nan等,设置成0
# 转换过后的图像不含nan值,会变为0
return uint8Image.astype('uint8') # 返回类型为uint8的图像,此时可以在桌面双击点开图像查看内容
至于图像拉伸又是怎么一回事吧,就不得不提到刚刚的伤心事。还是先放图。
这个是先裁剪,再拉伸的patch:(Tif图好像不能直接拉上来,会显示格式错误,所以用了截图,还多截了点没用的边缘)
这个是先拉伸,再裁剪的patch:
我是在RGB的三通道上进行的裁剪。所以第二个patch显示的应该是对的,是真彩色。而第一个patch,由于是先裁剪的,所以在整个patch上,相较于原来的裁剪前的大图(没有打开,图像太大了,但是仍能看到是真彩色图像):
在最大值和最小值上,并不一致,导致虽然在数据上面,是和原图一模一样。但是不论用什么东西打开,他都是会给你做拉伸的,ArcGIS或者是普通编辑器都应该会,会把你本来图像的数据进行拉伸.也就是说,上下限就成了你patch 的最大最小值,而不是原图的最大最小值。这样显示出来,当然颜色就会有差异,不过数值没有任何差异。就是看着不太舒服,不是很容易识别。为了避免这个问题,我们应该主动去做归一化的图像拉伸,避免其他软件的自动拉伸。所以应该在最开始就做拉伸,再进行裁剪,能保证所有的patch都和原图一致。最终结果就是,数据类型一致了,网络预测也不是黑色了。(当然,又重新做了一遍裁剪,筛选等等步骤,颈椎留下了痛苦的泪水)
同时还有个好处,这个代码对于其他情况考虑的也很周到,比如图像中存在nan值,或者是超过了拉伸区间的值,都进行了处理。其实看这个公式,会觉得拉伸好像和归一化挺像的,思想都是从一个区间,变到另一个区间。至于他们是什么样的关系,是不是相通,没有进一步查阅资料,就不瞎说了。总而言之这个代码解决了一个二维矩阵的拉伸问题,同时也解决了数据类型转换的问题。
但是对于自己的数据,光是二维矩阵是不够用的,我们可是多通道的,所以整合了一下,写了一个自己适用的。
PS: 为了省事,单独把波段的判断拎出来,写了个小函数,毕竟后面还有很多整合的函数,需要去判断这个波段。
# 检测tif图的波段数
def band_number(img_path):
im_data, im_geotrans, im_proj = readTiff(img_path)
if len(im_data.shape) == 3: # 三个参数,波段数,H,W
im_bands, im_height, im_width = im_data.shape
# elif len(im_data.shape) == 2: # 两个参数,一般只有灰度图,band为1,不显示,只有H,W,相当于直接是二维矩阵了
# im_data = np.array([im_data])
else:
im_bands, (im_height, im_width) = 1, im_data.shape # 同上,没有band值的返回值,就手动给band赋值
return im_bands
# 将所有tif图,转换成uint8类型
def transform_allTiff_into_uint8(src_path,des_path):
dirs = os.listdir(src_path) # 逐个读取文件名
i = 0
over_No = 134 # 需要检测的图像的总数,根据需要自行设置
for file_name in dirs:
file_path = src_path + "/" + file_name # 源img路径
im_data, im_geotrans, im_proj = readTiff(file_path)
if(band_number(file_path) == 1): # 单通道(灰度图)
# print(im_data.shape)
im_height, im_width = im_data.shape
uint_data = np.zeros((im_height, im_width)).astype(np.uint8)
uint_data[::] = get_uint8_image(im_data, None, None, 0, 100) # 最后两个参数可调为10,99,对应arcGIS显示
writeTiff(uint_data, im_geotrans, im_proj, des_path + "/" + file_name)
else: # 多通道
im_bands, im_height, im_width = im_data.shape
uint_data = np.zeros((im_bands, im_height, im_width)).astype(np.uint8)
for j in range(im_bands): # 多波段用for循环,对应波段一一转换
uint_data[j,::] = get_uint8_image(im_data[j], None, None, 0, 100) # 最后两个参数可调为10,99,对应arcGIS显示
# uint_data[0, ::] = get_uint8_image(im_data[0], None, None, 0, 100) # R
# uint_data[1, ::] = get_uint8_image(im_data[1], None, None, 0, 100) # G
# uint_data[2, ::] = get_uint8_image(im_data[2], None, None, 0, 100) # B
writeTiff(uint_data, im_geotrans, im_proj, des_path+"/"+ file_name)
# print(uint_data.shape)
# print(file_path)
print(i)
i = i + 1
if i == over_No: # 终止条件
break
print("transform over")
图像的读、写参考1的代码。直接复制粘贴即可。