深度篇——图像处理的一些方法(一) 关于 图像处理的三个层次 与 selective search

返回主目录

返回 图像处理的一些方法 目录

下一章:深度篇——图像处理的一些方法(二) 细说 性能评估 IOU 和 GIOU

目录内容

深度篇——图像处理的一些方法(一) 关于 图像处理的三个层次 与 selective search

深度篇——图像处理的一些方法(二) 细说 性能评估 IOU 和 GIOU

深度篇——图像处理的一些方法(三)  细说 HOG 特征 与 bag-of-word

深度篇——图像处理的一些方法(四) 细说 图像金字塔

本章节对图像处理的一些方法讲解,是为了在后面讲到图像处理时,需要的一些先验知识,以便于更好的理解。

论文地址:《Selective Search for Object Recognition

本小节,关于 图像处理的三个层次 与 selective search,下一小节细说 性能评估 IOU 和 GIOU

一. 图像处理的三个层次

1. 分类(classification)

      即是将图像结构化为某一类别的信息,用事先确定好的类别(String) 实例的 ID 来描述图像(如手写数字识别)。这一任务是最简单、最基础的图像理解任务,也是深度学习模型最先取得突破和实现大规模应用的任务。其中,ImageNet 是最权威的评测机构,每年的 ILSVRC 催生了大量的优秀深度网络结构,为其他任务提供了基成。在应用领域、人脸、场景的识别等都可以归为分类任务。

 

2. 检测(detection)

      分类任务关心整体,给出的是整张图像的内容描述,而检测则关注特定的物体目标,要求同时获得这一目标的类别信息和位置信息。相比分类,检测给出的是图像前景和背景的理解,需要从背景中分离出感兴趣的目标,并确定这一目标的描述(类别和位置),因而,检测模型的输出是一个列表,列表的每一项使用一个数据给出检测目标的类别和位置(常用矩形检测框的坐标表示)。

 

3. 分割(segmentation)

      分割包括语义分割(semantic segmentation) 和实例分割(instance segmenation),前者是对前背景分离的拓展,要求分离开具有不同语义的图像部分,而后者是检测任务的拓展,要求描述出目标的轮廓(相比检测框更为精细)。分割是对图像的像素级描述,它赋予每个像素类别(实例) 意义,适用于理解要求较高的场景,如无人驾驶中对道路和非道路的分割。

   (1). 语义分割

         只能分割出类别(如 人类、狗类 等)

   (2). 实例分割

         可以将每一个具体的实例分割出来(如分割出每一个人,每一条狗等),实现了目标检测和语义分割。

   (3). 全景分割

         包含了语义分割和实例分割。

二. selective search 选择性搜索

目标检测(Object Detection) 的目的是为了减少不必要因素的干扰(比如 从背景很负责的图像中识别出一个小动物,那样的准确率会大大的降低),此外还能同时得到目标在图像中的位置(还包括 宽度和高度)。当决定要进行 Object Detection 的时候,大家的想法都差不多的,滑动窗口扫描嘛,不同大小的窗口从左到右,从上到下的,一顿操作。不用想,这样是超级浪费时间的,绝大部分的扫描都是没用的,因为框住的根本不是目标。所以继续想,要是能直接框住图像中的目标,而不是那些无用的背景,而且也不需要框得特别准确。有了这一思想,于是人们想出了 selective search (选择性搜索)

selective search 的思想:

