Python CV基础

导言:在这篇文章中讲了一些经常在CV会经常用到的一些python中的工具。

迭代器

  • __iter__():迭代器,生成迭代对象时调用,返回值必须是对象自己,然后for可以循环调用next方法
  • next():每一次for循环都调用该方法(必须存在)

疑问:为什么list类型不是迭代器,但是其能放入for循环中遍历呢?
解答:迭代环境会优先使用迭代协议(也就是调用next方法),如果对象不支持迭代协议,则使用索引方式进行迭代,所以list可以被遍历。

生成器

如果一个def的主体包含yield,这个函数会自动变成一个生成器(即使它包含一个return)。生成器函数返回生成器的迭代器“生成器的迭代器”通常就被称作“生成器”。要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法(method),其中一个就是next()。如同迭代器一样,我们可以使用next()函数来获取下一个值。 而yield就是自带的next()函数:

外面调用生成器->调用该对象的next()函数->找到上一次yield出去的地方->继续执行yield后面的代码

lambda

lambda的一般形式是关键字lambda后面跟一个或多个参数,紧跟一个冒号,以后是一个表达式。lambda是一个表达式而不是一个语句。它能够出现在Python语法不允许def出现的地方。作为表达式,lambda返回一个值(即一个新的函数)。lambda用来编写简单的函数,而def用来处理更强大的任务。

1
2
f = lambda x, y, z :x + y + z
print f(1,2,3) #6

lambda后面的参数(:之前的)是从外界接受的参数,然后对其进行:后的运算。

4.python 继承

参考博客

在子类中必须要执行父类的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class B(A):
def __init__(self):
self.b = 10 #这样就是错的因为没有执行父类的构造函数

class B(A):
"""docstring for B"""
def __init__(self, arg):
super(B, self).__init__() #使用super调用父类构造 super(类名,self).__init__()
self.arg = arg

##或者使用父类名字.__init()___
def __init__(self,arg):
A.__init__()
self.arg = arg

import

__import__() 函数用于动态加载类和函数

1
__import__(mod_str)

源码中首先使用importclass的方法,传入文件名以及类名。
然后使用`_import
`来加载类。只是申明并未实例化。

得到的processors是一个字典,包含了两个类

1
2
processors['recognition'] = import_class('processor.recognition.REC_Processor')
processors['demo'] = import_class('processor.demo.Demo')

得到类对象:

1
2
Processor = processors[arg.processor]
p = Processor(sys.argv[2:])

processors中存储了两个类,只不过这两个类还并未实例化。
p = Processor(sys.argv[2:])实例化了此类。

import()的一个小实例:

文件T20190801中:

1
2
3
4
5
6
7
8
9
10
11

def import_model(self,str):
mod_str, _sep, class_str = str.rpartition('.')
__import__(mod_str)
return getattr(sys.modules[mod_str], class_str)

def start(self):
train_x,train_y,test_x,test_y = self.makedata()
Model = (self.import_model("T20190801_Model.Model"))
model = Model()
print(model)

__import__(mod_str)中只有传入文件名称就可以导入该文件了.

getattr(sys.modules[mod_str], class_str) 可以得到class_str这个类,相当于类的申明。

可以使用这个申明来实例化类对象

文件T20190801_Model中:

1
2
3
4
5
6
7
8
9
10
11
12
13

class Model(torch.nn.Module):
def __init__(self):
super(Model,self).__init__()
self.classification = nn.Sequential(
nn.Conv2d(1, 20, 5),
nn.ReLU(),
nn.Conv2d(20, 64, 5),
nn.ReLU()
)

def forward(self):
pass

rpartition

1
mod_str, _sep, class_str = import_str.rpartition('.')

rpartition() 方法类似于 partition() 方法,只是该方法是从目标字符串的末尾也就是右边开始搜索分割符。
如果字符串包含指定的分隔符,则返回一个3元的元组,第一个为分隔符左边的子串,第二个为分隔符本身,第三个为分隔符右边的子串

实例:

1
2
3
4
5
6
7

#!/usr/bin/python

str = "www.runoob.com"

print str.rpartition(".")
# ('www.runoob', '.', 'com')

parse 以及 subparsers:

每一个subparser可以继承父类的parse

1
2
3
4
# add sub-parser
subparsers = parser.add_subparsers(dest='processor')
for k, p in processors.items():
subparsers.add_parser(k, parents=[p.get_parser()])

k是键,{"recognition" "demo"},即parser的名称,注意后面使用parse_args()时所输入的子parser器名称必须要和其一样。不然会报错。详细见下面例子
实例:

1
2
3
python3 main.py recognition -h #正确调用,因为parser有名为recognition的parse

python3 main.py sdeqds -h #错误,找不到名为sdeqds子parse。

p是值,包含了两个类。

p.get_parser()可以进入到该类的get_parser方法中

在对应的类中,get_parser都会执行。

subparsers = parser.add_subparsers(dest='processor')得到的subparsers可以有多个parser,相当于parser子parsers句柄。向其中加入parser使用add_parser即可

parser.add_subparsers(dest=’processor’)返回的是子parser的句柄!!

使用add_parser()向subparsers中加入parser

add_parser(“名字”,parent = “父parser”)。

recognition类的get_parser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def get_parser(add_help=False):

