TowardsDataScience 博客中文翻译 2019(二百八十)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

图像伪造检测

原文:https://towardsdatascience.com/image-forgery-detection-2ee6f1a65442?source=collection_archive---------1-----------------------

利用 CNN 的力量来检测图像操纵

随着脸书和 Instagram 等社交网络服务的出现,过去十年中生成的图像数据量大幅增加。使用图像(和视频)处理软件,如 GNU GimpAdobe Photoshop 创建篡改过的图像和视频,是脸书等互联网公司的一大担忧。这些图像是假新闻的主要来源,并经常被恶意使用,如煽动暴民。在根据可疑图像采取行动之前,我们必须验证其真实性。IEEE 信息取证与安全技术委员会(IFS-TC)于 2013 年发起了一项检测和定位取证挑战,即首届图像取证挑战,以解决这一问题。他们提供了一个开放的数字图像数据集,包括在不同光照条件下拍摄的图像和使用以下算法创建的伪造图像:

  • 内容感知填充和修补匹配(用于复制/粘贴)
  • 内容感知修复(用于复制/粘贴和拼接)
  • 克隆图章(用于复制/粘贴)
  • 线缝雕刻(图像重定向)
  • 修复(受损部分的图像重建——复制/粘贴的特殊情况)
  • 阿尔法抠图(用于拼接)

挑战包括两个阶段。

  1. 第一阶段要求参与团队将图像分类为伪造的或原始的(从未被篡改)
  2. 第二阶段要求他们检测/定位伪造图像中的伪造区域

这篇文章将讲述一种解决第一阶段挑战的深度学习方法。从数据清洗,预处理,CNN 架构到训练和评估的一切都将被阐述。

为什么用 CNN?

在人工智能的深度学习时代之前,即在 2012 年的图像网络挑战赛之前,图像处理领域的研究人员过去常常设计手工制作的功能来解决一般图像处理问题,特别是图像分类问题。一个这样的例子是用于边缘检测的 Sobel 内核。之前使用的图像取证工具可分为 5 类,即

  1. 基于像素的技术,检测像素级引入的统计异常
  2. 基于格式的技术,利用特定有损压缩方案引入的统计相关性
  3. 基于相机的技术,利用相机镜头、传感器或片内后处理引入的伪像
  4. 基于物理的技术,明确地模拟和检测物理对象、光和相机之间的三维交互中的异常
  5. 基于几何的技术,对世界上的对象及其相对于相机的位置进行测量

礼貌:https://ieeexplore.ieee.org/abstract/document/4806202

几乎所有这些技术都利用了图像的基于内容的特征,即图像中存在的视觉信息。CNN 的灵感来自视觉皮层。从技术上讲,这些网络被设计成提取对分类有意义的特征,即最小化损失函数的特征。网络参数——通过梯度下降学习内核权重,以便从输入网络的图像中生成最具鉴别性的特征。这些特征然后被馈送到一个完全连接的层,该层执行分类的最终任务。

在看了一些伪造的图像后,很明显通过人类视觉皮层定位伪造区域是可能的。因此,CNN 是这项工作的完美深度学习模型。如果人类的视觉皮层能够探测到它,那么在一个专门为这项任务设计的网络中肯定会有更多的能量。

资料组

在对数据集进行概述之前,先要弄清楚所使用的术语

  • 假图像:使用两种最常见的操作,即复制/粘贴和图像拼接,对图像进行了处理/篡改。
  • 原始图像:除了根据比赛规则调整所有图像的尺寸以达到标准尺寸之外,没有被处理过的图像。
  • 图像拼接:拼接操作可以组合人的图像,给建筑物添加门,给停车场添加树和汽车等。拼接图像还可以包含复制/粘贴操作产生的部分。接收拼接部分的图像称为“主”图像。与宿主图像拼接在一起的部分被称为“外星人”。

第一阶段和第二阶段的整个数据集可以在这里找到。对于这个项目,我们将只使用列车组。它包含两个目录——一个包含假图像及其相应的遮罩,另一个包含原始图像。伪图像的遮罩是描述伪图像的拼接区域的黑白(非灰度)图像。蒙版中的黑色像素代表在源图像中执行操作以获得伪造图像的区域,具体来说,它代表拼接区域。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Example of a fake image and corresponding mask

该数据集由 1050 幅原始图像和 450 幅伪图像组成。彩色图像通常是 3 个通道的图像,每个通道用于红色、绿色和蓝色,然而有时可能存在用于黄色的第四通道。我们数据集中的图像是 1、3 和 4 通道图像的混合。在查看了几幅 1 通道图像(即灰度图像)后,很明显这些图像

  1. 数量非常少
  2. 是黑色或蓝色的溪流

挑战设置者故意添加这些图像,因为他们希望解决方案对这种噪声具有鲁棒性。尽管一些蓝色图像可以是晴朗天空的图像。因此,它们中的一些被包含在内,而另一些则作为噪声被丢弃。来到四通道图像——他们也没有任何有用的信息。它们只是用 0 值填充的像素网格。因此,清理后的原始数据集包含大约 1025 幅 RGB 图像。

伪图像是 3 和 4 通道图像的混合,然而,它们都没有噪声。相应的遮罩是 1、3 和 4 通道图像的混合。我们将使用的特征提取只需要来自掩模的一个通道的信息。因此,我们的假货图像语料库有 450 个假货。接下来,我们做了一个训练测试分割,保留 1475 个图像中的 20%用于最终测试。

训练集上的特征提取

当前状态的数据集不适合训练模型。它必须被转换成非常适合手头任务的状态,即检测由于伪造操作而引入的像素级异常。从这里的中汲取灵感,我们设计了以下方法来从给定的数据中创建相关的图像。

对于每一个假图像,我们都有一个相应的面具。我们使用该掩模沿着拼接区域边界对伪图像进行采样,以确保图像的伪造部分和非伪造部分至少有 25%的贡献。这些样本将具有只有在假图像中才会出现的有区别的边界。这些界限是我们设计的 CNN 要学习的。由于遮罩的所有 3 个通道包含相同的信息(不同像素的图像的虚假部分),我们只需要 1 个通道来提取样本。

为了使边界更加清晰,在使用高斯滤波器去噪后,使用 Otsu 的阈值(在 OpenCV 中实现)将灰度图像转换为二进制。在此操作之后,采样仅仅是移动 64×64 的窗口(步长为 8)通过假图像,并计数相应蒙版中的 0 值(黑色)像素,并在值位于某个区间的情况下进行采样。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Boundaries in a binary mask are much more distinct than in grayscale

经过采样,我们从假图像中得到了 175,119 个 64×64 的面片。为了生成 0 个标记的(原始的)补丁,我们从真实图像中采样了大致相同的数量。最后,我们有 350,728 个补丁,这些补丁被分成训练集和交叉验证集。

现在我们有了一个大的高质量输入图像数据集。是时候试验各种 CNN 架构了。

定制 CNN 架构

我们尝试的第一个架构受到了最初的研究论文中给出的架构的启发。他们的输入图像大小为 128×128×3,因此网络很大。由于我们只有一半的空间大小,我们的网络也更小。这是第一个尝试的建筑。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

First architecture

这里绿色层是卷积层,蓝色层是最大池。该网络在 150,000 个训练样本(用于测试目的)和 25,000 个验证样本上被训练。该网络有 8,536 个参数,与训练样本相比相对较少,因此避免了对更激进的退出的需要。0.2 的退出率适用于 20 个单位的扁平化产出。我们使用 Adam 优化器,默认值为学习率(0.001)和 beta_1、beta_2。大约 ___ 个时期后,结果如下

训练准确率:77.13%,训练损失:0.4678

验证准确率:75.68%,验证损失:0.5121

这些数字并不令人印象深刻,因为 2012 年 CNN 以巨大优势击败了专家长达一年的研究。然而,考虑到我们完全没有使用图像取证知识(像素统计和相关概念)来获得看不见的数据的 ___ 准确性,这些数字也不是很糟糕。

迁移学习

既然 CNN 在 ImageNet 分类任务中击败了所有经典的机器学习算法,为什么不利用这些强大机器中的一个来解决手头的问题呢?这就是转移学习背后的理念。简而言之,我们使用预训练模型的权重来解决我们的问题,该模型可能是在更大的数据集上训练的,并且在解决问题时给出了更好的结果。换句话说,我们“转移”一个模型的知识来构建我们的模型。在我们的例子中,我们使用在 ImageNet 数据集上训练的 VGG16 网络来矢量化数据集中的图像。

这里获取想法,我们尝试了两种方法

  1. 使用 VGG16 输出的瓶颈特性,并在此基础上构建一个浅层网络
  2. 微调上面(1)中 vgg 16+浅层模型的最后一个卷积层

很明显,2 比 1 给出了更好的结果。在最终实现之前,我们尝试了多种浅层网络架构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Top layer architecture

展平层的输入是 VGG16 输出的瓶颈特征。这些是形状为(2×2×512)的张量,因为我们使用了 64×64 的输入图像。

以上架构给出了以下结果

训练准确率:83.18%,训练损失:0.3230

验证准确率:84.26%,验证损失:0.3833

它使用 Adam optimizer 进行训练,具有每 10 个时期后降低的自定义学习率(除了 Adam 在每批后的定期更新之外)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Custom update rule

第二种方法需要最后一层的微调。这里需要注意的重要一点是,我们必须使用预训练的顶层模型进行微调。目标是稍微改变已经学习的权重,以便更好地适应数据。如果我们使用一些随机初始化的权重,微小的变化对它们没有任何好处,大的变化会破坏卷积层的学习权重。我们还需要一个非常小的学习率来微调我们的模型(原因和上面提到的一样)。在这个帖子里,建议使用 SGD 优化器进行微调。然而,我们观察到亚当在这项任务中表现优于 SGD。

微调模型给出了以下结果

训练准确率:99.16%,训练损失:0.018

验证准确率:94.77%,验证损失:0.30

稍微过度拟合的模型,可以通过使用更小的学习率来补救(我们使用 1e-6)。

除了 VGG16,我们还尝试了在 Image-Net 数据集上预训练的 ResNet50 和 VGG19 模型的瓶颈特征。ResNet50 的特性优于 VGG16。VGG19 没有给出一个很满意的表现。我们使用相同的学习率更新策略,以类似于 VGG16 的方式微调 ResNet50 架构(最后一个卷积层),它给出了更有希望的结果,过拟合问题更少。

训练准确率:98.65%,训练损失:0.048

验证准确率:95.22%,验证损失:0.18

测试数据的最终模型预测

为了从先前创建的测试集中采样图像,我们采用了与用于创建训练和交叉验证集类似的策略,即使用它们的遮罩在边界处采样伪图像,并采样相同数量的具有相同尺寸的原始图像。微调的 VGG16 模型用于预测这些补丁的标签,并给出以下结果

测试准确率:94.65%,测试损失:0.31

另一方面,ResNet50 对测试数据给出了以下结果

测试准确率:95.09%,测试损失:0.19

如我们所见,我们的模特表现不错。我们仍有很大的改进空间。如果通过数据扩充(剪切、调整大小、旋转和其他操作)可以生成更多的数据,也许我们可以微调更多层的 SOTA 网络。

在这篇文章中,我们谈到了检测假图像。然而,一旦检测到伪造图像,我们必须确定该图像中的伪造区域。假图像中拼接区域的定位将是下一篇文章的主题。这部分的全部代码可以在这里找到。

这个帖子到此为止。请在评论区告诉我其他检测假图片的好方法。下次见…再见。

来源

  1. http://ifc.recod.ic.unicamp.br/fc.website/index.py?sec=5
  2. 【https://ieeexplore.ieee.org/abstract/document/4806202
  3. https://www.youtube.com/watch?v=uihBwtPIBxM
  4. https://medium . com/@ gopalkalpande/biological-inspiration-of-convolutionary-neural-network-CNN-9419668898 AC
  5. https://ieeexplore.ieee.org/abstract/document/7823911
  6. https://blog . keras . io/building-powerful-image-class ification-models-using-very-little-data . html

Numpy 和 OpenCV 中的图像几何变换

原文:https://towardsdatascience.com/image-geometric-transformation-in-numpy-and-opencv-936f5cd1d315?source=collection_archive---------4-----------------------

图像变形背后的数学和代码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source: Geometric Transformations

几何变换在计算机视觉中普遍存在

几何变换是一种重要的图像处理技术,有着广泛的应用。例如,在计算机图形学中,一个简单的用例是在桌面和移动设备上显示图形内容时简单地重新缩放图形内容。

它还可以应用于将图像投影扭曲到另一个图像平面。例如,我们希望从另一个角度看一个场景,而不是直直地看着它,在这个场景中应用了透视变换来实现这一点。