1. 区域相似度计算 

   在计算多种相似度的时候,都是把单一相似度的值归一化到 [0, 1] 之间,1 表示两个区域之间的相似度最大。

   (1). 颜色相似度

        首先通过利用颜色的相似度(毕竟如果颜色很相似,那么极有可能是一个整体),形成一些小的区域,具体算法是将整张图看成一个无向图,节点之间的距离就是像素的距离(在这里使用 RGB 计算距离并不好,最好转换一下颜色空间,如 HSV 等)。使用 \large L_{1} - norm 归一化获取图像每个像素通道的 25 bins 的直方图,这样每个区域都可以得到一个 75 维的向量 \large \{c_{i, 1}, c_{i, 2}, ......, c_{i, n} \},区域之间颜色相似度通过下面的公式计算:

                 \LARGE S_{color}(r_{i}, r_{j}) = \sum_{k = 1}^{n} \min (c_{i, k}, c_{j, k})

         由于 \large \{c_{i, 1}, c_{i, 2}, ......, c_{i, n}\} 是归一化后的值,每一个颜色通道的直方图累加和为 1.0,三个通道的累加和就是 3.0,如果区域 \large c_{i} 和区域 \large c_{j} 直方图完全一样,则此时颜色相似度最大,为 3.0;如果不一样,由于累加取两个区域 bin 的最小值进行累加,当直方图差距越大,累加的和就会越小,即颜色相似度越小。在区域合并过程中需要对新的区域进行计算其直方图,计算方法:

                  \LARGE c_{t} = \frac{size(r_{i}) \times c_{i} + size(r_{j}) \times c_{j}}{size(r_{i}) + size(r_{j})}

 

   (2). 纹理相似度

         这里的纹理采用 SIFT-Like 特征。具体做法是对每个颜色通道的 8 个不同方向计算方差 \large \sigma = 1 的高斯微分(Gaussian Derivative),使用 \large L_{1}-norm 归一化获取图像每个颜色通道的每个方向的 10 bins 的直方图,这样就可以获得一个 240 (10 x 8 x 3) 维的向量 \large T_{i} = \{t_{i, 1}, t_{i, 2}, ......,, t_{i, n}\},区域之间纹理相似度计算方式和颜色相似度计算方式类似,合并之后新区域的纹理特征计算方式饿颜色特征计算相同:

                \LARGE S_{texture} (r_{i}, r_{j}) = \sum_{k = 1}^{n} \min (t_{i, k}, t_{j, k})

 

   (3). 尺寸相似度 (优先合并小的区域)

         如果仅仅是通过颜色和纹理特征合并的话,很容易使得合并后的区域不断吞并周围的区域,后果就是多尺度只应用在了那个区域,而不是全局的多尺度。因此,可以给小的区域更多的权重,这样保证在图像每个位置都是多尺度的合并:

                 \LARGE S_{size} (r_{i}, r_{j}) = 1 - \frac{size(r_{i}) + size(r_{j})}{size(im)}

                 \large size(im):是指区域中的尺寸,以像素为单位。

          上面的公司表示,两个区域越小,其相似度越大,越接近 1.0。保证合并操作的尺度较为均匀,避免一个大区域陆续 “吃掉” 其他的小区域

           如:设 有区域  \large a-b-c-d-e-f-g-f

                  较好的合并方式为:

                      \large ab-cd-ef-gh \Rightarrow abcd -efgh \Rightarrow abcdefgh

                   不好的合并方式为:

                      \large ab-c-d-e-f-g-h \Rightarrow abcd - e - f-g-h \Rightarrow abcdef - gh \Rightarrow abcdefgh

 

   (4). 交叠相似度

         如果区域 \large r_{i} 包含在 \large r_{j} 内,首先应该合并,另一方面,如果 \large r_{i} 很难与 \large r_{j} 相接,它们之间会形成断崖,不应该合并在一块。这里定义区域的合适度距离主要是为了衡量两个区域是否 “吻合”,其指标是合并后的区域的 bounding boxes (能够框住区域的最小矩形 \large bb_{ij}) 越小,其吻合度越高,即相似度越接近 1.0。其计算公式:

              \large S(r_{i}, r_{j}) = a_{1} S_{colour}(r_{i}, r_{j}) + a_{2} S_{texture}(r_{i}, r_{j}) + a_{3} S_{size}(r_{i}, r_{j}) + a_{4} S_{fill} (r_{i}, r_{j})

         其中 \large a_{i} \in \{0, 1 \}

 

selective search 的迭代流程:

 

2. 通过相似度形成最初的感兴趣区域(Region Of Interest, ROI),然后利用相似度作为合并标准,重复这个步骤,直到最终合并为一个区域,而在这个合并过程中,得到的大大小小的区域的边界框,都将作为 bounding boxes 候选框。

 

 

由于,现在基本不用此方法,就不去代码演示了。知道有这么一回事,就好了。毕竟,这是比较古典的玩法。

 

后来想了想,还是把代码演示 带一遍吧。不然,很多人,还是可能会懵逼的。

github 代码:selective_search_pro

使用的演示图像

 

README.md 文件