# parameter priority: command line > config > default
parent_parser = Processor.get_parser(add_help=False)
parser = argparse.ArgumentParser(
add_help=add_help,
parents=[parent_parser],
description='Spatial Temporal Graph Convolution Network')

# region arguments yapf: disable
# evaluation
parser.add_argument('--show_topk', type=int, default=[1, 5], nargs='+', help='which Top K accuracy will be shown')
# optim
parser.add_argument('--base_lr', type=float, default=0.01, help='initial learning rate')
parser.add_argument('--step', type=int, default=[], nargs='+', help='the epoch where optimizer reduce the learning rate')
parser.add_argument('--optimizer', default='SGD', help='type of optimizer')
parser.add_argument('--nesterov', type=str2bool, default=True, help='use nesterov or not')
parser.add_argument('--weight_decay', type=float, default=0.0001, help='weight decay for optimizer')
# endregion yapf: enable

return parser

调用处:subparsers.add_parser(k, parents=[p.get_parser()])
返回了一个parser
这个parser继承了Processor的parser,并且添加了自己的参数。

各个类的父子关系:

1
2
3
|IO----|----demo
|
|----processor----|----REC_Processor

subparser的作用可以复用相同的参数接口

所有parser写完后调用根parse进行解析:

  1. parser = argparse.ArgumentParser(description='Processor collection')

  2. subparsers = parser.add_subparsers(dest='processor'),添加子解析器

  3. arg = parser.parse_args(),开启解析,定义了所有参数之后,你就可以给 parse_args() 传递一组参数字符串来解析命令行。默认情况下,参数是从 sys.argv[1:] 中获取,但你也可以传递自己的参数列表。选项是使用GNU/POSIX语法来处理的,所以在序列中选项和参数值可以混合。

parse_args() 的返回值是一个命名空间,包含传递给命令的参数。该对象将参数保存其属性,因此如果你的参数 destmyoption,那么你就可以args.myoption来访问该值。

  1. 可以自己向parse_args中传递参数:
    1
    parser.parse_args(['-a', '-bval', '-c', '3'])

如果不parse_args()不加参数则是默认从sys.argv[1:]来传入

argparse.add_argument() dest参数的意义:

subparsers = parser.add_subparsers(dest='processor')

dest指定的值用作key值,从解析后的对象中取出用户输入的第一个参数

所以上述的parser拥有一个arg.processor的属性。而这个属性对应了cmd中第一个输入值。

python 中 三元表达式:

如果 i = 0 a为1,否则a = 10

1
2
3
i = 1
a = 1 if i == 0 else 10
print(a)

zip的用法:

将两个list横向组合,每一个小组和为一个元组,放进list中。
也可以解压,但是解压是在解压target前面加一个*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

a = [1,2,3]
b = [4,5,6]
c = [4,5,6,7,8]
zipped = zip(a,b) # 打包为元组的列表

## [(1, 4), (2, 5), (3, 6)]
zip(a,c) # 元素个数与最短的列表一致
## [(1, 4), (2, 5), (3, 6)]
zip(*zipped) # 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式

## [(1, 2, 3), (4, 5, 6)]

nums = ['flower','flow','flight']
print(list(zip(*nums)))
# [('f', 'f', 'f'), ('l', 'l', 'l'), ('o', 'o', 'i'), ('w', 'w', 'g')]

numpy中concatenate函数:

将两个数组进行连接,前提是两个array,在拼接方向上满足形状一致即可。

切片操作:

通常一个切片操作要提供三个参数 [start_index: stop_index: step]

  • 可以省略start_index:[:5:2]意思为从数组头开始,到下标5结束每隔2个单位取一个,[5]不包括。

  • 可以省略stop_index:[1::2]意思为从1开始到数组结尾,每隔两个取一个。

  • 可以省略step:[1:6:] step = 1

  • 可以[::1] -> 从头到尾步长为1遍历

  • 可以[::-1] -> 步长为-1时,当步长<0时,start_index 默认值为-1,stop_index-len(a)

  • 可以[:-1:] -> 从头到最后一个元素依次遍历

    1
    2
    3
    data_numpy[0, frame_index, :, m] = pose[0::2] #pose的偶数index的值
    data_numpy[1, frame_index, :, m] = pose[1::2] #pose的奇数index的值
    data_numpy[2, frame_index, :, m] = score

python OpenCV

video的读取操作:

cv2.VideoCapture()函数:

1
2
3
4
cap = cv2.VideoCapture(0)
VideoCapture()中参数是0,表示打开笔记本的内置摄像头。
cap = cv2.VideoCapture("…/1.avi")
VideoCapture("…/1.avi"),表示参数是视频文件路径则打开视频。

cap.isOpened()函数:

返回true表示成功,false表示不成功

ret,frame = cap.read()函数:

cap.read()按帧读取视频,其中ret是布尔值,如果读取帧是正确的则返回True,如果文件读取到结尾,它的返回值就为False。frame就是每一帧的图像numpy_array类型

resize

1
frame = cv2.resize(frame,(360,256))

circle

it 是元组类型(x,y)

1
cv2.circle(frame,it,3,(0,0,255),3)

json文件加载:

1
2
3
output_path = "/home/joey/datasets/hmdb/hmdb51_sta/pullup_json/50_pull_ups_made_in_germany_pullup_f_nm_np1_le_med_2.json"
with open(output_path, 'r') as f:
video_info = json.load(f)

读出来后,对json的操作就和字典的操作一模一样。