3 从零开始设计计算机视觉软件
本章导航:
- 说明如何使用 GitHub 创建一个项目。
- 使用 Python 如何创建一个 bbox 处理的工具。
本章需要的背景知识:
- 了解 Git 与 vscode。
- 熟悉 Python。
- Shell(命令行命令) 基本命令,本书均以
$
开头用于标识。
6.1 创建一个项目
本章尽可能地从零基础说明如何在 https://github.com 创建一个项目,并在本地进行项目开发。
创建项目的步骤很简单:
第 1 步:在 GitHub 注册一个账号。
第 2 步:基于您自己的 GitHub 账户,创建一个项目。
- 进入您自己的 GitHub 主页,比如:https://github.com/xinetzone,点击页面右上角的
,选择
New repository
:
![](https://img.haomeiwen.com/i1114626/42fcbff4d27a848d.png)
- 填写必需项目信息:
![](https://img.haomeiwen.com/i1114626/ab945b08b2721c31.png)
表单上的 Initialize this repository with a README
需要✔,.gitignore
选择您在项目中需要使用的编程语言,比如,Python
,license
选择一个您需要的即可。
如果您想要将此项目打造为一个社区,可以参考我的博客:构建属于自己的项目[1]。至此,您完成了项目的创建工作。
6.2 项目的准备工作
在您的电脑磁盘上创建一个名为 projects
的文件夹,然后使用 vscode 打开该文件夹。接着,创建一个终端(快捷键 Ctrl+shift+`),并使用 git clone URL
克隆您的项目:
![](https://img.haomeiwen.com/i1114626/15a49bd0ec0a5770.png)
转到项目目录,编辑 README.md
文件,通过网站 https://img.shields.io 为您的项目添加图标信息,比如:
## 数字图像处理
[![GitHub issues](https://img.shields.io/github/issues/xinetzone/image)](https://github.com/xinetzone/image/issues) [![GitHub forks](https://img.shields.io/github/forks/xinetzone/image)](https://github.com/xinetzone/image/network) [![GitHub stars](https://img.shields.io/github/stars/xinetzone/image)](https://github.com/xinetzone/image/stargazers) [![GitHub license](https://img.shields.io/github/license/xinetzone/image)](https://github.com/xinetzone/image/blob/master/LICENSE) ![GitHub repo size](https://img.shields.io/github/repo-size/xinetzone/image)
显示的效果如下:
![](https://img.haomeiwen.com/i1114626/016284d6caed1da5.png)
下面就以我自己的项目 image[2] 为例展示如何开发项目。
6.3 开发一个小工具:bbox
对于目标检测任务,总是会涉及到对边界框(Bounding Box,简称 bbox)的处理,因而,开发一个专门处理边界框的 API 是十分有必要的。在创建小工具 bbox 之前,我们需要了解一些 Python 的基础知识并创建一个数学的向量实例。
6.3.1 创建数学中的“向量”
Python 中存在一个十分强大的标准库:dataclass
。dataclass
的定义位于 PEP-557,一个 dataclass 是指“一个带有默认值的可变的 namedtuple”,广义的定义就是有一个类,它的属性均可公开访问,可以带有默认值并能被修改,而且类中含有与这些属性相关的类方法,那么这个类就可以称为 dataclass
,再通俗点讲,dataclass
就是一个含有数据及操作数据方法的容器。
关于 dataclass
的更多精彩内容可参阅 dataclasses[3] 与 typing[4](详细的介绍可参考:Python3 之类型注解[5])。
为了更好的说明 dataclass
的魅力。先从“整点”开始的定义开始。即定义点 为
。先看看 typing 的方法如何定义?
from typing import Any
class Point:
def __init__(self, name: str, x: int) -> Any:
self.x = x # 坐标值
self.name = name # 名称
def __repr__(self) -> Any:
print("name={self.name}, x={self.x}")
再看看 dataclass
如何定义?
from dataclasses import dataclass
@dataclass
class Point:
name: str
x: int
是不是清爽很多?
在数学中一般使用向量来表示点。即设 ,则可以使用
代表点
。这里的
被称为点或者向量的纬度。我们使用 Python 实现向量的构建工作。
from typing import Sequence
from dataclasses import dataclass
import numpy as np
@dataclass
class Vector:
'''
数学中的向量
'''
name: str
# 矢量的分量
components: Sequence[float]
@property
def toArray(self):
return np.asanyarray(self.components)
def __len__(self):
'''
向量的维度
'''
return len(self.components)
def __add__(self, other):
'''
向量加法运算
'''
assert len(self) == len(other), "向量的维度不相同"
out = self.toArray + other.toArray
return out
def __sub__(self, other):
'''
向量减法运算
'''
assert len(self) == len(other), "向量的维度不相同"
out = self.toArray - other.toArray
return out
def scale(self, scalar):
'''
向量的数乘运算
'''
return scalar * self.toArray
def __mul__(self, other):
'''
向量的内积运算
'''
assert len(self) == len(other), "向量的维度不相同"
return np.dot(self.toArray, other.toArray.T)
@property
def norm(self):
'''
计算模长
'''
# S = sum(component**2 for component in self.components)
mod = self * self
return np.sqrt(mod)
def __getitem__(self, index):
'''
向量的索引与切片
'''
return self.components[index]
将该代码保存到 app/vector.py
文件之中。
下面看一个实例:
a = Vector('a', range(2,7))
a1 = Vector('a1', range(5,10))
a2 = Vector('a2', range(7,12))
a + a1, a - a1, a.scalar(4), a.norm
创建了 3 个向量 ,接着,分别计算其运算:向量加法,减法,数乘以及模。输出结果:
(array([ 7, 9, 11, 13, 15]),
array([-3, -3, -3, -3, -3]),
array([ 8, 12, 16, 20, 24]),
9.486832980505138)
6.3.2 编写 box.py
代码
Vector
定义了“点”,下面我们需要定义:有向线段 。使用 Python 定义很简单:
x1 = Vector('x1', [1, 2])
x2 = Vector('x2', [3, 4])
LS = Vector('x2 - x1', x2 - x1) # 线段 x2 - x1
则点 与
之间的线段,可以使用
进行表示。这样一来,以
与
连接的线段为对角线的矩形框便由
与
唯一确定。
由于大多数情况,矩形框是二维平面图形,所以,接下来我们仅仅考虑二维向量生成的矩形框。使用 Python 可以这样定义:
from dataclasses import dataclass
from typing import Sequence
import numpy as np
from vector import Vector
@dataclass
class Rectangle:
box: Sequence[Vector]
def __post_init__(self):
self.w, self.h = np.abs(self.box[1] - self.box[0])
def __lt__(self, size):
'''
判断矩形的尺寸是否是小于 size
'''
w_cond = self.w < size # 宽是否小于 size
h_cond = self.h < size # 高是否小于 size
cond = w_cond or h_cond # 宽或者高是否均小于 size
return cond
def __ge__(self, size):
min_size = self < size
return not min_size
@property
def area(self):
'''
计算矩形面积
'''
return self.w * self.h
该代码保存在 /app/image/box.py
之中。Rectangle
完成了矩形边界框的基础构建工作。同时提供了宽、高、面积计算以及判断是否为最小边界框的实现。
我们依然看一个例子:
设存在一个矩形框 ,其中,
,
分别表示矩形框的左上角、右下角坐标。这样,利用
Rectangle
便可定义此矩形框:
a1 = Vector('a_1', [2, 3])
a2 = Vector('a_1', [7, 13])
bbox = Rectangle([a1, a2])
接着,可以计算 bbox 的面积、高、
以及宽:
bbox.area, bbox.h, bbox.w
输出结果是:
(66, 10, 5)
至此,一个简单的边界框模型搭建完成了。