# selective_search_pro
selective search 代码演示 2020/02/24 15:47
- 此处就一个代码文件,直接右键运行看效果即可。
- 具体,没什么太多可说的,代码里面有很多注释。

## 环境依赖
- pip install numpy==1.16
- pip install selectivesearch
- pip install matplotlib
- conda install pillow

文件层级结构

代码文件

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# ============================================
# @Time     : 2020/02/24 15:47
# @Author   : WanDaoYi
# @FileName : selective_search.py
# ============================================

from datetime import datetime
import numpy as np
import skimage.segmentation as seg
import skimage.util as util
import skimage.color as color
import skimage.feature as feature
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from PIL import Image


class SelectiveSearch(object):

    def __init__(self):

        self.colour_bins = 25
        self.texture_bins = 10
        pass

    def selective_search(self, image_orig, scale=1.0, sigma=0.8, min_size=50):
        # selective search 处理的是灰度图,单通道。如果不是 3 通道则拦截。
        # 如果是 PIL 的 Image.open() 读取的图像,则要用下面的方法判断 3 通道
        assert len(image_orig.split()) == 3, "3ch image is expected"
        # 如果是 cv2 的格式图像的话,直接 shape 就好。后面的处理 cv2 图像需要转成 PIL 图像
        # assert image_orig.shape[-1] == 3, "3ch image is expected"

        # 获取分割后的 4 通道 图像
        image_info = self.generate_segment(image_orig, scale=scale, sigma=sigma,
                                           min_size=min_size)

        if image_info is None:
            return None, {}

        # 获取 regions
        reg = self.extract_regions(image_info)

        # 提取相邻区域信息
        neighbours = self.extract_neighbours(reg)

        # 计算图像的大小
        h, w = image_info.shape[: 2]
        image_size = h * w

        # 相似度计算初始化
        similar = {}
        for (ai, ar), (bi, br) in neighbours:
            similar[(ai, bi)] = self.calc_similar(ar, br, image_size)

        # 层级查找
        while similar != {}:

            # 获取最高的相似度
            i, j = sorted(similar.items(), key=lambda i: i[1])[-1][0]

            # 合并相应的区域
            top_key = max(reg.keys()) + 1.0
            # 区域合并
            reg[top_key] = self.merge_regions(reg[i], reg[j])

            # 对需要移除的相似性区域进行标记
            key_to_delete = []
            for k, v in list(similar.items()):

                if (i in k) or (j in k):
                    key_to_delete.append(k)
                pass

            # 移除旧相似度的相关区域
            for k in key_to_delete:
                del similar[k]

            # 计算新区域的相似度
            for k in [a for a in key_to_delete if a != (i, j)]:
                n = k[1] if k[0] in (i, j) else k[0]
                similar[(top_key, n)] = self.calc_similar(reg[top_key], reg[n], image_size)
                pass
            pass

        reg_list = []
        for k, r in list(reg.items()):
            reg_list.append({"rect": (r["min_x"],
                                      r["min_y"],
                                      r["max_x"] - r["min_x"],
                                      r["max_y"] - r["min_y"]),
                             "size": r["size"],
                             "labels": r["labels"]
                             })
            pass

        return image_info, reg_list
        pass

    # 区域合并
    def merge_regions(self, r1, r2):
        new_size = r1["size"] + r2["size"]

        merge_reg = {"min_x": min(r1["min_x"], r2["min_x"]),
                     "min_y": min(r1["min_y"], r2["min_y"]),
                     "max_x": max(r1["max_x"], r2["max_x"]),
                     "max_y": max(r1["max_y"], r2["max_y"]),
                     "size": new_size,
                     "hist_c": (r1["hist_c"] * r1["size"] + r2["hist_c"] * r2["size"]) / new_size,
                     "hist_t": (r1["hist_t"] * r1["size"] + r2["hist_t"] * r2["size"]) / new_size,
                     "labels": r1["labels"] + r2["labels"]
                     }
        return merge_reg

    # 相似度计算
    def calc_similar(self, r1, r2, image_size):

        return (self.similar_colour(r1, r2) + self.similar_texture(r1, r2)
                + self.similar_size(r1, r2, image_size)
                + self.similar_fill(r1, r2, image_size))
        pass

    # 计算颜色直方图相交的和
    def similar_colour(self, r1, r2):
        return sum([min(a, b) for a, b in zip(r1["hist_c"], r2["hist_c"])])
        pass

    # 计算纹理直方图相交的和
    def similar_texture(self, r1, r2):
        return sum([min(a, b) for a, b in zip(r1["hist_t"], r2["hist_t"])])
        pass

    # 计算尺寸相似的图像
    def similar_size(self, r1, r2, image_size):
        return 1.0 - (r1["size"] + r2["size"]) / image_size
        pass

    # 计算交叠相似的图像
    def similar_fill(self, r1, r2, image_size):
        width = max(r1["max_x"], r2["max_x"]) - min(r1["min_x"], r2["min_x"])
        high = max(r1["max_y"], r2["max_y"]) - min(r1["min_y"], r2["min_y"])
        bbox_size = width * high
        return 1.0 - (bbox_size - r1["size"] - r2["size"]) / image_size
        pass

    # 图像分割
    def generate_segment(self, image_orig, scale, sigma, min_size):
        # 计算 Felsenszwalb 的基于有效图的图像分割。
        # 使用基于图像网格的快速,最小生成树聚类生成多通道(即RGB)图像的过度分割。
        # 该参数scale设置观察级别。规模越大意味着越来越小的部分。
        # sigma是高斯核的直径,用于在分割之前平滑图像。
        # 生产环节的数量及其规模只能通过scale间接控制。
        # 根据局部对比度不同,图像中的分段大小可能会有很大差异。
        # 图像:(宽度,高度,3)或(宽度,高度)ndarray输入图像。scale:float可用参数。
        # 较高意味着较大的群集 sigma:float预处理中使用的高斯内核的宽度。
        # min_size:int最小组件大小。使用后处理强制执行。
        # 多通道:bool,可选(默认值:True)图像的最后一个轴是否被解释为多个通道。
        # 对于3D图像,False的值目前不受支持。
        # segment_mask :(宽度,高度)ndarray整数掩码,指示段标签
        segment_mask = seg.felzenszwalb(util.img_as_float(image_orig),
                                        scale=scale, sigma=sigma,
                                        min_size=min_size
                                        )

        # 根据通道数 concatenation, 增加 1 个 0 值 的通道
        # image_mask.shape[:2] 获取 high 和 width 大小
        image_info = np.append(image_orig,
                               np.zeros(segment_mask.shape[:2])[:, :, np.newaxis],
                               axis=2)

        # 将上面 felzenszwalb 分割得到的图像 放入到 新增的 0 值 通道当中
        image_info[:, :, 3] = segment_mask

        # 返回 4 通道的图像
        return image_info
        pass

    # 提取 regions
    def extract_regions(self, image_info):

        # regions dic
        reg = {}
        # 第一步,计算像素的位置
        for number, pixel in enumerate(image_info):

            for i, (r, g, b, l) in enumerate(pixel):
                # 初始化一个新的 region
                if l not in reg:
                    reg[l] = {"min_x": 0xffff, "min_y": 0xffff,
                              "max_x": 0, "max_y": 0,
                              "labels": [l]}

                # bounding box
                if reg[l]["min_x"] > i:
                    reg[l]["min_x"] = i

                if reg[l]["min_y"] > number:
                    reg[l]["min_y"] = number

                if reg[l]["max_x"] < i:
                    reg[l]["max_x"] = i

                if reg[l]["max_y"] < number:
                    reg[l]["max_y"] = number

            pass

        # 第二步,计算 texture gradient
        tex_grad = np.zeros((image_info.shape[0], image_info.shape[1], image_info.shape[2]))
        # 基于高斯导数的选择性搜索算法对 8 个方向进行计算,这里使用 LBP 替代。
        for colour_channel in (0, 1, 2):
            tex_grad[:, :, colour_channel] = feature.local_binary_pattern(image_info[:, :, colour_channel],
                                                                          8, 1.0)

        # 第三步,计算每个 region 的 colour histogram
        # 将 rgb 转为 hsv
        image_hsv = color.rgb2hsv(image_info[:, :, :3])
        for k, v in list(reg.items()):
            masked_pixels = image_hsv[:, :, :][image_info[:, :, 3] == k]
            reg[k]["size"] = len(masked_pixels / 4)
            reg[k]["hist_c"] = self.calc_colour_hist(masked_pixels)

            # texture histogram
            reg[k]["hist_t"] = self.calc_texture_hist(tex_grad[:, :][image_info[:, :, 3] == k])
            pass

        return reg
        pass

    # 计算颜色直方图
    def calc_colour_hist(self, image_info):
        hist = np.array([])
        # 处理 (0, 1, 2) 通道的数据
        for colour_channel in range(3):
            # 提取 一个颜色通道
            c = image_info[:, colour_channel]
            # 计算每种颜色的直方图并加入结果中
            hist = np.concatenate([hist] + [np.histogram(c, self.colour_bins, (0.0, 255.0))[0]])

        # L1 norm
        hist = hist / len(image_info)

        return hist
        pass

    # 计算每个 region 的纹理直方图
    def calc_texture_hist(self, image_info):
        hist = np.array([])
        # 处理 (0, 1, 2) 通道的数据
        for colour_channel in range(3):
            # 彩色通道掩膜
            c = image_info[:, colour_channel]
            # 计算直方图的每个方向,并将结果 拼接起来。
            hist = np.concatenate([hist] + [np.histogram(c, self.texture_bins, (0.0, 1.0))[0]])
            pass

        # L1 norm
        hist = hist / len(image_info)
        return hist
        pass

    # 提取 相邻区域信息
    def extract_neighbours(self, regions):
        reg = list(regions.items())
        neighbours = []
        for cur, dic_a in enumerate(reg[: -1]):
            for dic_b in reg[cur + 1:]:
                if self.neighbour_flag(dic_a[1], dic_b[1]):
                    neighbours.append((dic_a, dic_b))

        return neighbours
        pass

    def neighbour_flag(self, dic_a, dic_b):
        flag_1 = dic_a["min_x"] < dic_b["min_x"] < dic_a["max_x"]
        flag_2 = dic_a["min_y"] < dic_b["min_y"] < dic_a["max_y"]

        flag_3 = dic_a["min_x"] < dic_b["max_x"] < dic_a["max_x"]
        flag_4 = dic_a["min_y"] < dic_b["max_y"] < dic_a["max_y"]

        if (flag_1 and flag_2) or (flag_3 and flag_4) or (flag_1 and flag_4) or (flag_3 and flag_2):
            return True

        return False
        pass


