点击上方“小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达

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

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

任务示例可以是:

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

  • 图像的增强,

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

  • 计算特征描述符,

  • 图像或对象分类,

  • 物体检测,

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

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

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

人脸检测流程

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

import cv2
import os
import json
import numpy as npdef parse_args():import argparse# Parse command line argumentsap = 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 structurefor (root_dir, dir_names, filenames) in os.walk(path):for filename in sorted(filenames):# Determine the file extension of the current fileext = filename[filename.rfind("."):].lower()if valid_exts and ext.endswith(valid_exts):# Construct the path to the file and yield itfile = os.path.join(root_dir, filename)image_files.append(file)return image_filesdef main(args):os.makedirs(args["output"], exist_ok=True)# load the face detectordetector = cv2.CascadeClassifier(args["classifier"])# list images from input directoryinput_image_files = list_images(args["input"], (".jpg", ".png"))# Storage for JSON summarysummary = {}# Loop over the image pathsfor image_file in input_image_files:# Load the image and convert it to grayscaleimage = cv2.imread(image_file)gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# Detect facesface_rects = detector.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=5,minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE)summary[image_file] = {}# Loop over all detected facesfor i, (x, y, w, h) in enumerate(face_rects):face = image[y:y+w, x:x+h]# Prepare output directory for facesoutput = os.path.join(*(image_file.split(os.path.sep)[1:]))output = os.path.join(args["output"], output)os.makedirs(output, exist_ok=True)# Save facesface_file = os.path.join(output, f"{i:05d}.jpg")cv2.imwrite(face_file, face)# Store summary datasummary[image_file][face_file] = np.array([x, y, w, h], dtype=int).tolist()# Display summaryprint(f"[INFO] {image_file}: face detections {len(face_rects)}")# Save summary dataif 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 pythonclass Pipeline(object):def __init__(self):self.source = Nonedef __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 otherdef filter(self, value):return Truedef map(self, value):return valueclass AllNumbers(Pipeline):def generator(self):value = 0while True:yield valuevalue += 1class Evens(Pipeline):def filter(self, value):return value % 2 == 0class MultipleOf(Pipeline):def __init__(self, factor=1):self.factor = factorsuper(MultipleOf, self).__init__()def filter(self, value):return value % self.factor == 0class Printer(Pipeline):def map(self, value):print valuereturn valueclass First(Pipeline):def __init__(self, total=10):self.total = totalself.count = 0super(First, self).__init__()def map(self, value):self.count += 1if self.count > self.total:raise StopIterationreturn valuedef 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 | printerfor i in pipeline:passif __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 = Nonedef __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 otherdef filter(self, data):return Truedef map(self, data):return datadef 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 cv2from pipeline.pipeline import Pipeline
import pipeline.utils as utilsclass LoadImages(Pipeline):def __init__(self, src, valid_exts=(".jpg", ".png")):self.src = srcself.valid_exts = valid_extssuper(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 cv2from pipeline.pipeline import Pipelineclass CascadeDetectFaces(Pipeline):def __init__(self, classifier):# load the face detectorself.detector = cv2.CascadeClassifier(classifier)super(DetectFaces, self).__init__()def map(self, data):image = data["image"]# Detect facesgray = 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_rectsreturn data

检测人脸步骤

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

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

import osfrom pipeline.load_images import LoadImages
from pipeline.cascade_detect_faces import CascadeDetectFaces
from pipeline.save_faces import SaveFaces
from pipeline.save_summary import SaveSummary
from pipeline.display_summary import DisplaySummarydef parse_args():import argparse# Parse command line argumentsap = 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 stepsload_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 pipelinepipeline = load_images | detect_faces | save_facesif args["out_summary"]:pipeline |= save_summarypipeline |= display_summary# Iterate through pipelinefor _ in pipeline:passif 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 中的一些深度学习模型和模块创建另一个实现,这将更准确,更容易在管道中替换它。

好消息,小白学视觉团队的知识星球开通啦,为了感谢大家的支持与厚爱,团队决定将价值149元的知识星球现时免费加入。各位小伙伴们要抓住机会哦!

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。

下载2:Python视觉实战项目52讲

在「小白学视觉」公众号后台回复:Python视觉实战项目即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。

下载3:OpenCV实战项目20讲

在「小白学视觉」公众号后台回复:OpenCV实战项目20讲即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

基于opencv实现模块化图像处理管道相关推荐

  1. Java服务器部署基于OpenCV的C++图像处理项目(三)图片上传并返回处理图

    Java服务器部署基于OpenCV的C++图像处理项目(三)图片上传并返回处理图 1.上传图片并返回灰度图功能 由于使用的springboot开发,直接写一个upload接口供图片上传,以下是spri ...

  2. 基于Opencv的数字图像处理技巧(python)

    图像处理(image processing),用计算机对图像进行分析,以达到所需结果的技术.又称影像处理.图像处理一般指数字图像处理.数字图像是指用工业相机.摄像机.扫描仪等设备经过拍摄得到的一个大的 ...

  3. 基于OpenCV的视频处理管道

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 目前可依靠模块化方式实现图像处理管道,检测一堆图像文件中的人脸,并 ...

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

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 通过HSV色阶使用彩色图像可以分割来分割图像中的对象,但这并不是分 ...

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

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 图像处理适用于图像和视频.良好的图像处理结果会为后续的进一步处理带 ...

  6. 基于opencv的c++图像处理(图像滤波)

    前言 基于opencv的c++接口,实现常用的图像滤波方法,包括了均值滤波.中值滤波.高斯滤波.双边滤波.高通滤波以及低通滤波. 相关的opencv接口解析 CV_EXPORTS_W void blu ...

  7. 基于opencv的c++图像处理(霍夫直线检测与最小二乘法直线拟合)

    前言 基于opencv的c++接口,实现标准的霍夫直线检测.基于统计概率的霍夫直线检测.以及最小二乘法直线拟合. 相关的opencv接口解析 CV_EXPORTS_W void HoughLines( ...

  8. 基于opencv使用人工图像处理算法实现车道偏离报警(LDW)

    今天讲一下车道偏离报警的整体算法实现,但是不能把项目中的所有代码都搬过来,毕竟商用几万台车的代码,要是让公司看到估计会有小麻烦.不过我还是找了一些demo放上来分析,这样才好玩. 首先,这次讲的ldw ...

  9. 实战10:基于opencv的数字图像处理:边缘检测 (完整代码+详细教程)

    给出"离散拉普拉斯算子"一般形式的数学推导 离散值的导数使用差分代替: 所以: 以(x, y)为中心点,在水平和垂直方向上应用拉普拉斯算子,滤波器(对应a=1的情况)为:

最新文章

  1. ActivityRouter 框架简单实用
  2. mysql declare 赋值_sql server和mysql变量赋值的区别 以及 MySql Declare(转)
  3. MFC中设备描述表dc的使用
  4. python在列表末尾删除一个_从链接列表的尾部移除(Python)
  5. android SQL 语句
  6. Windows Server 2008 RemoteApp---发布应用程序
  7. c语言汇编混编,c语言与汇编混编写法
  8. css_selector定位总结
  9. 【learn】learn1
  10. numpy - np.append
  11. 手把手学ROR三——Rails的代码方式
  12. Android动态生成答题卡,好分数网怎么制作答题卡
  13. 数据结构 09-链表概念及常见链表结构
  14. HD钱包(身份钱包)简介
  15. openjudge 1.9.14 铺地毯
  16. rk3288-android8.1-OV13850
  17. html如何防止内部撑开,CSS3 流式图片的设置,避免图片撑开所在的容器(附样例)...
  18. DB2表空间状态列表
  19. 怎么将视频配套讲义打印出来
  20. Linux-如何查看进程和关闭进程

热门文章

  1. AI大佬“互怼”:Bengio和Gary Marcus隔空对谈深度学习发展现状
  2. 刷爆了!这项技术BAT力捧!程序员:我彻底慌了...
  3. ​“后进生”数学学科上演完美逆袭,成最具竞争力专业之一
  4. 习惯了收听虾米酷狗网易云音乐的你,好歹知道一下音乐推荐到底是咋回事吧
  5. 妈呀!GitChat 发布 1.9 版本更新公告,这个红色简直亮瞎了我的眼...
  6. 稳~阿里程序员常用的 15 款开发者工具
  7. 让Python代码简洁的实用技巧!
  8. Github热榜:2021年33篇最酷AI论文综述!多位华人作者入选
  9. 中科院博士返乡卖汉服:3个月卖三百万,高定一件3.5万
  10. 字节跳动算法刷题宝典.pdf