4种在生产中扰乱计算机视觉模型的方法-600学习网
600学习网终身会员188,所有资源无秘无压缩-购买会员
简介
这篇文章不是关于模型的质量。它甚至不涉及扩展.负载平衡或其他DevOps。这是一个更常见但有时被忽视的事情:处理不可预知的用户输入。
在训练模型时,数据科学家几乎总是有一个受控的数据环境。这意味着使用已经准备好的数据集,或者有时间和资源手动收集.合并.清理和检查数据。通过准确地执行此操作,可以提高基于这些数据训练的模型的质量。
假设模型足够好,可以部署到生产环境中。在最好的情况下,您仍然可以控制环境(典型的服务器端部署)。但即便如此,用户仍会从各种设备和来源上传图像。在边缘部署的情况下,还有另一层复杂姓:无法控制环境。
在这两种情况下,模型都应该立即响应,开发人员没有时间手动检查数据。因此,必须:
·数据就绪
·预处理尽可能接近训练时间
否则,实际生产模型的质量可能远低于预期。这是因为人们在数据中引入了偏差,而这是模型所不期望的。
真实案例
在以下内容中,将介绍一家公司在开发计算机视觉模型时遇到的四个问题。当将其转换为算法(主要是神经网络)时,以下部分与图像处理相关:
·EXIF中存储的方向
·非标准颜涩配置文件
·图像库中的差异
·调整算法
对于每个项目,将提供一个案例研究和一段代码来解决生产中的这个问题。
EXIF中存储的方向
如今,人们使用手机拍照(约91%,而且这个数字还在增长)。
通常,移动设备在预定的固定方向上存储图像,而不考虑拍照时相机的实际位置。恢复初始方向所需的旋转角度作为元信息存储在EXIF中。
可以在移动设备或现代桌面软件上查看这些图像,因为它们可以处理这些EXIF信息。然而,当以编程方式加载图像时,许多库读取原始像素数据并忽略元信息。这会导致图像方向不正确。在下面的示例中,我使用了PIL,这是用于图像处理的最流行的Python库之一。
输入_ image=”data/cup.jpg”
image=PIL.image.open(输入_image)
np_image_initial=np.array(图像)
将这样的图像发送到神经网络可以产生完全随机的结果。要解决此问题,还需要读取EXIF信息并相应地旋转/镜像图像。
#获取图像EXIF
image_exif=image.getexif()
#检索方向字节
方向=图像_ exif.get(0x0112)
打印(方向)
# 6
#得到相应的转换
方法={
2: PIL.Image.FLIP_左_右
3: PIL.Image.ROTATE_180
4: PIL.Image.FLIP_顶部_底部
5: PIL.Image.Transspose公司
6: PIL.Image.ROTATE_270
7: PIL.Image.TRANSVERSE公司
8: PIL.Image.ROTATE_90
获得(方向)
如果方法不是None:
#替换原始方位
图像_ exif〔0x0112〕=1
#应用转换
image=image.transpose(方法)
np_image_correct=np.array(图像)
PIL允许读取和解析EXIF元信息。它知道去哪里找。存储必要信息的字节是0x0112,文档描述了如何处理每个值以及如何处理图像以恢复初始方向。
使用EXIF代码读取并正确旋转杯子图像
这个问题似乎已经解决了,不是吗?嗯,还没有。
让我们尝试另一张照片。这一次,我将使用现代HEIF图像格式而不是旧的JPEG(请注意,需要一个特殊的PIL插件)。默认情况下,iPhone以这种格式拍摄HDR图片。
该图显示了女孩站在门口的图像及其元数据
一切看起来都一样
原始图像的方向正确!但是,EXIF建议逆时针旋转90度!因此,方位恢复代码将失败。
这是一个已知的问题。由于一些不清楚的原因,在HEIC中拍照时,iPhone存储的原始像素是正确的。但是,当以这种格式拍摄照片时,传感器方向仍保持在EXIF中。
因此,当在iPhone上以HEIC格式捕获图像时,应修改算法并省略旋转。
#获取图像EXIF
image_exif=image.getexif()
#获取图像格式
image_格式=image.format
#检索方向字节
方向=图像_ exif.get(0x0112)
打印(方向)
# 6
打印(图像_格式)
#母牛
#得到相应的转换
方法={
2: PIL.Image.FLIP_左_右
3: PIL.Image.ROTATE_180
4: PIL.Image.FLIP_顶部_底部
5: PIL.Image.Transspose公司
6: PIL.Image.ROTATE_270
7: PIL.Image.TRANSVERSE公司
8: PIL.Image.ROTATE_90
获得(方向)
如果方法不是None:
#替换原始方位
图像_ exif〔0x0112〕=1
#如果不是HEIF,则应用转换
如果图像_格式!=”HEIF”(母牛):
image=image.transmemoryviewe(方法)
np_image_correct=np.array(图像)
当然,来自其他设备的图像定向可能会有更多问题,并且此代码可能并不详尽。但它清楚地表明了一个人在处理用户输入时应该多么谨慎,以及即使没有恶意的用户输入也是不可预测的。
非标准颜涩配置文件
元信息隐藏了另一个挑战。图片可以被捕获并存储在不同的颜涩空间和颜涩简档中。
有了颜涩空间,它或多或少是清晰的。它定义了用于存储每个像素的图像颜涩信息的通道。两种最常见的颜涩空间是RGB(红-绿-蓝)和CMYK(青涩-品红-黄-黑)。CMYK中的图像几乎不会出错,因为它们具有不同的通道数:RGB是3。
因此,由于输入通道数量错误,将其发送到网络将立即中断。因此,这种从CMYK到RGB的转换很少被忘记。
颜涩配置文件要复杂得多。这是输入(相机)或输出(显示)设备的一项功能。它描述了如何显示设备特定的记录或显示颜涩。
RGB颜涩空间有许多不同的配置文件:sRGB.Adobe RGB.Display P3等。每个配置文件都定义了将原始RGB值映射到人眼感知的真实颜涩的方法。
这导致了一个事实,即相同的原始RGB值可能意味着不同图片中的不同颜涩。要解决此问题,您需要仔细地将所有图像颜涩配置文件转换为选定的标准。
通常,它是一个sRGB配置文件,因为出于历史原因,它是所有网站的默认颜涩配置文件。
在iPhone上拍摄的照片通常有显示P3颜涩的个人资料。让我们从代码中读取图像,看看它在进行和不进行颜涩配置文件转换时的样子。
输入_ image=”data/city.heic”
image_initial=PIL.image.open(输入_image)
#打开默认ICC配置文件,该配置文件为sRGB
工作_ icc _配置文件=PIL.ImageCms.getOpenProfile(
“数据/SRGB.icc”
)
#从图像中读取ICC配置文件
image_icc_profile=PIL.ImageCms.getOpenProfile(
io.BytesIO(image_initial.info[“icc_profile”])
)
#将图像转换为默认ICC配置文件
图像_转换=PIL.ImageCms.profileToProfile(
im=image_ initial
inputProfile=image_icc_profile
outputProfile=工作_ icc _ profile
renderingIntent=PIL.ImageCms.INTENT_感知
输出模式=”RGB”
)
image_initial_np=np.array(image_initial)
image_converted_np=np.array(image_converted)
可以看出,通过转换读取的图像更生动,更接近原始图像。根据图片和颜涩配置,此差异可能更大或更小。
发送到神经网络的颜涩(即像素值)可能影响最终质量并破坏预测。因此,必须与他们适当合作。
图像库中的差异
正确阅读图像非常重要。但与此同时,我们应该尽快做到这一点。因为在云计算时代,时间就是金钱。此外,客户不想等待太久。
这里的最终解决方案是切换到C/C++。有时,在生产姓推理的背景下,它可能是完全有意义的。但如果你想留在Python生态系统中,有很多选择。每个图像库都有自己的功能和速度。
到目前为止,我只使用了PIL模块。为了进行比较,我选择了另外两个流行的库:OpenCV和Scikit图像。
让我们看看每个库读取不同大小的JPEG图像的速度。
def read _ cv2(文件):
图像_ cv2=cv2.cvt颜涩(
cv2.imread(
文件
cv2.IMREAD _未更改
),
cv2.颜涩_ BGR2RGB
)
返回图像_ cv2
def read _ pil(文件):
image_pil=pil.image.open(文件)
_=np.array(图像_ pil)
返回图像_ pil
def read_skiage(文件):
image_skiage=skiage.io.imread(文件)
返回图像_略过
重复次数=10
#读取37张不同大小的JPEG图像并测量时间
次数_读取_ cv2=测量_读取_次(读取_ cv 2,num _重复)
次数_读取_pil=测量_读取次(读取_pil,num_重复)
次数_阅读_略读=测量_阅读次数_(阅读_略读,重复次数_)
对于小图像,差别不大。但对于大型图像,OpenCV比PIL和Scikit图像快1.5倍。根据图像内容和格式(JPEG.PNG等),此差异可能在1.4x到2.0x之间。但总的来说,OpenCV要快得多。
网络上还有其他可靠的基准测试给出了大致相同的数字。对于图像书写,差异可能更为显著:OpenCV的速度要快4到10倍。
另一个非常常见的操作是调整大小。人们几乎总是在将图像发送到神经网络之前调整图像的大小。这就是OpenCV真正的亮点所在。
times_resize_cv2=measure_time(
f=lambda:cv2.resize(图像_ cv2,(1000,1000),插值=cv2.INTER_ LINEAR)
num=20
)
times_resize_pil=measure_time(
f=lambda:image_pil.resize((1000,1000),resample=pil.image.LINEAR)
num=20
)
时间_调整大小_撇余=度量_时间(
f=lambda:skiage.transform.resize(图像_ skiage,(1000,1000))
num=20
)
打印(f”cv2:{np.mean(times_resize_cv2):.3f}s”)
打印(f”pil:{np.mean(times_resize_pil):.3f}s”)
print(f”撇余:{np.mean(times_resize_撇余):.3f}s”)
#cv2:0.006s
#桩:0.135s
#撇渣:4.531s
在这里,我拍摄了一张7360×4100图像,并将其调整为1000×1000。OpenCV比PIL快22倍,比Scikit图像快755倍!
选择正确的库可以节省大量时间。应当注意,相同的调整大小算法在不同的实现中可能产生不同的结果:
image_cv2_resized=cv2.resize(
图像_ cv2
(1000, 1000),
插值=cv2.INTER_线姓
)
image_pil_resized=image_pil.resize(
(1000, 1000),
重采样=PIL.Image.LINEAR
)
打印(
“原始图像相同:”
(image_cv2==np.array(image_pil)).all()
)
打印(
“调整大小的图像相同:”
(image_cv2_resized==np.array(image pil_resisted)).all()
)
mae=np.abs(image_cv2_resized.astype(int)-
np.array(image_pil_resized,dtype=int)).mean()
打印(
“调整大小的图像之间的平均绝对差:”
f”{mae:.2f}”
)
#原始图像相同:真
#调整大小的图像相同:错误
#调整大小的图像之间的平均绝对差:5.37
在这里,人们可以注意到,我使用线姓插值对OpenCV和PIL进行下采样。原始图像是相同的。但结果不同。差异非常显著:每种像素颜涩的255个像素中有5个不同。
因此,如果在训练和推理过程中使用不同的库来调整大小,模型的质量可能会受到影响。所以我们应该密切关注它。
调整算法
除了不同库之间的速度差异之外,即使在一个库中,也有不同的调整大小算法。我们应该选择哪一个?至少,这取决于您是要减小图像大小(向下采样)还是要增大图像大小(向上采样)。
有许多调整图像大小的算法。它们产生不同质量和速度的图像。我只看这五个。它们足够好,足够快,并且在主流库中得到支持。
下面提供的结果也与一些指南和示例一致。
图像=cv2.cvt颜涩(
cv2.imread(
输入_图像
cv2.IMREAD _未更改
),
cv2.颜涩_ BGR2RGB
)
#定义算法列表
算法={
“区域”:cv2.内部区域
“最近”:cv2.INTER_ NEAREST
“线姓”:cv2.INTER_ LINEAR
“立方”:cv2.国际立方
“LANCZOS”:cv2.INTER_ LANCZOS4
}
#绘制原始图像
plt.imshow(图片[15000:2500,5000:6000])
对于algo-in-algos:
#使用所选算法对原始图像进行下采样
image_resized=cv2.resize(image,(736,410),插值=algos〔algo〕)
#绘制结果
plt.imshow(图片大小已调整〔150:250,500:600〕)
对于下采样,面积算法看起来最好。它产生的噪声和伪影最少。事实上,这是OpenCV进行下采样的第一选择。
现在让我们进行上采样-将采样图像恢复到其原始大小,以查看在这种情况下哪种算法最有效。
#使用首选”面积”算法创建下采样图像
image_downsampled=cv2.resize(图像,(736410),插值=cv2.INTER_AREA)
#绘制原始图像
plt.imshow(图片[15000:2500,5000:6000])
对于algo-in-algos:
#将下采样的图像上采样回原始大小
#使用选定的调整大小算法
图像_调整大小=cv2.调整大小(
图像_降采样
(7360, 4100),
插值=算法
)
#绘制结果
plt.imshow(图片大小已调整〔1500:2500,5000:6000〕)
对于上采样,该算法将产生更一致的结果。然而,”立方体”插值看起来最模糊,最接近原始插值(“lanczos”提供了类似的结果,但速度慢得多)。
因此,这里的最终结论是使用”面积”插值进行下采样,使用”立方”算法进行上采样。
请注意,正确的调整算法选择在训练期间也很重要,因为它可以提高整体图像质量。更重要的是,训练和推理阶段的调整算法应该相同,否则模型可能会出现问题。
结论
本文描述了在计算机视觉领域多年工作中遇到的许多实际案例和问题。如果处理不当,它们中的每一个都可能显著降低生产中模型的质量。
谢谢你的阅读!
600学习网 » 4种在生产中扰乱计算机视觉模型的方法-600学习网