if __name__ == "__main__":
    # 代码开始时间
    start_time = datetime.utcnow()
    print("开始时间: ", start_time)

    image_input_path = "./dataset/images/Monica.png"
    image_output_path = "./output/images/Monica.png"
    # 读取图像
    image_orig = Image.open(image_input_path)

    demo = SelectiveSearch()

    image_info, reg_list = demo.selective_search(image_orig, scale=400.0, sigma=0.9, min_size=10)
    print("ok")
    print(reg_list[2])

    bounding_box = set()
    for reg in reg_list:
        # 如果是相同的矩形框,则跳过
        if reg["rect"] in bounding_box:
            continue

        # 不包含小于 2k pixels 的区域
        if reg["size"] < 2000:
            continue

        # 筛除 长宽比过大的矩形框
        x, y, w, h = reg["rect"]
        if w / h > 1.2 or h / w > 1.2:
            continue

        bounding_box.add(reg["rect"])
        pass

    # 在原始图像上绘制矩形框
    fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6))
    ax.imshow(image_orig)
    # # 或者 使用下面图像的 前面 3 个通道
    # # 如果展示的时候全是白光,那就是数值太大,除以 255 标准化后,可以将图像清晰展示
    # ax.imshow(image_info[:, :, :3] / 255)

    for x, y, w, h in bounding_box:
        print(x, y, w, h)
        rect = mpatches.Rectangle((x, y), w, h, fill=False,
                                  edgecolor="red", linewidth=1)
        ax.add_patch(rect)

    # 保存图像
    # plt.savefig(image_output_path)

    plt.show()

    # 代码结束时间
    end_time = datetime.utcnow()
    print("结束时间: ", end_time, ", 训练模型耗时: ", end_time - start_time)
    pass

 

selective search 效果

                  

返回主目录

返回 图像处理的一些方法 目录

下一章:深度篇——图像处理的一些方法(二) 细说 性能评估 IOU 和 GIOU

发布了63 篇原创文章 · 获赞 16 · 访问量 5977

猜你喜欢

转载自blog.csdn.net/qq_38299170/article/details/104433623
今日推荐