另一个令人兴奋的应用是训练深度神经网络。训练深度模型需要大量数据。几乎在所有情况下,随着训练数据的增加,模型都会受益于更高的泛化性能。人工生成更多数据的一种方法是对输入数据随机应用仿射变换。这种技术也被称为增强

在本文中,我将带您了解一些转换,以及我们如何在 Numpy 中执行它们,首先从基本原则上理解这个概念。然后如何使用 OpenCV 轻松实现。如果你像我一样喜欢从基础理论中理解概念,这篇文章会让你感兴趣!

我将特别关注 2D 仿射变换。你需要的是一些线性代数的基础知识,你应该能跟上。附带的代码可以在这里找到,如果你喜欢自己尝试的话!

仿射变换的类型

在不深入数学细节的情况下,变换的行为由仿射 A 中的一些参数控制。

x’ = Ax

where A = [[a_11, a_12, a_13],
           [a_21, a_22, a_23],
           [  0 ,   0 ,   1 ]]

是齐次坐标中的 2x3 矩阵或 3x3,并且 x 是齐次坐标中形式为[x, y][x, y, 1]的向量。上面的公式说 A 取任意向量 x 并将其映射到另一个向量x’。

通常,仿射变换具有 6 个自由度,在逐像素矩阵相乘之后,将任何图像扭曲到另一个位置。变换后的图像保留了原图像中的平行线直线(想想剪切)。满足这两个条件的任何矩阵 A 都被认为是仿射变换矩阵。

缩小我们的讨论,有一些特殊形式的 A,这是我们感兴趣的。这包括如下图所示的旋转平移缩放矩阵。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Figure 1: 2D Coordinate Transformations. Note that rotation is about z-axis

上述仿射变换的一个非常有用的特性是它们是线性函数。它们保留了乘法和加法的运算,遵守叠加原理。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

source: Wikipedia

换句话说,我们可以组合两个或更多的变换:向量加法来表示平移,矩阵乘法来表示线性映射,只要我们在齐次坐标中表示它们。例如,我们可以将旋转后的平移表示为

A = array([[cos(angle),  -sin(angle), tx],
            [sin(angle), cos(angle),  ty],
            [0,          0,           1]])

图像表示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Figure 2: Pixel Coordinates

在 Python 和 OpenCV 中,2D 矩阵的原点位于左上角,从 x,y= (0,0)开始。坐标系是左手坐标系,其中 x 轴指向正右侧,y 轴指向正下方。

但是你在教科书和文献中找到的大多数变换矩阵,包括上面显示的 3 个矩阵,都遵循右手坐标系。所以必须做一些小的调整来对齐轴的方向。

欧氏空间中的常见变换

在我们对图像进行变换实验之前,让我们看看如何在点坐标上进行变换。因为它们本质上与图像是网格中的 2D 坐标阵列是一样的。

利用我们在上面学到的知识,下面的代码可以用来转换点[0, 0], [0, 1], [1, 0], [1,1]。图 3 中的蓝点。

Python 提供了一个有用的速记运算符,@ 来表示矩阵乘法。

# Points generator
def get_grid(x, y, homogenous=False):
    coords = np.indices((x, y)).reshape(2, -1)
    return np.vstack((coords, np.ones(coords.shape[1]))) if homogenous else coords# Define Transformations
def get_rotation(angle):
    angle = np.radians(angle)
    return np.array([
        [np.cos(angle), -np.sin(angle), 0],
        [np.sin(angle),  np.cos(angle), 0],
        [0, 0, 1]
    ])
def get_translation(tx, ty):
    return np.array([
        [1, 0, tx],
        [0, 1, ty],
        [0, 0, 1]
    ])
def get_scale(s):
    return np.array([
        [s, 0, 0],
        [0, s, 0],
        [0, 0, 1]
    ])R1 = get_rotation(135)
T1 = get_translation(-2, 2)
S1 = get_scale(2)# Apply transformation x' = Ax
coords_rot = R1 @ coords
coords_trans = T1 @ coords
coords_scale = S1 @ coords
coords_composite1 = R1 @ T1 @ coords
coords_composite2 = T1 @ R1 @ coords

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Figure 3: Transforming points: (0,0), (0,1), (1,0), (1,1)

重要的是要注意,除了少数例外,矩阵通常不交换。即

A1 @ A2 != A2 @ A1

因此,对于转换来说

# Translation and then rotation
coords_composite1 = R1 @ T1 @ coords# Rotation and then translation
coords_composite2 = T1 @ R1 @ coords

您将在图 3 中观察到,它们不会导致相同的映射,并且顺序很重要。函数如何应用可以从右到左理解。

数字转换

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source: Kitti Dataset

现在对于图像,有几件事需要注意。首先,如前所述,我们必须重新调整垂直轴。其次,变换后的点必须投影到图像平面上。

本质上,需要采取的步骤是:

  1. 创建一个新的图像 I’(x,y)来输出变换点
  2. 应用转换一个
  3. 将这些点投影到一个新的图像平面上,只考虑那些位于图像边界内的点。

示例:关于图像中心的旋转、缩放和平移

让我们来看一个变换,我们希望放大 2 倍并将图像围绕其中心位置旋转 45 度

这可以通过应用以下复合矩阵来实现。

height, width = image.shape[:2]
tx, ty = np.array((width // 2, height // 2))
angle = np.radians(45)
scale = 2.0R = np.array([
    [np.cos(angle), np.sin(angle), 0],
    [-np.sin(angle), np.cos(angle), 0],
    [0, 0, 1]
])T = np.array([
    [1, 0, tx],
    [0, 1, ty],
    [0, 0, 1]
])S = np.array([
    [scale, 0, 0],
    [0, scale, 0],
    [0, 0, 1]
])A = T @ R @ S @ np.linalg.inv(T)

应用于图像

# Grid to represent image coordinate
coords = get_grid(width, height, True)
x_ori, y_ori = coords[0], coords[1] # Apply transformation
warp_coords = np.round(A@coords).astype(np.int)
xcoord2, ycoord2 = warp_coords[0, :], warp_coords[1, :]# Get pixels within image boundary
indices = np.where((xcoord >= 0) & (xcoord < width) &
                   (ycoord >= 0) & (ycoord < height))xpix2, ypix2 = xcoord2[indices], ycoord2[indices]xpix, ypix = x_ori[indices], y_ori[indices]# Map the pixel RGB data to new location in another array
canvas = np.zeros_like(image)
canvas[ypix, xpix] = image[yy, xx]

在上面的两个代码片段中有几点需要注意。

  1. 左手坐标系旋转通过交换符号来解决。
  2. 因为点是围绕原点旋转的,所以在进行旋转和缩放之前,我们首先将中心平移到原点。
  3. 然后,点被转换回图像平面
  4. 变换点被四舍五入为整数以表示离散的像素值。
  5. 接下来,我们只考虑位于图像边界内的像素
  6. 映射对应 I(x,y)和 I’(x,y)

正如你所看到的,由于步骤 4,结果图像(图 4)将有几个锯齿和漏洞。为了消除这一点,开源库使用插值技术来填充转换后的缺口。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Figure 4: Image rotated by 45 degrees counter-clockwise and scale by 2x. Aliasing effect is significant

逆翘曲

另一种防止混叠的方法是在给定扭曲点 X’的情况下,将扭曲公式化为从源图像 I(x,y)重采样的扭曲。这可以通过将 X '乘以 a 的倒数来实现。注意,变换必须是可逆的。

  1. 对 X '进行逆变换。
X = np.linalg.inv(A) @ X'

注意:对于图像,X ‘的逆变形只是将 I’(x,y)重新投影到 I(x,y)上。所以我们简单地对 I’(x,y)像素坐标进行逆变换,如下图所示。

2.确定它在原始图像平面中的位置

3.从 I(x,y)重新采样 RGB 像素,并将其映射回 I’(x,y)

代码

# set up pixel coordinate I'(x, y)
coords = get_grid(width, height, True)
x2, y2 = coords[0], coords[1]# Apply inverse transform and round it (nearest neighbour interpolation)
warp_coords = (Ainv@coords).astype(np.int)
x1, y1 = warp_coords[0, :], warp_coords[1, :]# Get pixels within image boundaries
indices = np.where((x1 >= 0) & (x1 < width) &
                   (y1 >= 0) & (y1 < height))xpix1, ypix1 = x2[indices], y2[indices]
xpix2, ypix2 = x1[indices], y1[indices]# Map Correspondence
canvas = np.zeros_like(image)
canvas[ypix1, xpix1] = image[ypix2,xpix2]

运行上面的代码应该会给你一个密集的、没有孔洞的图像:)可以随意下载代码并使用参数来应用其他的转换。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

OpenCV 中的转换

既然您对几何变换有了更好的理解,大多数开发人员和研究人员通常会省去编写所有这些变换的麻烦,只需依靠优化的库来执行任务。在 OpenCV 中做仿射变换非常简单。

有几种方法可以做到。

  1. 自己写仿射变换,调用cv2.**warpAffine**(image, A, output_shape)

下面的代码显示了整个仿射矩阵,它将给出与上面相同的结果。一个很好的练习就是自己推导公式!

def get_affine_cv(t, r, s):
    sin_theta = np.sin(r)
    cos_theta = np.cos(r)

    a_11 = s * cos_theta
    a_21 = -s * sin_theta

    a_12 = s * sin_theta
    a_22 = s * cos_theta

    a_13 = t[0] * (1 - s * cos_theta) - s * sin_theta * t[1]
    a_23 = t[1] * (1 - s * cos_theta) + s * sin_theta * t[0]return np.array([[a_11, a_12, a_13],
                 [a_21, a_22, a_23]])A2 = get_affine_cv((tx, ty), angle, scale)
warped = cv2.warpAffine(image, A2, (width, height))

2.依靠 OpenCV 使用cv2.**getRotationMatrix2D**(center, angle, scale)返回仿射变换矩阵。

该功能将图像绕点中心旋转角度,并用标尺对其进行缩放

A3 = cv2.getRotationMatrix2D((tx, ty), np.rad2deg(angle), scale)warped = cv2.warpAffine(image, b3, (width, height), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=0)

摘要

在本文中,我介绍了几何变换的基本概念,以及如何将它应用于图像。许多先进的计算机视觉,如使用视觉里程计的 slam 和多视图合成依赖于首先理解变换。

我相信,作为一名计算机视觉从业者,当我们使用 imgaug 和 albumentation 等强大的库时,理解这些转换是如何工作的肯定是有益的。

感谢阅读!我希望您已经更好地理解了这些公式是如何在库中编写和使用的。请关注查看更多关于计算机视觉和机器学习的帖子:)如果你发现任何错误或任何不清楚的地方,请在评论中指出来!

更多文章

