基于opencv实现模块化图像处理管道

重磅干货,第一时间送达

在这篇文章中,我们将学习如何为图像处理实现一个简单的模块化管道,我们将使用 OpenCV 进行图像处理和操作,并使用 Python 生成器进行管道步骤。

图像处理管道是一组按预定义顺序执行的任务,用于将图像转换为所需的结果或提取一些有趣的特征。

任务示例可以是:

  • 图像转换,如平移、旋转、调整大小、翻转和裁剪,

  • 图像的增强,

  • 提取感兴趣区域(ROI),

  • 计算特征描述符,

  • 图像或对象分类,

  • 物体检测,

  • 用于机器学习的图像注释,

最终结果可能是一个新图像,或者只是一个包含一些图像信息的JSON文件。

假设我们在一个目录中有大量图像,并且想要检测其中的人脸并将每个人脸写入单独的文件。此外,我们希望有一些 JSON 摘要文件,它告诉我们在何处找到人脸以及在哪个文件中找到人脸。我们的人脸检测流程如下所示:

人脸检测流程

这是一个非常简单的例子,可以用以下代码总结:

    import cv2import osimport jsonimport numpy as np
    def parse_args(): import argparse
    # Parse command line arguments ap = argparse.ArgumentParser(description="Image processing pipeline") ap.add_argument("-i", "--input", required=True, help="path to input image files") ap.add_argument("-o", "--output", default="output", help="path to output directory") ap.add_argument("-os", "--out-summary", default=None, help="output JSON summary file name") ap.add_argument("-c", "--classifier", default="models/haarcascade/haarcascade_frontalface_default.xml", help="path to where the face cascade resides")
    return vars(ap.parse_args())
    def list_images(path, valid_exts=None): image_files = [] # Loop over the input directory structure for (root_dir, dir_names, filenames) in os.walk(path): for filename in sorted(filenames): # Determine the file extension of the current file ext = filename[filename.rfind("."):].lower() if valid_exts and ext.endswith(valid_exts): # Construct the path to the file and yield it file = os.path.join(root_dir, filename) image_files.append(file)
    return image_files
    def main(args): os.makedirs(args["output"], exist_ok=True)
    # load the face detector detector = cv2.CascadeClassifier(args["classifier"])
    # list images from input directory input_image_files = list_images(args["input"], (".jpg", ".png"))
    # Storage for JSON summary summary = {}
    # Loop over the image paths for image_file in input_image_files: # Load the image and convert it to grayscale image = cv2.imread(image_file) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # Detect faces face_rects = detector.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=5, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE) summary[image_file] = {} # Loop over all detected faces for i, (x, y, w, h) in enumerate(face_rects): face = image[y:y+w, x:x+h]
    # Prepare output directory for faces output = os.path.join(*(image_file.split(os.path.sep)[1:])) output = os.path.join(args["output"], output) os.makedirs(output, exist_ok=True)
    # Save faces face_file = os.path.join(output, f"{i:05d}.jpg") cv2.imwrite(face_file, face)
    # Store summary data summary[image_file][face_file] = np.array([x, y, w, h], dtype=int).tolist()
    # Display summary print(f"[INFO] {image_file}: face detections {len(face_rects)}")
    # Save summary data if args["out_summary"]: summary_file = os.path.join(args["output"], args["out_summary"]) print(f"[INFO] Saving summary to {summary_file}...") with open(summary_file, 'w') as json_file: json_file.write(json.dumps(summary))
    if __name__ == '__main__': args = parse_args() main(args)

    用于人脸检测和提取的简单图像处理脚本

    代码中的注释也很有探索性,让我们来深入研究一下。首先,我们定义命令行参数解析器(第 6-20 行)以接受以下参数:

    --input:这是包含我们图像的目录的路径(可以是子目录),这是唯一的强制性参数。

    --output: 保存管道结果的输出目录。

    --out-summary:如果我们想要一个 JSON 摘要,只需提供它的名称(例如 output.json)。

    --classifier:用于人脸检测的预训练 Haar 级联的路径

    接下来,我们定义list_images函数(第 22-34 行),它将帮助我们遍历输入目录结构以获取图像路径。对于人脸检测,我们使用称为Haar级联(第 40 行)的 Viola-Jones 算法,在深度学习和容易出现误报(在没有人脸的地方报告人脸)的时代,这是一种相当古老的算法。

    来自电影“老友记”的示例图像,其中存在一些误报

    主要处理循环如下:我们遍历图像文件(第 49行),逐个读取它们(第 51 行),检测人脸(第 55 行),将它们保存到准备好的目录(第 59-72 行)并保存带有人脸坐标的摘要报告(第 78-82 行)。

    准备项目环境:

      $ git clone git://github.com/jagin/image-processing-pipeline.git$ cd image-processing-pipeline$ git checkout 77c19422f0d7a90f1541ff81782948e9a12d2519$ conda env create -f environment.yml$ conda activate pipeline

      为了确保你们的代码能够正常运行,请检查你们的切换分支命令是否正确:

      77c19422f0d7a90f1541ff81782948e9a12d2519

      让我们运行它:

      $ python process_images.py --input assets/images -os output.json

      我们得到了一个很好的总结:

      [INFO] assets/images/friends/friends_01.jpg: face detections 2
      [INFO] assets/images/friends/friends_02.jpg: face detections 3
      [INFO] assets/images/friends/friends_03.jpg: face detections 5
      [INFO] assets/images/friends/friends_04.jpg: face detections 14
      [INFO] assets/images/landscapes/landscape_01.jpg: face detections 0
      [INFO] assets/images/landscapes/landscape_02.jpg: face detections 0
      [INFO] Saving summary to output/output.json...

      每个图像的人脸图像(也有误报)存储在单独的目录中。

        output├── images│ └── friends│ ├── friends_01.jpg│ │ ├── 00000.jpg│ │ └── 00001.jpg│ ├── friends_02.jpg│ │ ├── 00000.jpg│ │ ├── 00001.jpg│ │ └── 00002.jpg│ ├── friends_03.jpg│ │ ├── 00000.jpg│ │ ├── 00001.jpg│ │ ├── 00002.jpg│ │ ├── 00003.jpg│ │ └── 00004.jpg│ └── friends_04.jpg│ ├── 00000.jpg│ ├── 00001.jpg│ ├── 00002.jpg│ ├── 00003.jpg│ ├── 00004.jpg│ ├── 00005.jpg│ ├── 00006.jpg│ ├── 00007.jpg│ ├── 00008.jpg│ ├── 00009.jpg│ ├── 00010.jpg│ ├── 00011.jpg│ ├── 00012.jpg│ └── 00013.jpg└── output.json

        摘要文件output.json将包含人脸的坐标(x、y、宽度、高度):

        {
         "assets/images/friends/friends_01.jpg": {
           "output/images/friends/friends_01.jpg/00000.jpg": [
             434,
             121,
             154,
             154
           ],
           "output/images/friends/friends_01.jpg/00001.jpg": [
             571,
             145,
             192,
             192
           ]
         },
         ...
        }

        上面的例子并不复杂,只有几个步骤,所以很容易快速创建一个简单的脚本,但很快它就会变得复杂。

        在其中一个项目中,我正在研究步态识别,管道包含以下步骤:

        • 捕捉视频

        • 检测人员

        • 估计人的姿势

        • 跟踪姿势

        • 创建蒙版

        • 缓冲区掩码序列

        • 编码步态

        • 识别步态嵌入

        • 显示结果

        还有更多用于数据注释、指标生成等。

        当我们的管道不断增长,但是不只是有我们在处理它时,问题就会开始出现。还有其他队友在做不同的步骤,管道的某些部分可以在其他管道中重复使用(例如读取图像、捕获视频等)。

        我们需要管道是模块化的!我们还需要一种巧妙的方式在管道的步骤之间传递数据。在寻找解决方案时,我偶然发现了一个很好的代码片段,它允许我们使用 Python 生成器创建类似Unix 的管道。

          #! /usr/bin/env python
          class Pipeline(object): def __init__(self): self.source = None def __iter__(self): return self.generator() def generator(self): while True: value = self.source.next() if self.filter(value): yield self.map(value) def __or__(self, other): other.source = self.generator() return other def filter(self, value): return True def map(self, value): return value

          class AllNumbers(Pipeline): def generator(self): value = 0 while True: yield value value += 1

          class Evens(Pipeline): def filter(self, value): return value % 2 == 0

          class MultipleOf(Pipeline): def __init__(self, factor=1): self.factor = factor super(MultipleOf, self).__init__() def filter(self, value): return value % self.factor == 0

          class Printer(Pipeline): def map(self, value): print value return value

          class First(Pipeline): def __init__(self, total=10): self.total = total self.count = 0 super(First, self).__init__() def map(self, value): self.count += 1 if self.count > self.total: raise StopIteration return value

          def main(): all_numbers = AllNumbers() evens = MultipleOf(2) multiple_of_3 = MultipleOf(3) printer = Printer() first_10 = First(10) pipeline = all_numbers | evens | multiple_of_3 | first_10 | printer for i in pipeline: pass

          if __name__ == '__main__': main()

          下面这个简单的例子创建了一个Pipeline,来打印前 10 个既是 3 的倍数又是偶数的数字。

          $ python example_pipeline.py
          0
          6
          12
          18
          24
          30
          36
          42
          48
          54

          最重要和最有趣的部分是Pipeline生成器类本身:

            class Pipeline(object): def __init__(self): self.source = None
            def __iter__(self): return self.generator()
            def generator(self): while self.has_next(): data = next(self.source) if self.source else {} if self.filter(data): yield self.map(data)
            def __or__(self, other): other.source = self.generator() return other
            def filter(self, data): return True
            def map(self, data): return data
            def has_next(self): return True

            生成器函数允许我们声明一个行为类似于迭代器的函数,即它可以在 for 循环中使用。换句话说,生成器是一个函数,它返回一个我们可以迭代的对象(迭代器)(一次一个值)。

            Pipeline是一个抽象类,它包含generator函数(第8-12行),默认情况下,agenerator函数通过filter函数(第18-19行)和map函数传递数据(来自上一个生成器)。

            filter函数允许我们过滤通过管道的数据(如Even上面代码片段中的类)。map函数使我们能够像在第一类中一样操作(映射)管道数据或更新步骤的状态。

            通过覆盖或运算符,可以创建类似 Unix 的管道:

            load_images | detect_faces | save_faces | display_summary

            管道的第一步必须生成我们的输入数据,因此我们必须覆盖generator函数。在我们的例子中,输入数据是要处理的图像列表,让我们将加载图像部分解耦到名为LoadImages的管道步骤中:

              import cv2
              from pipeline.pipeline import Pipelineimport pipeline.utils as utils
              class LoadImages(Pipeline): def __init__(self, src, valid_exts=(".jpg", ".png")): self.src = src self.valid_exts = valid_exts
              super(LoadImages, self).__init__()
              def generator(self): source = utils.list_images(self.src, self.valid_exts) while self.has_next(): image_file = next(source) image = cv2.imread(image_file)
              data = { "image_file": image_file, "image": image }
              if self.filter(data): yield self.map(data)

              管道生成器步骤加载图像

              我们可以看到,generator会为在src目录中找到的每个图像文件生成以下字典结构:

              data = {
                 "image_file": image_file,
                 "image": image
              }

              对于每个文件,我们都获得了图像文件的路径和文件的二进制文件。

              使用面向对象的编程,我们可以扩展LoadIamges类,并在需要过滤掉文件名或路径中包含选定单词的图像文件时重写filter函数。

              下一步是检测人脸:

                import cv2
                from pipeline.pipeline import Pipeline
                class CascadeDetectFaces(Pipeline): def __init__(self, classifier): # load the face detector self.detector = cv2.CascadeClassifier(classifier)
                super(DetectFaces, self).__init__()
                def map(self, data): image = data["image"]
                # Detect faces gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) face_rects = self.detector.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=5, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE) data["face_rects"] = face_rects
                return data

                检测人脸步骤

                它将使用map函数中源生成器(load_images)的数据,提取图像二进制文件(第14行),检测人脸(第17–18行),并使用人脸坐标(第20行)丰富数据,以便下一步操作。

                我们可以将整个管道包含在以下main函数中:

                  import os
                  from pipeline.load_images import LoadImagesfrom pipeline.cascade_detect_faces import CascadeDetectFacesfrom pipeline.save_faces import SaveFacesfrom pipeline.save_summary import SaveSummaryfrom pipeline.display_summary import DisplaySummary
                  def parse_args(): import argparse
                  # Parse command line arguments ap = argparse.ArgumentParser(description="Image processing pipeline") ap.add_argument("-i", "--input", required=True, help="path to input image files") ap.add_argument("-o", "--output", default="output", help="path to output directory") ap.add_argument("-os", "--out-summary", default=None, help="output JSON summary file name") ap.add_argument("-c", "--classifier", default="models/haarcascade/haarcascade_frontalface_default.xml", help="path to where the face cascade resides")
                  return vars(ap.parse_args())
                  def main(args): # Create pipeline steps load_images = LoadImages(args["input"]) detect_faces = CascadeDetectFaces(args["classifier"]) save_faces = SaveFaces(args["output"]) if args["out_summary"]: summary_file = os.path.join(args["output"], args["out_summary"]) save_summary = SaveSummary(summary_file) display_summary = DisplaySummary()
                  # Create image processing pipeline pipeline = load_images | detect_faces | save_faces if args["out_summary"]: pipeline |= save_summary pipeline |= display_summary
                  # Iterate through pipeline for _ in pipeline: pass
                  if args["out_summary"]: print(f"[INFO] Saving summary to {summary_file}...") save_summary.write()
                  if __name__ == '__main__': args = parse_args() main(args)

                  其中,单个步骤的逻辑是分离的,主要功能是干净整洁的,功能与文章开头的脚本相同。

                  处理管道的模块化使我们可以在视频处理中重用CascadeDetectFaces类:

                    python process_video_pipeline.py -i assets/videos/faces.mp4 -ov faces.avi -p 12%|██████████████████████████ ██████▋ | 71/577 [00:08<01:12, 6.99it/s]

                    具有以下示例结果:

                    CascadeDetectFaces并不完美,但我们可以使用cv2.dnnOpenCV 中的一些深度学习模型和模块创建另一个实现,这将更准确,更容易在管道中替换它。

                    (0)

                    相关推荐

                    • 使用sklearn做自然语言处理-2

                      本文聚焦于特征工程(feature engineering)和其他步骤,如特征抽取(feature extraction).构建流水线(pipeline,很多翻译成油管,我个人觉得流水线似乎更准确). ...

                    • 【视频讲解】Scrapy递归抓取简书用户信息

                      好久没有录制实战教程视频,大邓就在圣诞节后直接上干货. 之前写过一期[视频教程-用python批量抓取简书用户信息]的文章,是自己造的轮子,今天我趁着刚入门scrapy和xpath,操刀重写这个任务. ...

                    • 开源云原生CI/CD框架Tekton国内部署方式

                      Tekton 是一款功能非常强大而灵活的 CI/CD 开源的云原生框架.致力于提供全功能.标准化的云原生 CI/CD 解决方案.[本文主要是通过流水线自动化的将tekton镜像同步到腾讯云仓库,并部署 ...

                    • Python游戏编程(Pygame)

                      安装Pygame pip install pygame 1 C:\Users> pip install pygame Collecting pygame Downloading https:// ...

                    • 用 Python 写个坦克大战

                      来源:Python 技术「ID: pythonall」 坦克大战是一款策略类的平面射击游戏,于 1985 年由 Namco 游戏公司发布,尽管时至今日已经有了很多衍生类的游戏,但这款游戏仍然受到了相当 ...

                    • Python实现单例模式的5种方式

                      写在前面 学究嘛,就记录一下; 本质都是通过设置一个标志位来实现, 通俗的讲就是当第一次实例化时, 记录下"已经实例化了", 当再次实例化时, 将"记录"的地址 ...

                    • (4条消息) 10分钟学会使用YOLO及Opencv实现目标检测(上)|附源码

                      计算机视觉领域中,目标检测一直是工业应用上比较热门且成熟的应用领域,比如人脸识别.行人检测等,国内的旷视科技.商汤科技等公司在该领域占据行业领先地位.相对于图像分类任务而言,目标检测会更加复杂一些,不 ...

                    • python threads,threading的用法

                      python threads,threading的用法

                    • 基于OpenCV实战的图像处理:色度分割

                      重磅干货,第一时间送达 通过HSV色阶使用彩色图像可以分割来分割图像中的对象,但这并不是分割图像的唯一方法.为什么大多数人偏爱色度而不是RGB / HSV分割? 可以获得RGB / HSV通道之间的比 ...

                    • 基于OpenCV的视频处理管道

                      重磅干货,第一时间送达 目前可依靠模块化方式实现图像处理管道,检测一堆图像文件中的人脸,并将其与漂亮的结构化JSON摘要文件一起保存在单独的文件夹中. 让我们对视频流也可以进行同样的操作.为此,我们将 ...

                    • 基于OpenCV的实用图像处理操作

                      重磅干货,第一时间送达 图像处理适用于图像和视频.良好的图像处理结果会为后续的进一步处理带来很大的帮助,例如提取到图像中的直线有助于对图像中物体的结构进行分析,良好的特征提取会优化深度学习的结果等.今 ...

                    • 基于system verilog的图像处理验证平台 bmp文件解析

                      版权所有:转载请注明 https://blog.csdn.net/jayash/article/details/79947314 基于FPGA的图像处理中,rtl代码的仿真验证一直是重中之重, 之前也 ...

                    • 基于OpenCV实战:车牌检测

                      重磅干货,第一时间送达 拥有思维导图或流程将引导我们朝着探索和寻找实现目标的正确道路的方向发展.如果要给我一张图片,我们如何找到车牌并提取文字? 一般思维步骤: 识别输入数据是图像. 扫描图像以查看由 ...

                    • 基于OpenCV的实战:轮廓检测(附代码解析)

                      重磅干货,第一时间送达 利用轮廓检测物体可以看到物体的各种颜色,在这种情况下放置在静态和动态物体上.如果是统计图像,则需要将图像加载到程序中,然后使用OpenCV库,以便跟踪对象. 每当在框架中检测到 ...

                    • 基于OpenCV实战:绘制图像轮廓(附代码)

                      重磅干货,第一时间送达 山区和地形图中海拔高的区域划出的线称为地形轮廓,它们提供了地形的高程图.这些线条可以手动绘制,也可以由计算机生成.在本文中,我们将看到如何使用OpenCV在简单图像上绘制轮廓线 ...

                    • 基于OpenCV实战:动态物体检测

                      重磅干货,第一时间送达 最近,闭路电视安全系统运行着多种算法来确保安全,例如面部识别,物体检测,盗窃检测,火灾警报等.我们在运动检测的基础上实现了许多算法,因为在空闲帧上运行所有这些进程没有任何意义. ...

                    • 基于OpenCV实战:对象跟踪

                      重磅干货,第一时间送达 介绍 跟踪对象的基本思想是找到对象的轮廓,基于HSV颜色值. 轮廓:突出显示对象的图像片段.例如,如果将二进制阈值应用于具有(180,255)的图像,则大于180的像素将以白色 ...