[## 深度估计:基础和直觉

理解事物相对于相机有多远仍然是困难的,但对于激动人心的…

towardsdatascience.com](/depth-estimation-1-basics-and-intuition-86f2c9538cd1) [## 相机-激光雷达投影:在 2D 和 3D 之间导航

激光雷达和相机是感知和场景理解的两个必不可少的传感器。他们建立了一个环境…

medium.com](https://medium.com/swlh/camera-lidar-projection-navigating-between-2d-and-3d-911c78167a94)

10 个 Python 图像处理工具。

原文:https://towardsdatascience.com/image-manipulation-tools-for-python-6eb0908ed61f?source=collection_archive---------2-----------------------

概述了一些常用的 Python 库,它们提供了一种简单直观的方法来转换图像。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Luriko Yamaguchi from Pexels

本文内容改编自我之前在opensource.com发表的 自己的文章

介绍

当今世界充满了数据,而图像是这些数据的重要组成部分。然而,为了投入使用,这些图像需要被处理。因此,图像处理是分析和处理数字图像的过程,主要目的是提高图像质量或从中提取一些信息,然后加以利用。

图像处理中的典型任务包括显示图像、基本操作,如裁剪、翻转、旋转等。图像分割、分类和特征提取、图像恢复和图像识别。Python 成为这种图像处理任务的合适选择。这是因为它作为一门科学编程语言越来越受欢迎,并且在其生态系统中有许多先进的图像处理工具可以免费获得。

让我们看看一些常用于图像操作任务的 Python 库。

1.scikit 图像

scikit-image 是一个开源 Python 包,可以与[numpy](http://docs.scipy.org/doc/numpy/reference/index.html#module-numpy)数组一起工作。它在研究、教育和工业应用中实现算法和实用程序。这是一个相对简单的库,即使对于那些不熟悉 Python 生态系统的人来说也是如此。该代码由一群活跃的志愿者编写,质量很高,经过了同行评审。

资源

有很多例子和实际使用案例很好地证明了这一点。在这里阅读文档。

使用

这个包作为skimage导入,大多数功能都在子模块中。克扣的一些例子包括:

  • 滤像
import matplotlib.pyplot as plt 
%matplotlib inlinefrom skimage import data,filtersimage = data.coins()
# ... or any other NumPy array!
edges = filters.sobel(image)
plt.imshow(edges, cmap='gray')

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image filtering in skimage

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Template Matching using match_template function

您可以在图库中找到更多示例。

2.Numpy

Numpy 是 Python 编程中的核心库之一,提供对数组的支持。图像本质上是包含数据点像素的标准 Numpy 数组。因此,通过使用基本的 NumPy 操作,如切片、遮罩和花式索引,我们可以修改图像的像素值。可以使用 skimage 加载图像,并使用 matplotlib 显示图像。

资源

Numpy 的官方文档页面上有完整的资源和文档列表。

使用

使用 Numpy 遮罩图像。

import numpy as np
from skimage import data
import matplotlib.pyplot as plt 
%matplotlib inlineimage = data.camera()
type(image)**numpy.ndarray #Image is a numpy array**mask = image < 87
image[mask]=255
plt.imshow(image, cmap='gray')

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.Scipy

scipy 是 Python 的另一个核心科学模块,类似于 Numpy,可用于基本的图像操作和处理任务。特别地,子模块[**scipy.ndimage**](https://docs.scipy.org/doc/scipy/reference/ndimage.html#module-scipy.ndimage)提供了在 n 维 NumPy 阵列上操作的功能。该软件包目前包括线性和非线性过滤功能,二元形态学,B 样条插值,和对象测量。

资源

关于scipy.ndimage包提供的功能的完整列表,请参考此处的文档

使用

使用 SciPy 使用高斯滤波器进行模糊处理:

from scipy import misc,ndimageface = misc.face()
blurred_face = ndimage.gaussian_filter(face, sigma=3)
very_blurred = ndimage.gaussian_filter(face, sigma=5)#Results
plt.imshow(<image to be displayed>)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.PIL/枕头

PIL ( Python 图像库)是 Python 编程语言的免费库,增加了对打开、操作和保存许多不同图像文件格式的支持。然而,它的发展停滞不前,最后一次发布是在 2009 年。幸运的是,还有 Pillow ,这是 PIL 积极开发的一款更容易安装的 fork,可以运行在所有主流操作系统上,并且支持 Python 3。该库包含基本的图像处理功能,包括点操作、使用一组内置卷积核进行过滤以及色彩空间转换。

资源

文档包含安装说明和涵盖库每个模块的示例。

使用

使用图像过滤器增强 Pillow 中的图像:

**from** PIL **import** Image**,** ImageFilter
*#Read image*
im = Image.open**(** 'image.jpg' **)**
*#Display image*
im.show**()**from PIL import ImageEnhance
enh = ImageEnhance.Contrast(im)
enh.enhance(1.8).show("30% more contrast")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source

5.OpenCV-Python

OpenCV ( 开源计算机视觉库)是计算机视觉应用最广泛使用的库之一。OpenCV-Python是针对 OpenCV 的 Python API。OpenCV-Python 不仅速度快,因为后台包含用 C/C++编写的代码,而且易于编码和部署(由于前台有 Python 包装器)。这使得它成为执行计算密集型计算机视觉程序的最佳选择。

资源

OpenCV2-Python-Guide 让 OpenCV-Python 的入门变得简单。

使用

下面这个例子展示了 OpenCV-Python 在使用金字塔创建一个名为**“苹果”的新水果的**图像混合中的能力****

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source

6.简单 CV

SimpleCV 也是一个构建计算机视觉应用的开源框架。有了它,您可以访问 OpenCV 等几个高性能的计算机视觉库,而不必先了解位深度、文件格式、色彩空间等。学习曲线比 OpenCV 小得多,正如他们的标语所说,“这是使计算机视觉变得容易的**”支持 SimpleCV 的一些观点有:**

  • 即使是初级程序员也能编写简单的机器视觉测试
  • 相机、视频文件、图像和视频流都可以互操作

资源

T21 的官方文档简单易懂,有大量的例子和用例可供参考。

使用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

7.马霍塔斯

Mahotas 是另一个用于 Python 的计算机视觉和图像处理库。它包含传统的图像处理功能,如过滤和形态学操作,以及用于特征计算的更现代的计算机视觉功能,包括兴趣点检测和局部描述符。界面是 Python 语言的,适合快速开发,但算法是用 C++实现的,并针对速度进行了微调。Mahotas 库速度很快,代码很少,依赖性也很小。点击阅读他们的官方论文获得更多见解。

资源

文档 n 包含安装说明、示例,甚至一些教程来帮助开始使用 Mahotas。

使用

Mahotas 库依靠使用简单的代码来完成工作。对于’ 寻找沃利 的问题,’ Mahotas 做得非常出色,而且代码量也很少。下面是源代码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source

8.SimpleITK

ITKInsight Segmentation and Registration Toolkit是一个开源的跨平台系统,为开发人员提供了一套广泛的图像分析软件工具。K 是建立在 ITK 之上的一个简化层,旨在促进它在快速原型、教育和解释语言中的使用。 SimpleITK 是一个图像分析工具包,包含许多支持一般过滤操作、图像分割和配准的组件。SimpleITK 是用 C++编写的,但可用于许多编程语言,包括 Python。

资源

已经提供了大量的 Jupyter 笔记本来说明 SimpleITK 在教育和研究活动中的应用。这些笔记本演示了使用 Python 和 R 编程语言使用 SimpleITK 进行交互式图像分析。

使用

下面的动画显示了用 SimpleITK 和 Python 创建的严格的 CT/MR 配准过程。这里看源代码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source

9.pgmagick

p gmagick 是 GraphicsMagick 库的基于 Python 的包装器。GraphicsMagick图像处理系统有时被称为图像处理的瑞士军刀。它提供了一个强大而高效的工具和库集合,支持以超过 88 种主要格式读取、写入和操作图像,包括重要的格式,如 DPX、GIF、JPEG、JPEG-2000、PNG、PDF、PNM 和 TIFF。

资源

PgMagick 的官方 Github 库有安装说明和要求。关于这个主题还有一个详细的用户 guid。

使用

使用 pgmagic k 可以执行的一些图像操作活动包括:

图像缩放 g :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

边缘提取:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

10.皮开罗

Pycairo 是图形库 Cairo 的一套 Python 绑定。Cairo 是一个用于绘制矢量图形的 2D 图形库。矢量图形很有趣,因为它们在调整大小或变换时不会失去清晰度。Pycairo 是用 Python 为 cairo 编写的一组绑定。

资源

Pycairo GitHub 资源库是一个很好的资源,里面有关于安装和使用的详细说明。还有一个 G etting 入门 指南,里面有 Pycairo 的简要教程。

使用

使用 Pycairo 绘制线条、基本形状和径向渐变

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结论

这些是 Python 的一些有用的免费图像处理库。有些相对比较了解,有些可能对你来说比较陌生。尝试一下,了解更多。

基于 Tensorflow-js 的图像目标检测🤔

原文:https://towardsdatascience.com/image-object-detection-with-tensorflow-js-b8861119ed46?source=collection_archive---------7-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是图像处理系列从零到一的第四篇帖子。

这是其他帖子的列表

  1. 图像处理— OpenCV 和 Node.js(第三部分)
  2. 图像处理—制作自定义滤镜— React.js —第二部分
  3. 使用 Cloundinary 的图像处理(第一部分)

在这篇文章中,我们将使用 Tensorflow-js 和预训练的模型构建一个图像对象检测系统。

首先,在网页中部署 TensorFlow 有很多方法,其中一种方法是包含 ml5js。参观 https://ml5js.org/。它是 tf.js 的包装器,一个张量流和 p5.js 库,用于在 Html 元素中进行操作。

但是,我们希望保持后端部分的电源,以便我可以尝试使用 API 的后端进程等在后端运行这些模型。

因此,在本文的前半部分,我们将使用 React.js 和 Material-UI 创建一个 UI,在后半部分,我们将在 Node.js 中创建一个 API 来支持这个 UI。

让我们从构建一个示例 React 项目开始。🚀

前端部分:-

如果您遵循了我的前一篇文章,那么 react 项目似乎很容易构建。

  1. 打开终端,做
create-react-app image_classification_react_ui

这将创建一个 react 项目来使用。

2.让我们安装所需的依赖项

npm install @material-ui/core
npm install — save isomorphic-fetch es6-promise

注意:从 React 代码调用对象检测 API 端点需要同构提取。

3.在你最喜欢的编辑器中打开项目,让我们创建两个文件夹

  1. 容器 —这将包含一个文件— ImageOps.jsx,其中包含所有的前端 UI 代码。
  2. utils —这将包含一个文件Api.js,用于调用对象检测端点。
└── src
    ├── containers
        ├── ImageOps.jsx
    ├── utils
        ├── Api.js

让我们研究一下ImageOps.jsx代码并理解它。

import React from 'react';

import Container from '[@material](http://twitter.com/material)-ui/core/Container';
import Grid from '[@material](http://twitter.com/material)-ui/core/Grid';

import Card from '[@material](http://twitter.com/material)-ui/core/Card';
import CardContent from '[@material](http://twitter.com/material)-ui/core/CardContent';
import Typography from '[@material](http://twitter.com/material)-ui/core/Typography';
import Button from '[@material](http://twitter.com/material)-ui/core/Button';
import { red } from '[@material](http://twitter.com/material)-ui/core/colors';

import {api} from '../utils/Api';

import Table from '[@material](http://twitter.com/material)-ui/core/Table';
import TableBody from '[@material](http://twitter.com/material)-ui/core/TableBody';
import TableCell from '[@material](http://twitter.com/material)-ui/core/TableCell';
import TableHead from '[@material](http://twitter.com/material)-ui/core/TableHead';
import TableRow from '[@material](http://twitter.com/material)-ui/core/TableRow';
import Paper from '[@material](http://twitter.com/material)-ui/core/Paper';
import CircularProgress from '[@material](http://twitter.com/material)-ui/core/CircularProgress';

export default class ImageOps extends React.Component {

   constructor(props) {
       super(props);

       this.state = {
           image_object: null,
           image_object_details: {},
           active_type: null
       }
   }

   updateImageObject(e) {
       const file  = e.target.files[0];
       const reader = new FileReader();

       reader.readAsDataURL(file);
       reader.onload = () => {
           this.setState({image_object: reader.result, image_object_details: {}, active_type: null});
       };

   }

   processImageObject(type) {

       this.setState({active_type: type}, () => {

           if(!this.state.image_object_details[this.state.active_type]) {
               api("detect_image_objects", {
                   type,
                   data: this.state.image_object
               }).then((response) => {

                   const filtered_data = response;
                   const image_details = this.state.image_object_details;

                   image_details[filtered_data.type] = filtered_data.data;

                   this.setState({image_object_details: image_details });
               });
           }
       });
   }

   render() {
       return (
           <Container maxWidth="md">
               <Grid container spacing={2}>
                   <Grid item xs={12}>
                       <CardContent>
                           <Typography variant="h4" color="textPrimary" component="h4">
                               Object Detection Tensorflow
                           </Typography>
                       </CardContent>
                   </Grid>
                   <Grid item xs={12}>
                       {this.state.image_object &&
                           <img src={this.state.image_object} alt="" height="500px"/>
                       }
                   </Grid>
                   <Grid item xs={12}>
                       <Card>
                           <CardContent>
                               <Button variant="contained"
                                   component='label' // <-- Just add me!
                                   >
                                   Upload Image
                                   <input accept="image/jpeg" onChange={(e) =>  this.updateImageObject(e)} type="file" style={{ display: 'none' }} />
                               </Button>
                           </CardContent>
                       </Card>
                   </Grid>
                   <Grid item xs={3}>
                       <Grid container justify="center" spacing={3}>
                           <Grid item >
                               {this.state.image_object && <Button onClick={() => this.processImageObject("imagenet")}variant="contained" color="primary">
                                   Get objects with ImageNet
                               </Button>}
                           </Grid>
                           <Grid item>
                               {this.state.image_object && <Button onClick={() => this.processImageObject("coco-ssd")}variant="contained" color="secondary">
                                   Get objects with Coco SSD
                               </Button>}
                           </Grid>
                       </Grid>
                   </Grid>
                   <Grid item xs={9}>
                       <Grid container justify="center">
                           {this.state.active_type && this.state.image_object_details[this.state.active_type] &&
                               <Grid item xs={12}>
                                   <Card>
                                       <CardContent>
                                           <Typography variant="h4" color="textPrimary" component="h4">
                                               {this.state.active_type.toUpperCase()}
                                           </Typography>
                                           <ImageDetails type={this.state.active_type} data = {this.state.image_object_details[this.state.active_type]}></ImageDetails>
                                       </CardContent>
                                   </Card>
                               </Grid>
                           }
                           {this.state.active_type && !this.state.image_object_details[this.state.active_type] &&
                               <Grid item xs={12}>
                                   <CircularProgress
                                       color="secondary"
                                   />
                               </Grid>
                           }
                       </Grid>
                   </Grid>
               </Grid>
           </Container>
       )
   }
}

class ImageDetails extends React.Component {

   render() {

       console.log(this.props.data);

       return (
           <Grid item xs={12}>
               <Paper>
                   <Table>
                   <TableHead>
                       <TableRow>
                       <TableCell>Objects</TableCell>
                       <TableCell align="right">Probability</TableCell>
                       </TableRow>
                   </TableHead>
                   <TableBody>
                       {this.props.data.map((row) => {
                           if (this.props.type === "imagenet") {
                               return (
                                   <TableRow key={row.className}>
                                       <TableCell component="th" scope="row">
                                       {row.className}
                                       </TableCell>
                                       <TableCell align="right">{row.probability.toFixed(2)}</TableCell>
                                   </TableRow>
                               )
                           } else if(this.props.type === "coco-ssd") {
                               return (
                                   <TableRow key={row.className}>
                                       <TableCell component="th" scope="row">
                                       {row.class}
                                       </TableCell>
                                       <TableCell align="right">{row.score.toFixed(2)}</TableCell>
                                   </TableRow>
                               )
                           }
                           })
                       }
                   </TableBody>
                   </Table>
               </Paper>

           </Grid>
       )
   }
}

}

注:这里是上面的 Github repo 链接—https://Github . com/overflow js-com/image _ object _ det ction _ react _ ui。如果你觉得理解很难,那么我强烈推荐你阅读我们的第 2 部分和第 1 部分。

在渲染中,我们创建了一个三行的网格,其中一行包含标题

第二,包含要显示的图像

<Grid item xs={12}>
  {this.state.image_object &&
    <img src={this.state.image_object} alt="" height="500px"/>}                
</Grid>

如果图像已经上传或图像对象处于可用状态,我们将在此显示图像

下一个网格包含一个按钮,用于上传文件并将上传的文件更新到当前状态。

<Grid item xs={12}>
    <Card>
        <CardContent>
            <Button variant="contained"
                component='label' // <-- Just add me!
                >
                Upload Image
                <input accept="image/jpeg" onChange={(e) =>  this.updateImageObject(e)} type="file" style={{ display: 'none' }} />
            </Button>
        </CardContent>
    </Card>
</Grid>

我们调用了一个函数updateImage来更新状态下当前选中的图像。

updateImageObject(e) {
       const file  = e.target.files[0];
       const reader = new FileReader();

       reader.readAsDataURL(file);
       reader.onload = () => {
           this.setState({image_object: reader.result, image_object_details: {}, active_type: null
           });
       };
}

在上面的代码中,我们从文件输入上传器中读取当前文件对象,并在当前状态下加载它的数据。随着新图像的上传,我们正在重置 image_object_details 和 active_type,以便可以对上传的图像应用新的操作

下面是下一个网格,包含每个模型的两个按钮的代码。

<Grid item xs={3}>
        <Grid container justify="center" spacing={3}>
            <Grid item >
                {this.state.image_object && <Button onClick={() => this.processImageObject("imagenet")}variant="contained" color="primary">
                    Get objects with ImageNet
                </Button>}
            </Grid>
            <Grid item> 
                {this.state.image_object && <Button onClick={() => this.processImageObject("coco-ssd")}variant="contained" color="secondary">
                    Get objects with Coco SSD
                </Button>}
            </Grid>
        </Grid>
    </Grid>
    <Grid item xs={9}>
        <Grid container justify="center">
            {this.state.active_type && this.state.image_object_details[this.state.active_type] &&
                <Grid item xs={12}>
                    <Card>
                        <CardContent>
                            <Typography variant="h4" color="textPrimary" component="h4">
                                {this.state.active_type.toUpperCase()}
                            </Typography>
                            <ImageDetails data = {this.state.image_object_details[this.state.active_type]}></ImageDetails>
                        </CardContent>
                    </Card>
                </Grid>
            }
            {this.state.active_type && !this.state.image_object_details[this.state.active_type] && 
                <Grid item xs={12}>
                    <CircularProgress
                        color="secondary"
                    />
                </Grid>
            }
     </Grid>
</Grid>

在这里,我们将网格从 12 列父网格分为 3 列和 9 列两部分。

第一个有 3 列的网格包含两个有两个按钮的网格

<Grid container justify="center" spacing={3}>
    <Grid item >
        {this.state.image_object && <Button onClick={() => this.processImageObject("imagenet")}variant="contained" color="primary">
            Get objects with ImageNet
        </Button>}
    </Grid>
    <Grid item> 
        {this.state.image_object && <Button onClick={() => this.processImageObject("coco-ssd")}variant="contained" color="secondary">
            Get objects with Coco SSD
        </Button>}
    </Grid>
</Grid>

我们正在使用 ImageNet 和 Coco SSD 型号分析图像检测,并比较输出。

每个按钮都有一个动作事件 onClick,它调用一个函数processImageObject(),该函数将模型的名称作为参数。

processImageObject(type) {this.setState({active_type: type}, () => {
        api("detect_image_objects", {
            type,
            data: this.state.image_object
        }).then((response) => {

            const filtered_data = response;
            const image_details = this.state.image_object_details;image_details[filtered_data.type] = filtered_data.data;this.setState({image_object_details: image_details });
        });
    });
}

我们正在用当前选择的模态设置状态对象action_type

Process image 对象函数将从 state 中获取当前图像,并将其发送给 API 函数,我接下来将向您展示该函数,API 将被调用detect_image_objects,作为响应,我们将在 UI 中进行处理和显示。

将从 API 获取响应,并在阶段image_object_details中设置。

我们根据型号类型 (imagenet/coco-ssd) 设置每个 API 响应

该按钮仅在image_object处于该状态时可见。

{
 this.state.image_object && 
 <Button onClick={() => this.processImageObject()} variant="contained" color="primary">Process Image 
 </Button>
}

下面是我们创建的另一个网格:

<Grid item xs={9}>
    <Grid container justify="center">
        {this.state.active_type && this.state.image_object_details[this.state.active_type] &&
            <Grid item xs={12}>
                <Card>
                    <CardContent>
                        <Typography variant="h4" color="textPrimary" component="h4">
                            {this.state.active_type.toUpperCase()}
                        </Typography>
                        <ImageDetails  type={this.state.active_type} data = {this.state.image_object_details[this.state.active_type]}></ImageDetails>
                    </CardContent>
                </Card>
            </Grid>
        }
        {this.state.active_type && !this.state.image_object_details[this.state.active_type] && 
            <Grid item xs={12}>
                <CircularProgress
                    color="secondary"
                />
            </Grid>
        }
    </Grid>
</Grid>

这里我们已经检查了当前的action_type模式是否被选中,如果 API 已经处理了细节,它将显示对象细节。为此,我们创建了一个组件ImageDetails

让我们看看ImageDetails组件代码,它很容易理解。

class ImageDetails extends React.Component {

   render() {

       console.log(this.props.data);

       return (
           <Grid item xs={12}>
               <Paper>
                   <Table>
                   <TableHead>
                       <TableRow>
                       <TableCell>Objects</TableCell>
                       <TableCell align="right">Probability</TableCell>
                       </TableRow>
                   </TableHead>
                   <TableBody>
                       {this.props.data.map((row) => {
                           if (this.props.type === "imagenet") {
                               return (
                                   <TableRow key={row.className}>
                                       <TableCell component="th" scope="row">
                                       {row.className}
                                       </TableCell>
                                       <TableCell align="right">{row.probability.toFixed(2)}</TableCell>
                                   </TableRow>
                               )
                           } else if(this.props.type === "coco-ssd") {
                               return (
                                   <TableRow key={row.className}>
                                       <TableCell component="th" scope="row">
                                       {row.class}
                                       </TableCell>
                                       <TableCell align="right">{row.score.toFixed(2)}</TableCell>
                                   </TableRow>
                               )
                           }
                           })
                       }
                   </TableBody>
                   </Table>
               </Paper>

           </Grid>
       )
   }
}

该组件将显示从对象的模态名称接收的详细信息及其概率。基于我们正在处理的模态的类型,我们可以显示在这个类中处理的两个不同的输出。

4.最后一步是编写 API.js 包装器进行服务器端调用。

import fetch from  'isomorphic-fetch';

const BASE_API_URL = "[http://localhost:4000/api/](http://localhost:4000/api/)"

export function api(api_end_point, data) {

   return fetch(BASE_API_URL+api_end_point,
       {
           method: 'POST',
           headers: {
               'Content-Type': 'application/json'
           },
           body:JSON.stringify(data)
       }).then((response) => {
           return response.json();
       });
}

在这个示例代码中,我们提供了一个通过获取 API 函数的包装器,它将获取 API 端点和数据,并将构造完整的 URL 和从 API 发送的返回响应。

最终的用户界面将如下所示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

后端部分:-

现在,既然我们已经有了自己的 UI,让我们开始使用 tensorflow.js 创建一个 API 端点,看起来像这样

[http://localhost:4000/api/detect_image_objects](http://localhost:4000/api/detect_image_objects)
  1. 第一步是选择一个样板文件,它使用 express.js 并提供只编写路由和对象检测逻辑的能力。在本教程中,我们使用https://github.com/developit/express-es6-rest-api。让我们克隆它
git clone [https://github.com/developit/express-es6-rest-api](https://github.com/developit/express-es6-rest-api) image_detection_tensorflow_api

2.现在,通过运行以下命令安装所有依赖项

cd image_detection_tensorflow_api
npm install

3.转到项目根目录下的config.json,将port编辑为 4000,将bodylimit编辑为 10000kb。

注意:我们将使用预先训练的模型imagenet and coco-ssd.从图像中寻找多个对象是一项繁琐的工作,尽管 image net 以从图像中检测单个对象(动物/其他对象)而闻名,但这两种模型都基于非常广泛的不同数据集。所以,如果你没有得到你的目标,不要担心😅。

4.从 TensorFlow 开始,如果您使用的是旧版本,我们需要更新节点版本。在你熟悉了节点版本之后,让我们运行下面的命令来安装https://github.com/tensorflow/tfjs-models

npm install @tensorflow/tfjs-node

注意:您可以根据您的系统 Linux/Windows/Mac 使用—https://www.npmjs.com/package/@tensorflow/tfjs-node安装 tfjs-node

5.现在让我们安装我们将要使用的两个模型,所以运行

npm install @tensorflow-models/mobilenet — save
npm install @tensorflow-models/coco-ssd — save

6.我们需要安装下面的模块,因为需要依赖

npm install base64-to-uint8array — save

7.现在转到src > api文件夹下的index.js,创建一个新的端点

api.post('/detect_image_objects', async (req, res) => {
  const data = req.body.data;
  const type = req.body.type; const objectDetect = new ObjectDetectors(data, type);
  const results = await objectDetect.process(); res.json(results);
});

这里我们调用ObjectDetectors类并传递从 UI 接收的两个参数,一个是 base64 编码的图像,另一个是模型的类型。

8.现在让我们创建ObjectDetectors类。转到src > api文件夹并创建object_detector文件夹。在object_detector中,我们将创建一个新文件ObjectDetectors.js

const tf = require('[@tensorflow/tfjs-node](http://twitter.com/tensorflow/tfjs-node)');

const cocossd = require('[@tensorflow](http://twitter.com/tensorflow)-models/coco-ssd');
const mobilenet = require('[@tensorflow](http://twitter.com/tensorflow)-models/mobilenet');

import toUint8Array from 'base64-to-uint8array';

export default class ObjectDetectors {

   constructor(image, type) {

       this.inputImage = image;
       this.type = type;
   }

   async loadCocoSsdModal() {
       const modal = await cocossd.load({
           base: 'mobilenet_v2'
       })
       return modal;
   }

   async loadMobileNetModal() {
       const modal = await mobilenet.load({
           version: 1,
           alpha: 0.25 | .50 | .75 | 1.0,
       })
       return modal;
   }

   getTensor3dObject(numOfChannels) {

       const imageData = this.inputImage.replace('data:image/jpeg;base64','')
                           .replace('data:image/png;base64','');

       const imageArray = toUint8Array(imageData);

       const tensor3d = tf.node.decodeJpeg( imageArray, numOfChannels );

       return tensor3d;
   }

   async process() {

       let predictions = null;
       const tensor3D = this.getTensor3dObject(3);

       if(this.type === "imagenet") {

           const model =  await this.loadMobileNetModal();
           predictions = await model.classify(tensor3D);

       } else {

           const model =  await this.loadCocoSsdModal();
           predictions = await model.detect(tensor3D);
       }

       tensor3D.dispose();

      return {data: predictions, type: this.type};
   }
}

我们有一个构造函数,它有两个参数,一个是图像 base64 编码,另一个是图像类型。

调用一个调用getTensor3dObject(3).process函数

注意:这里 3 是通道的数量,因为在 UI 中,我们将图像类型限制为 jpeg,现在是 3 通道图像。我们不处理 png 的 4 通道图像,你可以很容易地建立这个,因为你可以在 API 中发送图像类型,并根据需要改变给定的函数。

getTensor3dObject(numOfChannels) {
 const imageData = this.inputImage.replace('data:image/jpeg;base64','')
           .replace('data:image/png;base64','');const imageArray = toUint8Array(imageData);const tensor3d = tf.node.decodeJpeg( imageArray, numOfChannels );return tensor3d;
}

在这个函数中,我们从 base64 图像中移除标签,将其转换为图像数组,并构建 tensor3d。

我们的预训练模型使用 tensor3d 对象或<img> HTML 标记或 HTML 视频标记,但当我们从 Node.js API 执行此操作时,我们有一个 base64 图像,它被转换为 tensor3d 对象。

欣然 tensorflow.js 为它提供了一个函数decodeJpeg

TensorFlow 还提供了其他功能,您可以查看更多详细信息—https://js.tensorflow.org/api_node/1.2.7/#node.decodeJpeg

现在decodeJpeg将把我们的 3 通道图像的ArrayBuffer转换成 tesnor3d 对象。

if(this.type === "imagenet") {
 const model =  await this.loadMobileNetModal();
 predictions = await model.classify(tensor3D);} else {
 const model =  await this.loadCocoSsdModal();
 predictions = await model.detect(tensor3D);
}

基于选择的模型类型,我们在 API 调用中加载模型。你可以在 API 开始加载的时候加载模型,但是对于这个博客,我只是在 API 得到一个调用的时候加载它们,所以 API 可能需要时间来响应。

下面是我目前为止得到的结果

IMAGENET 模型输出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

imagenet的输出它提供了物体的名称及其概率有三个物体用imagenet.标识

COCO-SSD 模型输出-

如果您了解更多关于 coco-ssd 的信息,它可以识别多个对象,即使它们是相似的。以及它们的对象所依赖的矩形坐标。

在这里阅读更多—https://github . com/tensor flow/tfjs-models/tree/master/coco-SSD

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这里你可以看到它已经确定了 6 个人,他们的位置是一个矩形。现在,您可以将这些坐标用于任何目的,因为它们会告诉您对象名称和对象位置。

您可以使用任何图像库来绘制这些矩形,围绕这些细节构建一些很酷的图像效果应用程序。

你可以试试我在 React.js 上的关于 Cloudniary 和 OpenCV 的教程,以前文章中的 Nodejs 试图使用这些知识来构建很酷的东西。

快乐编码❤️

如果您想被添加到我的电子邮件列表中,请考虑在这里输入您的电子邮件地址 和关注我的 medium 阅读更多关于 javascript 的文章,并关注github查看我的疯狂代码。如果有什么不清楚或者你想指出什么,请在下面评论。

你可能也会喜欢我的其他文章

  1. 用 Tensorflow-js 进行图像目标检测🤔
  2. Nodejs 应用程序结构——构建高度可扩展的架构。
  3. 图像处理——在 React.js 中制作自定义图像滤镜

如果你喜欢这篇文章,请随意分享并帮助他人找到它!

谢谢你!

用 OpenCV 实现图像全景拼接

原文:https://towardsdatascience.com/image-panorama-stitching-with-opencv-2402bde6b46c?source=collection_archive---------0-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图像拼接是计算机视觉中最成功的应用之一。如今,很难找到不包含此功能的手机或图像处理 API。

在这篇文章中,我们将讨论如何使用 Python 和 OpenCV 执行图像拼接。给定一对共享一些公共区域的图像,我们的目标是“缝合”它们并创建一个全景图像场景。

在整篇文章中,我们回顾了一些最著名的计算机视觉技术。其中包括:

  • 关键点检测
  • 局部不变描述符(SIFT、SURF 等)
  • 特征匹配
  • 使用 RANSAC 的单应性估计
  • 透视扭曲

我们探索了许多特征提取器,如 SIFT、SURF、BRISK 和 ORB。你可以使用这款 Colab 笔记本进行跟踪,甚至用你的图片进行尝试。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

特征检测和提取

给定一对像上面这样的图像,我们想把它们拼接起来,创建一个全景场景。需要注意的是,两个图像需要共享一些公共区域。

此外,我们的解决方案必须是健壮的,即使图片在以下一个或多个方面有差异**😗*

  • 缩放比例
  • 空间位置
  • 捕捉设备

这个方向的第一步是提取一些关键点和感兴趣的特征。然而,这些特征需要具有一些特殊的属性。

让我们首先考虑一个简单的解决方案。

关键点检测

一个最初的可能是幼稚的方法是使用算法提取关键点,比如 Harris Corners。然后,我们可以尝试基于某种相似性度量(如欧几里德距离)来匹配相应的关键点。我们知道,角点有一个很好的性质:它们对于旋转是不变的。这意味着,一旦我们检测到一个角点,如果我们旋转图像,这个角点将仍然存在。

然而,如果我们旋转然后缩放一幅图像呢?在这种情况下,我们将有一段艰难的时间,因为角落不是不变的比例。也就是说,如果我们放大一幅图像,之前检测到的角点可能会变成一条线!

总之,我们需要对旋转和缩放不变的特征。这就是 SIFT、SURF 和 ORB 等更健壮的方法的用武之地。

要点和描述符。

像 SIFT 和 SURF 这样的方法试图解决角点检测算法的局限性。通常,角点检测算法使用固定大小的核来检测图像上的感兴趣区域(角点)。很容易看出,当我们缩放图像时,这个内核可能会变得太小或太大。

为了解决这个限制,像 SIFT 这样的方法使用高斯差分(DoD)。这个想法是将 DoD 应用于同一幅图像的不同比例版本。它还使用相邻像素信息来寻找和改进关键点和相应的描述符。

*首先,我们需要加载两个图像,一个查询图像和一个训练图像。最初,我们从提取关键点和描述符开始。我们可以使用 OpenCV*detectAndCompute()函数一步完成。注意,为了使用 detectAndCompute() ,我们需要一个关键点检测器和描述符对象的实例。可以是 ORB,SIFT 或者 SURF 等。此外,在将图像输入 detectAndCompute() 之前,我们将它们转换成灰度。

我们在查询和火车图像上运行 detectAndCompute() 。此时,我们有了两幅图像的一组关键点和描述符。如果我们使用 SIFT 作为特征提取器,它为每个关键点返回一个 128 维的特征向量。如果选择 SURF,我们得到一个 64 维的特征向量。下图显示了使用 SIFT、SURF、BRISK 和 ORB 提取的一些特征。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Detection of key points and descriptors using SIFT

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Detection of key points and descriptors using SURF

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Detection of key points and descriptors using BRISK and Hamming distances.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Detection of key points and descriptors using ORB and Hamming distances.

特征匹配

如我们所见,我们从两幅图像中获得了大量特征。现在,我们想比较两组特征,并坚持使用显示更多相似性的特征对。

在 OpenCV 中,特征匹配需要一个 Matcher 对象。在这里,我们探索两种口味:

  • 强力匹配器
  • KNN(k-最近邻)

BruteForce (BF)匹配器正如其名所示。给定 2 组特征(来自图像 A 和图像 B),将 A 组中的每个特征与 B 组中的所有特征进行比较。默认情况下,BF Matcher 计算两点之间的欧几里德距离。因此,对于集合 A 中的每个特征,它返回集合 b 中最接近的特征。对于 SIFT 和 SURF,OpenCV 建议使用欧几里德距离。对于其他特征提取器,如 ORB 和 BRISK,建议使用汉明距离。****

要使用 OpenCV 创建一个 BruteForce 匹配器,我们只需要指定 2 个参数。第一个是距离度量。第二个是交叉检查布尔参数。

crossCheck bool 参数指示两个特征是否必须相互匹配才能被视为有效。换句话说,对于被认为有效的一对特征( f1,F2),f1需要匹配 f2 ,并且 f2 也必须匹配 f1 作为最接近的匹配。该过程确保了一组更稳健的匹配特征,并且在原始 SIFT 论文中有所描述。

然而,对于我们想要考虑多个候选匹配的情况,我们可以使用基于 KNN 的匹配过程。

KNN 不会返回给定要素的单个最佳匹配,而是返回 k 个最佳匹配。

注意,k 值必须由用户预先定义。正如我们所料,KNN 提供了更多的候选特性。然而,我们需要确保所有这些匹配对在进一步之前都是健壮的。

比率测试

为了确保 KNN 返回的特征具有很好的可比性,SIFT 论文的作者建议使用一种叫做比率测试的技术。基本上,我们迭代 KNN 返回的每一对,并执行距离测试。对于每一对特征( f1,f2 ),如果 f1f2 之间的距离在一定比例之内,我们保留它,否则,我们丢弃它。此外,比率值必须手动选择。

本质上,比率测试的工作与来自 BruteForce Matcher 的交叉检查选项相同。两者都确保一对检测到的特征确实足够接近以被认为是相似的。下图显示了 BF 和 KNN 匹配器在 SIFT 特征上的结果。我们选择只显示 100 个匹配点,以便清晰可视化。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Feature matching using KNN and Ration Testing on SIFT features

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Feature matching using Brute Force Matcher on SIFT features

请注意,即使在 KNN 对暴力和比率测试进行了交叉检查之后,一些特性仍然不能正确匹配。

然而,匹配器算法将从两幅图像中给我们最好的(更相似的)特征集。现在,我们需要获得这些点,并找到转换矩阵,该矩阵将基于它们的匹配点将两幅图像缝合在一起。

这种变换称为单应矩阵。简而言之,单应矩阵是一个 3×3 矩阵,可用于许多应用,如相机姿态估计、透视校正和图像拼接。单应是 2D 变换。它将点从一个平面(图像)映射到另一个平面。让我们看看我们如何得到它。

估计单应性

随机样本一致性或 RANSAC 是一种拟合线性模型的迭代算法。与其他线性回归不同,RANSAC 旨在对异常值保持稳健。

线性回归等模型使用最小二乘估计来拟合数据的最佳模型。然而,普通最小二乘法对异常值非常敏感。因此,如果离群值的数量很大,它可能会失败。

RANSAC 通过仅使用数据中的内嵌器的子集来估计参数,从而解决了这个问题。下图显示了线性回归和 RANSAC 之间的比较。首先,注意数据集包含相当多的异常值。

我们可以看到,线性回归模型很容易受到异常值的影响。这是因为它试图减少平均误差。因此,它倾向于支持最小化从所有数据点到模型本身的总距离的模型。这包括离群值。

相反,RANSAC 仅在被识别为内点的点的子集上拟合模型。

这个特征对我们的用例非常重要。这里,我们将使用 RANSAC 来估计单应矩阵。事实证明,单应性对我们传递给它的数据质量非常敏感。因此,重要的是要有一种算法(RANSAC ),能够将明显属于数据分布的点从不属于数据分布的点中过滤出来。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Comparison between Least Squares and RANSAC model fitting. Note the substantial number of outliers in the data.

一旦我们有了估计的单应性,我们需要将其中一个图像扭曲到一个公共平面上。

这里,我们将对其中一幅图像应用透视变换。基本上,透视变换可以组合一个或多个操作,如旋转、缩放、平移或剪切。想法是转换其中一个图像,使两个图像合并为一个。为此,我们可以使用 OpenCV warpPerspective() 函数。它接受一幅图像和单应性作为输入。然后,基于单应性将源图像扭曲到目的图像。

生成的全景图像如下所示。正如我们看到的,结果中有几个工件。更具体地说,我们可以看到一些与图像边界处的光照条件和边缘效应相关的问题。理想情况下,我们可以执行后处理技术来归一化亮度,如直方图匹配**。这可能会使结果看起来更真实。**

感谢阅读!

**外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传****外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Input image pair** 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Panoramic Image

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传****外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Input image pair

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Panoramic Image

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传****外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Input image pair

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Panoramic Image

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传****外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Input image pair.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Panoramic Image

使用 scikit 图像进行图像处理

原文:https://towardsdatascience.com/image-processing-using-scikit-image-cb57ce4321ed?source=collection_archive---------13-----------------------

突破图像分析的恐惧和复杂性

数据科学求职过程的一个积极方面是暴露在现实世界中的挑战和问题。一个这样的挑战是检测图像中的对象位置。这个项目可以用两种方法来处理。

  • 不使用神经网络的计算机视觉
  • 卷积神经网络的计算机视觉

对于一个不知道如何在没有神经网络的情况下进行基本图像分析的人来说,我最终在 4 天内成功完成了这个项目。这样做最关键的部分是弄清楚图像实际上是什么,以及如何处理它们。

这篇文章的重点就是要做到这一点,用 scikit image 简单地理解图像。遵循这些要点将对对计算机视觉感兴趣的初露头角的数据科学家大有裨益。

1。什么是图像?

它们只是 numpy 数组。对于数据科学家来说,搞清楚这一点可以减少一半的复杂性。

不相信我,试试这个。你将画出一幅 80 年代没有任何信号接收的电视机的图像。

import numpy as np
import matplotlib.pyplot as pltrandom_image = np.random.random([500,500])
plt.imshow(random_image, cmap = 'gray')
plt.colorbar();

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

TV set from the past

2。大致来说,图像有三层:红色、蓝色和绿色

图像只是数字的集合。您可以在真实图像上通过调整值和编辑图像来改变这些。这是一个演示,将显示图像的规模,也是它的形状。

#Checking out a color image
from skimage import data
cat = data.chelsea()print(f'Shape: {cat.shape}')
print("Values min/max", cat.min(), cat.max())
plt.imshow(cat)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意形状中的 3。这些表示红色、蓝色和绿色层。编辑这些数组中的值应该可以编辑图像。让我们来测试一下。让我们在图像上画一个正方形。

#Drawing a red square on the image
cat[10:110, 10:110, :] = [255, 0, 0] #{red, green, blue }
plt.imshow(cat)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就像一个上课无聊的学生,我只是用 python 代码丑化了一个图像。如果有人感兴趣的话,试着用一些颜色和一条金链让这只猫看起来更酷(这是一个完整的附带项目)。我们继续吧。

3。在您的系统上处理图像

学习将图像从您的系统上传到 python 环境是必不可少的,可以使用以下代码完成

from skimage import io
image = io.imread('image path')

4。将图像转换为灰度

大多数时候,灰度图像的图像处理不太复杂,用外行人的话来说就是黑白图像。浏览可以通过以下方式将彩色(红、蓝、绿)图像转换为灰度图像:

gray_image = color.rgb2gray(image)
plt.imshow(gray_image, cmap = 'gray');

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5。寻找图像的边缘

图像中对象检测和定位的一个非常关键的因素是检测其边缘的能力。让我们拍下这张一个人操作照相机的照片。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

skimage 过滤并模糊这张图片,不要太详细,但找到了这个人的轮廓。这被称为像素化。

####Code for smooth pixelation####from skimage import filters, img_as_float
import scipy.ndimage as ndi#Convolution step(Like in CNN's)smooth_mean = ndi.convolve(image, mean_kernel)
image = data.camera()
pixelated = image[::10, ::10]
pixelated_float = img_as_float(pixelated)
sigma  =1
smooth = filters.gaussian(pixelated_float, sigma)
plt.imshow(smooth);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用来自滤波器的 sobel 滤波,可以容易地找到该图像的边缘。使用下面的代码:

#Using the Sobel filter
plt.imshow(filters.sobel(pixelated_float))

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

建立在这些基础知识点上,肯定会是进入计算机视觉的一个很好的开始。通过观看和学习 2019 年 SciPy 会议,这些方法可以进入更实际的细节。它在 youtube 上免费提供,正是它教会了我基础知识。

跟随这个链接到达那里

我对每一个阅读这篇文章的读者的最终意图是消除对任何图像分析任务的恐惧和恐吓。图像只是基本的数字,这是每个数据科学家都乐于接受的一件事。

图像推荐引擎—利用迁移学习

原文:https://towardsdatascience.com/image-recommendation-engine-leverage-transfert-learning-ec9af32f5239?source=collection_archive---------12-----------------------

应用于图像推荐的预训练模型世界之旅

在本文中,我们将开发一个基于图像这种非结构化数据的推荐系统。为了有一个快速、可操作的模型,而不需要费力地微调[Convolutional neural network](https://en.wikipedia.org/wiki/Convolutional_neural_network)算法,我们将使用[Transfer Learning](https://en.wikipedia.org/wiki/Transfer_learning)

迁移学习机器学习中的一个研究问题,它专注于存储在解决一个问题时获得的知识,并将其应用于另一个不同但相关的问题。【1】例如,在学习识别汽车时获得的知识可以应用于识别卡车

为了使它更容易,我们将使用高级 API [Keras](https://keras.io/)。Keras 提供了几个预训练模型,这些模型已经证明了它们在泛化方面的价值。让我们立即开始建模吧!

我有一些关于时尚产品的数据,我在这里得到了,由于计算时间的原因,我将语料库减少到 2000 张图片。因此,正如我之前所说,Keras 提供了几个只需导入的预训练模型。

我输入了他们已经在训练的算法,现在我该怎么办?有几个初步的步骤,我们将准备我们的图像语料库并重新设计算法,以便它将返回通过训练确定的高级特征。

1.准备图片语料库

我们有一份巴布亚新几内亚的清单:

  • 对于每个 png,我们将调整它的大小
  • 把它变成三维数组
  • 在 3D 数组列表上构建矩阵
  • 用 Keras 内置函数对其进行预处理。

2.准备算法

  • 为了预测最后一个要素而不是预测的 1000 个标注的最后一层,我们需要重新设计模型以移除预测层。

首先,我们需要知道模型的输入是什么形状,并根据这个形状调整图像的大小。让我们用 VGG16 来做我们的预处理,它有如下结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

VGG16 structure model

由于我们看到输入层中的输入形状是(none,224,224,3),我们将直接从模型中提取输入中请求的大小和宽度,这样我们就不必查看每个模型的摘要。

#with vgg model (or any model above)
image_width = eval(str(vgg_model.layers[0].output.shape[1]))
image_height = eval(str(vgg_model.layers[0].output.shape[2]))

注意:由于 self . model . layers[0]. output . shape[1]的输出是tensor flow . python . framework . tensor _ shape。Dimension 对象,我将对其求值以获得整数。然后,我们加载和调整图像大小为一个 PIL。Image . Image

pil_img = load_img('image.png',  target_size=(image_width, image_height))

我们将它转换为 shape (224,224,3)数组,并使用额外的维度对其进行扩展,以适应算法所需的(none,224,224,3)输入形状。

from keras.preprocessing.image import load_img,img_to_arrayarray_img = img_to_array(pil_img)
images = np.expand_dims(array_img, axis=0)

我们可以对每个文件都这样做,然后把它变成一个矩阵。

预处理 _ 输入减去数据的平均 RGB 通道。这是因为您正在使用的模型已经在不同的数据集上进行了训练:image.shape仍然是带有堆栈和预处理 _ 输入的(1, 224, 224, 3),它变成了带有 n 个输入数(即图像数)的(n, 224, 224, 3)

现在我们的输入已经准备好了,可以放入算法中了。我们需要重新设计我们的模型,在新模型中加入预先训练的输入和输出的倒数第二层。

from keras.models import Modelfeat_extract = Model(inputs=vgg_model.input,outputs=vgg_model.layers[-2].output)

有了这个模型,我们就可以使用**。预测方法**得到的图像特征与第二层特征一样多(vgg_model 'fc2 层’为 4096)。

imgs_features = feat_extract.predict(dense_mat)

现在,我们可以计算每个图像之间的余弦相似性,以获得索引和列中有 png 的(n*n)密集矩阵,然后我们可以提出一个或多个推荐。

cosSimilarities = cosine_similarity(imgs_features)cos_similarities_df = pd.DataFrame(cosSimilarities, columns=files, index=files)

让我们将所有这些归纳起来,一次测试几个模型。

我们已经定义了一个通用类,它采用任何预训练模型,并相应地调整图像集,并针对给定的图像提出建议,让我们看看结果。

  • MobileNetV2

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 移动网络

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • InceptionResNet

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • VGG16

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

很酷不是吗!尽管我们使用了只有 2000 张图片的语料库,但我们有非常相关的推荐。

最后

我们已经看到了使用迁移学习的优势。为了有一个更准确的推荐系统,创建我们自己的卷积网络会更有意义,这将是下一篇文章的主题**,保持联系!谢了。**

此处代码:https://github.com/AlexWarembourg/Medium/blob/master/product _ recommendation . ipynb

带有 Keras 的图像推荐引擎

原文:https://towardsdatascience.com/image-recommendation-engine-with-keras-d227b0996667?source=collection_archive---------25-----------------------

建立一个 CNN 分类器,把它变成一个推荐引擎。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

https://www.cleanpng.com/png-convolutional-neural-network-natural-language-proc-3083964/

我有一些漫画数据,我甚至写了一篇文章,以便你可以收集这些数据集(有一些修改)见:https://towards data science . com/scrape-multiple-pages-with-scrapy-ea 8 EDFA 4318

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于每一部漫画,我都在海报栏里找到了他的照片的网址。我将在此基础上建立我的图像语料库

import requestsmanga["title"] = manga["title"].replace({"%" : ""}, regex=True)
manga= manga[(manga["title"] != "") & (manga["title"].notnull()) & (manga["poster"].notnull())]import requests
from tqdm import tqdmkeep_in_memory = []for url, title in tqdm(zip(manga["poster"], manga["title"])) : 
    str_name = "/home/jupyter/Untitled Folder/%s.jpg" %title
    str_name = str_name.strip()
    keep_in_memory.append(str_name)
    with open(str_name, 'wb') as f:
        f.write(requests.get(url).content)

manga["pics_ref"] = keep_in_memory

我将使用性别列作为我的图像分类器的标签。但是我之前需要做一点清理,因为我有大约 3000 个独特的标签,我会清理我的标签并减少它们。

manga["genre"] = manga["genre"].str.strip()
manga["genre"] = manga["genre"] +  " ,"import redef clean(genre, string_to_filter) :
    return re.sub('(?<={})(.*\n?)(?=,)'.format(string_to_filter), '', genre)manga["genre"] = manga["genre"].apply(lambda  x : clean(str(x), 'Comic'))
manga["genre"] = manga["genre"].apply(lambda  x : clean(str(x), 'Action'))
manga["genre"] = manga["genre"].apply(lambda  x : clean(str(x), 'Josei'))
manga["genre"] = manga["genre"].apply(lambda  x : clean(str(x), 'Shoujo'))
manga["genre"] = manga["genre"].apply(lambda  x : clean(str(x), 'Shounen'))
manga["genre"] = manga["genre"].apply(lambda  x : clean(str(x), 'Horror'))manga['genre'] = [list(map(str.strip, y)) for y in [x.split(',') for x in manga['genre']]]manga['genre'] =[','.join(x) for x in manga['genre']]my_cat = ['Action', 'Adventure', 'Comedy', 'Hentai',
          'Harem', 'Fantasy', 'Drama', 'Horror', 'Romance','Josei',
          'Fantasy', 'Seinen', 'Sci-Fi', 'Slice of Life', 'Mecha', 'Yaoi',
          'Yuri', 'Thriller', 'Comic']manga["genre"] = manga["genre"].apply(lambda z : [x for x in my_cat if x in z])
manga['genre'] =[','.join(x) for x in manga['genre']]genre = manga["genre"].str.get_dummies(',')
manga = pd.concat([manga, genre], axis = 1)

我现在有 18 个标签,我把它们编码成每个标签的二进制矩阵。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我还有另一个问题:我的标签是不平衡的,所以我们将定义一个类权重,稍后我将在我的模型中传递它。

label_cnt = [(columns, manga[columns].sum()) for columns in manga.columns[8:]]
tot = sum([x[1] for x in label_cnt])
class_weight = dict(zip([x[0] for x in label_cnt], [x[1]/tot for x in label_cnt]))

最后,我们将建立我们的输入矩阵。

train_image = []
index_to_drop = []
for i, png in tqdm(list(enumerate(manga["pics_ref"]))):
    try : 
        img = image.load_img(png,target_size=(256,256,3))
        img = image.img_to_array(img)
        img = img/255
        train_image.append(img)
    except OSError : 
        index_to_drop.append(i)manga = manga.drop(manga.index[index_to_drop])

X = np.array(train_image)
y = np.array(manga[manga.columns[8:].tolist()])from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 777)

然后,我们将定义我们的模型。不管结构如何,这里有一点很重要,即我们的预测必须通过 sigmoid 函数激活,而不是 softmax,因为否则我们将有属于这样一个标签的累积概率,或者我们想要属于一个标签的独立概率。

最后,使用 ImageDataGenerator 模块增加数据,该模块将通过更改 RGB 通道、缩放、翻转图像等方式生成新照片……

我们的模型拟合得非常准确,但它不是这项任务的完美指标。我们试试吧,省省吧。

randint = np.random.randint(1, manga.shape[0], 1)[0]
poster = manga["pics_ref"].iloc[randint]
img = image.load_img(poster, target_size=(256,256,3))
img = image.img_to_array(img)
img = img/255classes = np.array(manga.columns[8:])
proba = model.predict(img.reshape(1,256,256,3))
top_3 = np.argsort(proba[0])[:-4:-1]
for i in range(3):
    print("{}".format(classes[top_3[i]])+" ({:.3})".format(proba[0][top_3[i]]))
plt.imshow(img)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

from keras.models import model_from_json# serialize model to JSON
model_json = model.to_json()
with open("model.json", "w") as json_file:
    json_file.write(model_json)
# serialize weights to HDF5
model.save_weights("model.h5")
print("Saved model to disk")
# load json and create model
json_file = open('model.json', 'r')
loaded_model_json = json_file.read()
json_file.close()
'''
loaded_model = model_from_json(loaded_model_json)
loaded_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
'''

无论如何,我们对分类器的准确性不感兴趣,而是对训练产生的特征感兴趣,这将允许我们建立我们的推荐!首先,我们将通过删除最后一个预测层,用我们的预训练模型重建一个模型。

image_features_extractor = Model(inputs=model.input,  outputs=model.layers[-2].output)img_features = image_features_extractor.predict(X)cosSimilarities = cosine_similarity(img_features)cos_similarities_df = pd.DataFrame(cosSimilarities,
                                   columns=manga["title"],
                                   index=manga["title"])

最后,我们得到与给定海报最相似的海报。

def most_similar_to(given_img, nb_closest_images = 5):print("-----------------------------------------------------------------------")
    print("original manga:")original = load_img(given_img, target_size=(256,256,3))
    plt.imshow(original)
    plt.show()print("-----------------------------------------------------------------------")
    print("most similar manga:")closest_imgs = cos_similarities_df[given_img].sort_values(ascending=False)[1:nb_closest_images+1].index
    closest_imgs_scores = cos_similarities_df[given_img].sort_values(ascending=False)[1:nb_closest_images+1]for i in range(0,len(closest_imgs)):
        img = image.load_img(closest_imgs[i], target_size=(256,256,3))
        plt.imshow(img)
        plt.show()
        print("similarity score : ",closest_imgs_scores[i])

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 推荐

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

看起来不错!但是还可以更好!

最后

我们编写了一个卷积神经网络来对我们的漫画进行分类,然后我们检索了训练过程中产生的变量,使其成为一个推荐系统。同一个模型的几种用法相当不错不是吗!
谢谢,代码在这里:https://github.com/AlexWarembourg/Medium

使用 Python 进行图像抓取

原文:https://towardsdatascience.com/image-scraping-with-python-a96feda8af2d?source=collection_archive---------0-----------------------

使用 PYTHON 进行 WEB 抓取

学习如何用 Python 从 Google 下载图片的代码指南!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Mr Cup / Fabien Barral on Unsplash

为了训练一个模型,你需要图像。你当然可以手动下载,甚至可以批量下载,但我认为有一种更令人愉快的方式。让我们使用 Python 和一些 web 抓取技术来下载图像。

**更新 2(2020 年 2 月 25 日)😗*抓取网页的一个问题是目标元素依赖于某种类型的 a selector。我们使用css-selectors从页面中获取相关元素。谷歌似乎在过去的某个时候改变了它的网站布局,这使得它有必要更新相关的选择器。提供的脚本应该可以再次工作。

**更新:**自从写了这篇关于图像抓取的文章后,我已经发表了关于构建图像识别卷积神经网络的文章。如果你想好好利用这些图片,看看下面这篇文章吧!

[## 猫,狗,还是埃隆马斯克?

了解如何使用 Python 和 Keras 在不到 15 分钟的时间内构建您的图像识别卷积神经网络!

towardsdatascience.com](/cat-dog-or-elon-musk-145658489730)

在今天的文章中,我们将讨论以下主题:

  1. 抓取静态页面
  2. 抓取交互式页面
  3. 从谷歌抓取图片
  4. 合法性后记

先决条件

一个 Python 环境(我建议 Jupyter 笔记本)。如果你还没有设置这个,不要担心。这是毫不费力的,不到 10 分钟。

[## 所以你想成为一名数据科学家?

到底是什么阻止了你?下面是如何开始!

towardsdatascience.com](/get-started-with-python-e50dc8c96589)

①抓取静态页面

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by David Marcu on Unsplash

编写静态页面(即不使用 JavaScript 在页面上创建高度交互的页面)非常简单。静态网页就是一个用标记语言编写的大文件,它定义了内容应该如何呈现给用户。您可以非常快速地获得原始内容,而无需应用标记。假设我们想从这个维基百科页面中获取下表:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Screenshot from Wikipedia page shows country codes and corresponding names

我们可以通过利用一个名为 requests 的基本 Python 库来实现这一点,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

using requests library to download static page content

如你所见,这不是很有用。我们不想要所有的噪音,而是想只提取页面的特定元素(准确地说是表格)。这种情况下美汤就派上了用场。

Static Extraction

漂亮的 Soup 允许我们轻松地导航、搜索或修改解析树。在通过适当的解析器运行原始内容之后,我们得到了一个可爱的干净的解析树。在这个树中,我们可以使用类“wikitable sortable”搜索“table”类型的元素。您可以通过右键单击表并单击 inspect 查看源代码来获得关于类和类型的信息。然后,我们遍历该表,逐行提取数据,最终得到以下结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

parsed table from Wikipedia Page

巧妙的技巧: Pandas 有一个内置的read_html方法,通过运行pip install lxml安装 lxml (一个强大的 xml 和 HTML 解析器)后就可以使用了。read_html允许您执行以下操作:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

the second result from read_html

正如你所看到的,我们调用res[2]是因为pd.read_html()会把它找到的所有东西转储到一个单独的数据帧中,即使是松散地类似于一个表。您必须检查哪个结果数据帧包含所需的数据。对于结构良好的数据,尝试一下read_html是值得的。

②抓取互动页面

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Ross Findon on Unsplash

然而,大多数现代网页都具有很强的互动性。“单页应用程序”的概念意味着网页本身会改变,而用户不必一直重新加载或从一个页面重定向到另一个页面。因为只有在特定的用户交互之后才会发生这种情况,所以在抓取数据时几乎没有什么选择(因为这些操作必须发生)。

有时,用户操作可能会触发对公开的后端 API 的调用。在这种情况下,可以直接访问 API 并获取结果数据,而不必经过中间不必要的步骤。然而,大多数时候,你将不得不经历点击按钮、滚动页面、等待加载和所有这些步骤……或者至少你必须让网页认为正在做所有这些。硒来拯救!

Selenium 可用于自动化 web 浏览器与 Python(以及其他语言)的交互。通俗地说,selenium 假装是一个真实的用户,它打开浏览器,“移动”光标,并点击按钮,如果你告诉它这样做的话。据我所知,Selenium 背后最初的想法是自动化测试。然而,在自动化基于 web 的重复性任务方面,Selenium 同样强大。

让我们看一个例子来说明硒的用法。不幸的是,需要事先做一点准备。我将用**谷歌 Chrome 概述 Selenium 的安装和使用。**如果您想使用另一个浏览器(如 Headless ),您必须下载相应的 web 驱动程序。你可以在这里找到更多信息。

步骤:

  1. 安装谷歌浏览器(如果已经安装,跳过)
  2. 识别你的 Chrome 版本。通常通过点击“关于谷歌浏览器”找到。我目前有版本 77.0.3865.90 (我的主版本因此是 77 ,第一个点之前的数字)。
  3. 这里为你的主版本下载相应的 from 驱动程序,并将可执行文件放到一个可访问的位置(我使用Desktop/Scraping
  4. 通过pip install selenium安装 Python Selenium 包

启动网络驱动

运行下面的代码片段(为了便于演示,请在 Jupyter 笔记本上运行)并查看幽灵浏览器是如何打开的。

import selenium# This is the path I use
# DRIVER_PATH = '.../Desktop/Scraping/chromedriver 2'# Put the path for your ChromeDriver here
DRIVER_PATH = <YOUR PATH>wd = webdriver.Chrome(executable_path=DRIVER_PATH)

如果一切按计划进行,您现在应该会看到这样的内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Google Chrome browser controlled by Selenium

现在运行(在新单元格中):

wd.get('[https://google.com'](https://google.com'))

你的浏览器应该导航到——不出所料——google.com。现在运行:

search_box = wd.find_element_by_css_selector('input.gLFyf')
search_box.send_keys('Dogs')

当你的浏览器在搜索框中输入Dogs时,你就会看到结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

好吧,让我们关闭驱动程序:

wd.quit()

完美!你已经掌握了基本知识。Selenium 非常强大,几乎每个交互都可以模拟。有些动作甚至可以通过抽象方法来实现,比如点击按钮或者悬停在物体上。此外,如果出现最坏的情况,您可以通过将光标移动到您想要的位置,然后执行单击操作来模仿人类行为。

③从谷歌上抓取图片

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Bharathi Kannan on Unsplash

既然你现在明白了基本知识,我们就可以把所有的东西拼凑起来。让浏览器按照我们的要求行事:

  • 搜索特定术语并获取图片链接
  • 下载图像

搜索特定短语&获取图片链接

get_image_links.py

函数fetch_image_urls需要三个输入参数:

  1. **query**:搜索词,如**Dog**
  2. **max_links_to_fetch**:刮刀应该收集的链环数
  3. **webdriver**:实例化的 Webdriver

下载图像 为了让下面的代码片段工作,我们首先必须通过运行pip install Pillow来安装PIL

persist_image.py

persist_image功能抓取一个图片 URL url并下载到folder_path中。该函数将为图像分配一个随机的 10 位 id。

综合起来 下面的函数search_and_download结合了前面的两个函数,为我们使用 ChromeDriver 增加了一些弹性。更准确地说,我们在一个with上下文中使用 ChromeDriver,这保证了浏览器正常关闭,即使在with上下文中出现错误。search_and_download允许您指定**number_images**,默认设置为 5,但可以设置为您想要下载的任意数量的图像。

search_and_donwload.py

现在,我们可以执行以下操作:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Download some doggie images

并将获得:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

恭喜你!你已经建立了你自己的图像刮刀。考虑使用刮刀,享受你正在喝的咖啡,而不是手动下载 100 张图片。

如果您想了解如何自动执行抓取过程并持续运行,请查看以下文章:

[## 实用 Python:如何在几分钟内编写和部署一个监控+通知服务?

你曾经希望有一种服务,一旦它观察到一个特定的事件,就反复地检查和 pings 你吗?—了解如何…

towardsdatascience.com](/practical-python-how-to-write-and-deploy-a-monitoring-notification-service-within-minutes-for-free-b682cffa66ef)

哦,如果你喜欢阅读这样的故事,并想支持我成为一名作家,考虑注册成为一名灵媒成员。每月 5 美元,你可以无限制地阅读媒体上的故事。如果你用我的链接注册,我甚至会做一些🍩。

[## 通过我的推荐链接加入 Medium-Fabian Bosler

作为一个媒体会员,你的会员费的一部分会给你阅读的作家,你可以完全接触到每一个故事…

medium.com](https://medium.com/@fabianbosler/membership)

④关于网络抓取合法性的后记

我不是律师,所以我所说的一切都不应被视为法律意见。话虽如此,围绕网络抓取合法性的问题很可能要逐案评估。只要你不违反任何服务条款或对你抓取的网页产生负面影响,你就是清白的,这似乎是一个共识。网络抓取行为本身不可能违法。你可以抓取你的页面而不会有任何影响,毕竟谷歌机器人每天都在抓取整个网页。我的建议是:

确保您没有违反任何法律、服务条款或对您的目标产生负面影响。

使用 Python 的 scikit-image 模块进行图像分割。

原文:https://towardsdatascience.com/image-segmentation-using-pythons-scikit-image-module-533a61ecc980?source=collection_archive---------2-----------------------

scikit-image 库的图像分割方法概述。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

source

所有的东西迟早都是数字,包括图像。

看过 【终结者】 的人一定会同意这是那个时代最伟大的科幻电影。在电影中, 詹姆斯·卡梅隆 引入了一个有趣的视觉效果概念,使观众有可能获得被称为终结者的电子人的眼睛后面。这种效果后来被称为 终结者视觉中的一种方式,它把人类从背景中分割出来。这听起来可能完全不合时宜,但图像分割是当今许多图像处理技术的重要组成部分。**

图象分割法

我们都很清楚 Photoshop 或类似的图形编辑器提供的无限可能性,它们可以将一个人从一幅图像中取出,放入另一幅图像中。然而,这样做的第一步是 识别该人在源图像中的位置 和这是图像分割发挥作用的地方。有许多为图像分析目的而编写的库。在本文中,我们将详细讨论 scikit-image ,这是一个基于 Python 的图像处理库。

完整的代码也可以从与本文相关的 Github 资源库 中获得。

sci kit-图像

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

scikit-image.org

Scikit-image 是一个专门用于图像处理的 Python 包。

装置

scikit-image 可以按如下方式安装:

*pip install scikit-image# For Conda-based distributions
conda install -c conda-forge scikit-image*

Python 中的图像概述

在讨论图像分割的技术细节之前,有必要先熟悉一下 scikit 图像生态系统及其处理图像的方式。

  • 从浏览库导入灰度图像

skimage 数据模块包含一些内置的示例数据集,这些数据集通常以 jpeg 或 png 格式存储。

*from skimage import data
import numpy as np
import matplotlib.pyplot as pltimage = data.binary_blobs()
plt.imshow(image, cmap='gray')*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 从浏览库导入彩色图像
*from skimage import data
import numpy as np
import matplotlib.pyplot as pltimage = data.astronaut()plt.imshow(image)*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 从外部来源导入图像
*# The I/O module is used for importing the image
from skimage import data
import numpy as np
import matplotlib.pyplot as plt
from skimage import ioimage = io.imread('skimage_logo.png')plt.imshow(image);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 加载多张图片
*images = io.ImageCollection('../images/*.png:../images/*.jpg')print('Type:', type(images))
images.files
Out[]: Type: <class ‘skimage.io.collection.ImageCollection’>*
  • 保存图像
*#Saving file as ‘logo.png’
io.imsave('logo.png', logo)*

图象分割法

现在我们对 scikit-image 有了一个概念,让我们进入图像分割的细节。 图像分割 n 本质上是将数字图像分割成多个片段的过程,以简化和/或改变图像的表示,使其更有意义且更易于分析。

在本文中,我们将结合监督和非监督算法来处理分割过程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Some of the Segmentation Algorithms available in the scikit-image library

***监督分割:*可能来自人的输入的一些先验知识用于指导算法。

无监督分割:不需要先验知识。这些算法试图将图像自动细分成有意义的区域。用户可能仍然能够调整某些设置以获得期望的输出。

让我们从称为阈值处理的最简单算法开始。

阈值处理

通过选择高于或低于某个 阈值的像素,这是从背景中分割对象的最简单方法。当我们打算从背景中分割出物体时,这通常是有帮助的。你可以在这里阅读更多关于阈值的内容。

让我们在预先加载了 scikit-image 数据集的教科书图像上尝试一下。

基本进口

*import numpy as np
import matplotlib.pyplot as pltimport skimage.data as data
import skimage.segmentation as seg
import skimage.filters as filters
import skimage.draw as draw
import skimage.color as color*

一个简单的函数来绘制图像

***def** image_show(image, nrows=1, ncols=1, cmap='gray'):
    fig, ax = plt.subplots(nrows=nrows, ncols=ncols, figsize=(14, 14))
    ax.imshow(image, cmap='gray')
    ax.axis('off')
    **return** fig, ax*

图像

*text = data.page()image_show(text)*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这张图像有点暗,但也许我们仍然可以选择一个值,在没有任何高级算法的情况下,给我们一个合理的分割。现在,为了帮助我们选择这个值,我们将使用一个直方图

直方图是显示在图像中发现的不同亮度值下图像中的像素的数量的图表。简而言之,直方图是一种图表,其中 x 轴显示图像中的所有值,而 y 轴显示这些值的频率。

*fig, ax = plt.subplots(1, 1)
ax.hist(text.ravel(), bins=32, range=[0, 256])
ax.set_xlim(0, 256);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们的例子恰好是一个 8 位图像,所以我们在 x 轴上总共有 256 个可能的值。我们观察到存在相当亮的像素浓度(0:黑色,255:白色)。这很可能是我们相当轻的文本背景,但其余的是一种涂抹了。一个理想的分割直方图应该是双峰的,并且是完全分开的,这样我们就可以在中间选择一个数字。现在,让我们试着基于简单的阈值分割制作一些分段图像。

监督阈值

由于我们将自己选择阈值,我们称之为监督阈值。

*text_segmented = *text > (value concluded from histogram i.e 50,70,120 )*image_show(text_segmented);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Left: text>50 | Middle : text > 70 | Right : text >120

我们没有得到任何理想的结果,因为左边的阴影产生了问题。现在让我们尝试无监督阈值处理。

无监督阈值处理

Scikit-image 有许多自动阈值方法,在选择最佳阈值时不需要输入。一些方法有:otsu, li, local.

*text_threshold = filters.threshold_  *# Hit tab with the cursor after the underscore to get all the methods.*image_show(text < text_threshold);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传**外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Left: otsu || Right: li

local的情况下,我们还需要指定block_sizeOffset 帮助调整图像以获得更好的效果。

*text_threshold = filters.threshold_local(text,block_size=51, offset=10) 
image_show(text > text_threshold);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

local thresholding

这是非常好的,并且在很大程度上摆脱了噪声区域。

监督分割

阈值分割是一个非常基本的分割过程,在高对比度图像中无法正常工作,为此我们需要更先进的工具。

在本节中,我们将使用一个免费提供的示例图像,并尝试使用监督分割技术分割头部。

**# import the image*
from skimage import io
image = io.imread('girl.jpg') 
plt.imshow(image);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

source

在对图像进行任何分割之前,使用一些滤波器对其进行降噪是一个好主意。

但是,在我们的例子中,图像并不是很嘈杂,所以我们会照原样处理。下一步是用rgb2gray将图像转换成灰度。

*image_gray = color.rgb2gray(image) 
image_show(image_gray);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们将使用两种基于完全不同原理的分割方法。

活动轮廓分割

活动轮廓分割也称为*和使用感兴趣区域周围的用户定义轮廓或线条进行初始化,然后该轮廓缓慢收缩,并被光线和边缘吸引或排斥。*

对于我们的示例图像,让我们在人的头部周围画一个圆来初始化蛇。

***def** circle_points(resolution, center, radius): *"""*
 *Generate points which define a circle on an image.Centre refers to the centre of the circle*
 *"""*   
    radians = np.linspace(0, 2*np.pi, resolution) c = center[1] + radius*np.cos(radians)#polar co-ordinates
    r = center[0] + radius*np.sin(radians)

    **return** np.array([c, r]).T*# Exclude last point because a closed path should not have duplicate points*
points = circle_points(200, [80, 250], 80)[:-1]*

上面的计算计算了圆周上的点的 x 和 y 坐标。由于我们给定的分辨率是 200,它将计算 200 个这样的点。

*fig, ax = image_show(image)
ax.plot(points[:, 0], points[:, 1], '--r', lw=3)*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后,该算法通过将闭合曲线拟合到人脸的边缘,将人脸从图像的其余部分中分割出来。

*snake = seg.active_contour(image_gray, points)fig, ax = image_show(image)
ax.plot(points[:, 0], points[:, 1], '--r', lw=3)
ax.plot(snake[:, 0], snake[:, 1], '-b', lw=3);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以调整名为alphabeta的参数。较高的 alpha 值会使蛇收缩得更快,而 beta 值会使蛇更平滑。

*snake = seg.active_contour(image_gray, points,alpha=0.06,beta=0.3)fig, ax = image_show(image)
ax.plot(points[:, 0], points[:, 1], '--r', lw=3)
ax.plot(snake[:, 0], snake[:, 1], '-b', lw=3);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

随机行者分割

在这种方法中,用户交互式地标记少量像素,这些像素被称为标记。然后,设想每个未标记的像素释放一个随机行走体,然后可以确定随机行走体从每个未标记的像素开始并到达预先标记的像素之一的概率。通过将每个像素分配给计算出最大概率的标签,可以获得高质量的图像分割。点击阅读参考文件。

在这里,我们将重用上一个示例中的种子值。我们可以用
完成不同的初始化,但是为了简单起见,让我们坚持使用圆形。

*image_labels = np.zeros(image_gray.shape, dtype=np.uint8)*

随机漫步算法需要一个标签图像作为输入。因此,我们将有一个较大的圆圈包围着这个人的整个脸,另一个较小的圆圈靠近脸的中间。

*indices = draw.circle_perimeter(80, 250,20)#from [here](https://medium.com/p/533a61ecc980/oeed)image_labels[indices] = 1
image_labels[points[:, 1].astype(np.int), points[:, 0].astype(np.int)] = 2image_show(image_labels);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,让我们使用随机漫步机,看看会发生什么。

*image_segmented = seg.random_walker(image_gray, image_labels)# Check our results
fig, ax = image_show(image_gray)
ax.imshow(image_segmented == 1, alpha=0.3);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它看起来不像我们想要的那样抓住边缘。为了解决这种情况,我们可以调整β参数,直到我们得到想要的结果。经过几次尝试后,值 3000 工作得相当好。

*image_segmented = seg.random_walker(image_gray, image_labels, beta = 3000)# Check our results
fig, ax = image_show(image_gray)
ax.imshow(image_segmented == 1, alpha=0.3);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这就是监督分割,我们必须提供某些输入,还必须调整某些参数。然而,让一个人看着一幅图像,然后决定给出什么输入或者从哪里开始,并不总是可能的。幸运的是,对于这些情况,我们有无监督分割技术。

无监督分割

无监督分割不需要先验知识。考虑一个如此大的图像,以至于不可能同时考虑所有像素。因此,在这种情况下,无监督分割可以将图像分解成几个子区域,所以不是数百万像素,而是数十到数百个区域。让我们来看两个这样的算法:

SLIC(简单线性迭代聚类)

SLIC 算法实际上使用了一种叫做 K-Means 的机器学习算法。它接受图像的所有像素值,并试图将它们分成给定数量的子区域。在此阅读参考文件

SLIC 是彩色的,所以我们将使用原始图像。

*image_slic = seg.slic(image,n_segments=155)*

我们所做的只是将我们找到的每个子图像或子区域设置为该区域的平均值,这使得它看起来不像是随机分配的颜色的拼凑物,而更像是被分解成类似区域的图像。

**# label2rgb replaces each discrete label with the average interior color*
image_show(color.label2rgb(image_slic, image, kind='avg'));*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们将这张图片从 512512 = 262,000 像素缩减到 155 个区域。*

费尔曾兹瓦尔布

该算法还使用了一种叫做最小生成树聚类的机器学习算法。Felzenszwaib 没有告诉我们图像将被划分成多少个簇。它将运行并生成它认为适合图像上给定的
比例或缩放因子的尽可能多的集群。参考文件可在此处获取。

*image_felzenszwalb = seg.felzenszwalb(image) 
image_show(image_felzenszwalb);*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是很多地区。让我们来计算独特区域的数量。

*np.unique(image_felzenszwalb).size
3368*

现在让我们使用区域平均值对它们重新着色,就像我们在 SLIC 算法中做的那样。

*image_felzenszwalb_colored = color.label2rgb(image_felzenszwalb, image, kind='avg')image_show(image_felzenszwalb_colored);*

现在我们得到了相当小的区域。如果我们想要更少的区域,我们可以改变scale参数或者从这里开始合并它们。这种方法有时被称为过度分割

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这几乎看起来更像是一个色调分离的图像,本质上只是减少了颜色的数量。要再次组合它们,可以使用 区域邻接图(RAG ) ,但这超出了本文的范围。

基于深度学习的图像分割

到目前为止,我们只使用 scikit 图像模块研究了图像分割技术。然而,值得一提的是一些使用深度学习的图像分割技术。这里有一篇精彩的博客文章,重点介绍了图像分割架构、损失、数据集和框架,您可以将其用于您的图像分割项目。

* [## 2020 年的图像分割:架构、损失、数据集和框架

在这篇文章中,我们将使用深度学习深入研究图像分割的世界。我们将讨论:什么…

海王星. ai](https://neptune.ai/blog/image-segmentation-in-2020)*

结论

图像分割是一个非常重要的图像处理步骤。它是一个活跃的研究领域,应用范围从计算机视觉到医学成像,再到交通和视频监控。Python 以 scikit-image 的形式提供了一个健壮的库,其中包含大量用于图像处理的算法。它是免费的,没有任何限制,背后有一个活跃的社区。看看他们的文档,了解更多关于这个库及其用例的信息。

参考文献:

Scikit 图像文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值