> 文章列表 > AutoCV第六课:Python基础

AutoCV第六课:Python基础

AutoCV第六课:Python基础

目录

  • Python基础
    • 注意事项
    • 一、2023/4/12更新
    • 前言
    • 1.作用域(scope)
      • 1.1 作用域的划分
      • 1.2 作用域内符号的查看
      • 1.3 符号的查找原则
    • 2.模块和包管理
      • 2.1 模块、包、脚本的概念
      • 2.2 import
        • 2.2.1 import语法
        • 2.2.2 import package的处理
        • 2.2.3 相对和绝对导入
    • 3.第三方库
      • 3.1 numpy
      • 3.2 cv2
      • 3.3 matplotlib
    • 4.Dataset和DataLoader
      • 4.1 MNIST
      • 4.2 拓展-MSB和LSB
      • 4.3 Dataset和DataLoader
    • 5.argparse模块
      • 5.1 命令行参数
      • 5.2 argparse解析参数
      • 5.3 argparse处理的参数类型
    • 6.作业
      • 6.1 作业9
        • 6.1.1 创建目录结构
        • 6.1.2 准备文件
        • 6.1.3 编写核心代码
        • 6.1.4 生成分发档案
        • 6.1.5 发布包到PyPI
        • 6.1.6 验证发布的包
      • 6.2 作业10
      • 6.3 作业11
    • 总结

Python基础

注意事项

一、2023/4/12更新

新增argparse模块解析命令行参数

前言

手写AI推出的全新保姆级从零手写自动驾驶CV课程,链接。记录下个人学习笔记,仅供自己参考。
本次课程主要学习Python中的作用域以及第三方库和包
课程大纲可看下面的思维导图。

在这里插入图片描述

1.作用域(scope)

作用域就是一个Python程序可以直接访问命名空间的区域

在一个python程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误

python中,程序的变量并不是在哪个为止都可以访问的,访问权限取决于这个变量是在哪里赋值

1.1 作用域的划分

如何区分、划分作用域?

  • 思想:同一时期运行的代码,必定属于同一作用域
  • 作用域的层级是可以嵌套的

Python中有以下四种作用域:

  • L (Local scope):本地作用域处于最内层,包含局部变量,比如一个函数/方法内部
  • E (Enclosing scope):内嵌作用域包含了(non-local)也非全局(non-global)的变量
  • G (Global scope):全局作用域位于当前脚本的最外层,比如当前模块的全局变量
  • B (Built-in scope):内置作用域指python中的内置函数和模块,如print、len等,可以在程序任意位置访问

当在代码中访问变量时,Python解释器会按照LEGB顺序查找变量。即,先在本地作用域查找变量,如果没有找到,就在上层作用域(内嵌作用域或全局作用域)中查找,最后在内置作用域中查找。如果还没有找到,就会抛出NameError异常。

下面是一个简单的示例:

x = 0  # 全局作用域def outer():y = 1  # 外部嵌套作用域def inner():z = 2  # 内部嵌套作用域print("x in inner:", x)  # 访问全局变量print("y in inner:", y)  # 访问外部嵌套作用域变量print("z in inner:", z)  # 访问内部嵌套作用域变量inner()print("x in outer:", x)  # 访问全局变量print("y in outer:", y)  # 访问外部嵌套作用域变量outer()
print("x in global:", x)  # 访问全局变量

1.2 作用域内符号的查看

在Python中,我们可以通过内置函数locals()来查看当前作用域内的符号以及它们对应的值。locals()函数返回一个字典,其中包含当前作用域中所有局部变量和它们的值。

下面的示例代码中查看了一个简单函数的作用域符号:

def my_function():a = 1b = 2print(locals()) # 输出:{'a': 1, 'b': 2}my_function()

我们还可以使用内置函数dir()globals()来查看当前作用域中的符号。dir()函数返回一个列表,其中包含当前作用域中所有的符号(包括变量、模块、函数、类等),而globals()函数则返回一个字典,其中包含当前作用域中所有全局变量和它们的值。

输出结果如下:

['x', 'y']
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001DA6F1C3DC0>, '__spec__': None, '__annotations__': {}, '__builtins__': 
<module 'builtins' (built-in)>, '__file__': 'd:/文档/工作/博客/教程/从零手写自动驾驶CV/Code/01.Basic/test.py', '__cached__': None, 'math': <module 'math' (built-in)>, 'my_function': <function my_function at 0x000001DA6F20D160>}

1.3 符号的查找原则

符号的查找原则是什么?

  • 原则:就近原则 通过对符号从内到外,一步步查找。直到找到匹配为止,如果没有找到,则报错
  • 符号的读取:就是就近原则去读
  • 符号的写入(修改):需要明确修改的是谁

下面代码展示了符号读取时就近原则的效果:

x = 1  # 全局变量def func():x = 2  # 局部变量,会覆盖全局变量print(x)  # 输出局部变量xfunc()  # 输出2
print(x)  # 输出1,因为在全局范围内调用x

在python中,符号的修改情况有些复杂,如果我们需要在一个函数内部修改一个全局变量,需要使用global关键字。如果我们需要在一个嵌套函数内部修改外部函数的变量,需要使用nonlocal关键字

  • global:用于声明一个变量在全局作用域中定义,从而可以在函数内部对其进行修改
  • nonlocal:用于声明一个变量在父函数的作用域中定义,从而可以在嵌套函数内部对其进行修改

注意globalnonlocal关键字只是声明符号的作用域范围,并不会创建新的符号。修改的变量必须在作用域内已经存在。

下面代码演示了符号修改时的效果:

x = 0  # 全局变量 xdef outer():y = 1  # 外部函数作用域变量 yz = 2  # 外部函数作用域变量 zdef inner():nonlocal y  # 在内部函数中修改外部函数作用域变量 yglobal x    # 在内部函数中修改全局变量 xy += 1x += 1print("inner: y =", y, ", x =", x)print("outer: y =", y, ", z =", z)inner()print("outer: y =", y, ", z =", z)outer()
print("global: x =", x)

输出结果为:

outer: y = 1 , z = 2
inner: y = 2 , x = 1
outer: y = 2 , z = 2
global: x = 1

可以看到,在内部函数inner中,我们使用了nonlocal关键字来修改外部函数作用域变量y,使用了global关键字来修改全局变量x。最后输出的全局变量x的值也确实被修改了。

2.模块和包管理

2.1 模块、包、脚本的概念

🚀🚀🚀正确使用Python的方式:使用基本的python语法,调用大量的库包,高效愉快的完成自己的工作(俗称调包侠😂)

在Python中,程序的划分可以分为三个层次:脚本、模块和包

  • 脚本(script):指独立的Python代码文件,可以被执行或作为其他程序的一部分进行调用。通常,脚本是一个单独的.py文件,其中包含可执行的代码
  • 模块(module):一组相关的Python代码,被组织成一个单独的文件,模块通常包含一些函数、类、变量和常量等,可以被其他程序导入和调用。在Python中,导入模块是一种常见的代码组织方式,可以将代码分解成更小的、可重用的模块
  • 包(package):是一种将模块组织在一起的方式,通常包含一个或多个模块文件和一个名为__init__.py的初始化文件。包可以是一个文件夹,也可以是一个压缩文件。包的主要目的是为了更好地组织和管理模块、以便于复用和维护

2.2 import

2.2.1 import语法

import语句用于在Python程序中导入其他模块或包,从而可以使用其中的函数、类、变量等。它可以有多种语法形式:

  • import module:将一个模块导入到当前作用域,并创建一个名为module的符号,可以使用该符号来访问模块中的内容。
    • 导入模块时,会执行模块中的代码,并将其中定义的符号添加到模块的命名空间中
    • 无论导入多少次,模块的代码只会执行一次,之后只有模块的命名空间被添加到当前作用域
  • import package.module:将一个包中的模块导入到当前作用域,并创建一个名为package的符号。
    • 在这种情况下,package是指要导入模块所在的包
    • 导入包中的模块时,会执行包中__init__.py中的代码,并将其中定义的符号添加到包的命名空间中
    • 如果导入的模块中有全局变量或函数,需要通过package.module.symbol的方式来访问它们

2.2.2 import package的处理

在导入一个package时,其子module并不会直接导入。这是因为package并没有容纳可以执行代码的地方,需要使用package中的__init__.py文件来容纳执行代码

__init__.py中导入模块时,应该使用from . import module而不应该使用import module,因为后者是绝对导入,符号名字是全局名字,容易导致歧义。

因此,正确的做法是在__init__.py中使用from . import module来导入需要的子module。这样,当导入package时,会自动导入__init__.py中所导入的子module

2.2.3 相对和绝对导入

绝对导入和相对导入是Python中用于导入模块的两种不同的方法。

绝对导入是通过使用模块名字的全局名称来导入模块。例如,使用import abc语句来导入模块abc。在这种情况下,Python将按照一定的规则来查找和导入模块。

相对导入是从当前模块或包的相对路径中导入模块。相对导入通常用于包内部的模块之间的导入。例如from .aaa import bbb表示从当前包内的aaa模块中导入bbb[module、package、symbol],而from ..aaa import bbb表示从当前包的上一级包中的aaa模块中导入bbb

需要注意的是,相对导入只能用于包内部的模块之间的导入,而不能用于单独的脚本文件。此外,在使用相对导入时,应该使用特定的语法来指定相对路径。例如以...开头的路径表示相对于当前包的路径。

绝对导入的检索规则是指Python导入模块时,按照一定的规则从特定的路径中查找模块的过程。具体规则如下:

  • 1.在当前脚本所在目录查找
  • 2.在PYTHONPATH列举的路径中查找
    • PYTHONPATH是一个环境变量,其值为一个用冒号隔开的路径列表
    • 可以通过sys.path获取PYTHONPATH变量,也可以为其添加新的路径
  • 3.在默认安装路径下的库中查找
    • 使用module.__file__可以获取一个模块的路径

3.第三方库

3.1 numpy

numpy是一个开源的Python数学库,主要用于数学、科学和工程计算。它提供了丰富的数学函数库,包括矩阵运算、随机数生成、傅里叶变换等等。numpy中最重要的对象是ndarray(n-dimensional array),它是一个多维数组,可以用来存储同种类型的数据,例如整数、浮点数等等。ndarray的优势在于它可以进行快速的矩阵运算和向量运算,这使得numpy成为了数据科学和机器学习领域的常用工具之一。(from chatGPT)

numpy提供了许多有用的函数和类,包括:

  • np.array:把对象转换为numpy的ndarray
  • np.zeros()和np.ones():创建指定形状的全零或全一数组
  • np.arange():创建指定范围内的一维数组
  • np.linspace():在指定的范围内生成等间隔的一维数组
  • np.random模块:用于生成各种随机数,包括均匀分布、正态分布、随机整数等等
  • np.fft模块:实现傅里叶变换
  • np.linalg模块:提供了线性代数相关的函数和类,包括求逆矩阵、求行列式、求特征值等等

下面是numpy的一些简单示例:

import numpy as np# 创建ndarray对象
arr1 = np.array([1, 2, 3])  # 一维数组
arr2 = np.array([[1, 2, 3], [4, 5, 6]])  # 二维数组
arr3 = np.zeros((3, 3))  # 3x3全0数组
arr4 = np.ones((3, 2))  # 3x2全1数组# 矩阵相乘
arr5 = np.dot(arr2, arr4)# 索引和切片
arr6 = np.array([1, 2, 3, 4, 5])
print(arr6[0])  # 输出1
print(arr6[1:3])  # 输出[2, 3]arr7 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr7[1][2])  # 输出6
print(arr7[:, 2])  # 输出3 6 9# 广播
arr8 = np.array([1, 2, 3]) 
arr9 = np.array([[1], [2], [3]])
arr10 = arr8 + arr9
print(arr10)  # 输出[[2, 3, 4], [3, 4, 5], [4, 5, 6]]

在上面的示例代码中,演示了numpy库的广播特性。

广播机制是numpy中非常重要的特征之一,它可以使得不同形状的数组在一些条件下能够进行数学运算。当运算中两个数组的形状不一致时,numpy就会自动进行扩展,以满足运算条件,这个过程就称为广播

具体来说,numpy会从尾部开始比较两个数组的维度大小,若相同则继续比较下一维度,若不同,则考虑以下几种情况:

  • 数组中存在一维度大小为1的数组,将其复制到与另一个数组的对应维度匹配,使得两个数组在该维度上大小一致
  • 数组的维度数不同,将维度较小的数组在前面添加若干个大小为1的维度,使得两个数组在维度数上一致
  • 数组的形状无法匹配,广播失败,抛出异常

对上述的广播示例代码分析有:

# 广播
arr8 = np.array([1, 2, 3]) 
arr9 = np.array([[1], [2], [3]])
arr10 = arr8 + arr9
print(arr10)  # 输出[[2, 3, 4], [3, 4, 5], [4, 5, 6]]
  • arr8的shape为(3,),arr9的shape为(3,1)
  • 首先将arr9进行广播,得到一个shape为(3,3)的数组,每一列都是原来的arr9数组即[[1, 1, 1], [2, 2, 2], [3, 3, 3]]
  • 然后将arr8进行广播,得到一个shape为(3,3)的数组,每一行都是原来的arr8数组即[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
  • 将广播后的arr8和arr9按位相加,得到arr10即[[2, 3, 4], [3, 4, 5], [4, 5, 6]]

3.2 cv2

cv2是OpenCV的Python接口,主要用于图像处理和计算机视觉任务,它支持多种图像处理操作,例如读取、显示、保存、绘制、变换、滤波、特征提取、目标检测等。cv2提供的操作可以大大简化图像处理的代码实现(from chatGPT)

在cv2中,读取出来的图像实际上是numpy的ndarray对象,通过对这些对象进行操作实现图像处理。cv2支持多种图像格式,例如PNG、JPEG、BMP等。

OpenCV的官方文档可查看OpenCV docs

下面是利用cv2读取并显示图像的简单示例代码:

import cv2# 读取图像
img = cv2.imread("image.jpg")# 显示图像
cv2.imshow("image", img)# 等待按下任意按键
cv2.waitKey(0)# 关闭所有窗口
cv2.destroyAllWindows()

3.3 matplotlib

matplotlib是一个用于数据可视化和绘图的Python库,它可以创建各种静态、动态、交互式的图形。matplotlib提供了一个基于面向对象的API,可以绘制直线图、散点图、条形图、饼图、3D图等各种类型的图形。

matplotlib主要包含以下模块:

  • pyplot模块:提供了与matlab类似的绘图API,是matplotlib的主要绘图接口,使用方便
  • pylab模块:是一个与matlab兼容的模块,它将pyplot、numpy、matplotlib各个模块的命令空间混合在一起,便于交互式工作
  • mlab模块:包含了一些用于科学计算的函数,如快速傅里叶变换等

matplotlib的图片可以保存成多种格式,包括PNG、PDF、SVG、PS、EPS等。另外,matplotlib还支持LaTex排版的文本和数学符号的显示。

在opencv中,图像是以BGR格式存储的,而在matplotlib中则是以RGB格式显示的。因此,在将opencv中读取的图像显示在matplotlib中时,需要将BGR格式转换成RGB格式。

下面是一个简单的matplotlib绘制的示例代码

import matplotlib.pyplot as plt# x和y轴的数据
x = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 10]# 绘制折线图
plt.plot(x, y, label='Line 1')# 添加图例
plt.legend()# 显示图像
plt.show()

显示的图像如下:

在这里插入图片描述

4.Dataset和DataLoader

4.1 MNIST

MNIST是一个手写数字识别数据集,它包含60,000个训练图像和10,000个测试图像。每个图像是28x28像素大小的灰度图像,表示0到9之间的一个数字。该数据集最初由美国国家标准与技术研究所(NIST)在20世纪80年代收集和创建,后来由MNIST数据库维护。该数据集已经成为机器学习和计算机视觉领域的标准基准数据集之一。(from chatGPT)

MNIST官网有关于手写数字数据集及其格式的详细介绍

MNIST数据集的格式为二进制格式,分为训练集和测试集两部分,每个部分都由一个图像文件和一个标签文件组成。训练集包含60,000个图像,测试集包含10,000个图像。图像文件和标签文件的名称分别为:

  • 训练集图像文件:train-images-idx3-ubyte.gz
  • 训练集标签文件:train-labels-idx1-ubyte.gz
  • 测试集图像文件:t10k-images-idx3-ubyte.gz
  • 测试集标签文件:t10k-labels-idx1-ubyte.gz

图像文件包含原始的灰度图像数据,标签文件包含每个图像对应的数字标签。

具体来说,图像文件的格式如下:

  • 前4个字节:magic number(MSB first)
  • 4个字节:数据集中图像的数量
  • 4个字节:每个图像的行数
  • 4个字节:每个图像的列数
  • 后续的字节:表示每个图像的像素值,按照行优先的顺序排列

标签文件的格式如下:

  • 前4个字节:magic number(MSB first)
  • 4个字节:数据集中标签的数量
  • 后续的字节:表示每个图像对应的标签,取值范围为0到9

因此,MNIST数据集中的每个图像都是一个28x28像素的灰度图像,由0到255之间的整数值表示每个像素的灰度级别。标签是一个0到9之间的整数值,表示每个图像对应的数字。

下面的示例代码用于加载MNIST数据集并返回对应的图像和标签文件:

import numpy as np
import matplotlib.pyplot as pltdef load_mnist_data(images_file_path, labels_file_path):# load labelswith open(labels_file_path, "rb") as f:magic_number, num_of_items = np.frombuffer(f.read(8), dtype=">i", count=2, offset=0)labels = np.frombuffer(f.read(), dtype=np.uint8, count=-1, offset=0)# load imageswith open(images_file_path, "rb") as f:magic_number, num_of_images, rows, cols = np.frombuffer(f.read(16), dtype=">i", count=4, offset=0)pixels = np.frombuffer(f.read(), dtype=np.uint8, count=-1, offset=0)images_matrix = pixels.reshape(num_of_images, rows, cols)return images_matrix, labelsdef show_mnist_image(images, labels, n=1):for i in range(n):plt.title(f"label is {labels[i]}")plt.imshow(images[i])plt.show()# example usage
images, labels = load_mnist_data("mnist/t10k-images-idx3-ubyte", "mnist/t10k-labels-idx1-ubyte")
show_mnist_image(images, labels)

上述示例代码通过函数load_mnist_data加载MNIST数据集并返回对应的图像和标签数据,通过show_mnist_image来显示MNIST数据集中的前n张图像和对应的标签。

4.2 拓展-MSB和LSB

在计算机中,存储多字节数据时有两种存储方式:MSB(Most Significant Bit first,最高有效位优先)和LSB(Least Significant Bit first,最低有效位优先)。在MSB中,数据的高位存储在低地址中,数据的低位存储在高地址中,即高位在前,低位在后;而在LSB中,数据的低位存储在低低地址中,数据的高位存储在高地址中,即低位在前,高位在后。

以二进制数11011011为例,它表示十进制数219。在MSB中,它的高位1存储在低地址中,依次存储为11011011;而在LSB中,它的低位1存储在低地址中,依次存储为11011011。因此,在不同的存储方式下,同一个二进制数在计算机中所占用的字节是不同的,而这在处理数据时需要进行特殊处理,以兼容不同的存储方式。

在MNIST数据集中,它的数据文件是以大端格式存储的,即高位在前,低位在后。因此,在解析MNIST数据集时需要使用>i这种大端格式,以正确地解析数据。而在实际的计算机中,绝大部分情况下采用的是小端格式存储数据。因此,为了正确地处理数据,在读取数据时需要考虑不同的存储方式。

4.3 Dataset和DataLoader

Dataset和DataLoader的用途是为了方便地处理大规模数据集,尤其是在机器学习和深度学习中。当处理大量地数据集时,需要将数据集分成多个batch,并随机从中选择一部分数据进行训练或者测试,以免因为内存不足或者计算速度过慢而导致训练时间过长或者无法完成训练。

Dataset是数据集的抽象类,它存储了数据集的所有信息,如数据集的大小、数据集的数据类型、数据集的存储位置等。Dataset也提供了一些方法,比如获取数据集长度的方法__len__,获取数据集某个特定条目的方法__getitem__

DataLoader是数据加载器,它是一个迭代器,可以从数据集中随机地加载数据,并将数据拼接成一个batch。DataLoader提供了一些参数来控制数据的加载方式,比如batch_size、shuffle等。其中,batch_size表示每个batch的大小,shuffle表示是否在每个epoch之前打乱数据集的顺序。

下面是自定义Dataset和DataLoader的一个简单示例:

import numpy as npclass ImageDataset:def __init__(self, raw_data):self.raw_data = raw_datadef __len__(self):return len(self.raw_data)def __getitem__(self, index):image, label = self.raw_data[index]return image, labelclass DataLoader:def __init__(self, dataset, batch_size, shuffle=True):self.dataset = datasetself.batch_size = batch_sizeself.shuffle = shuffledef __iter__(self):# 每次准备迭代的时候,打乱数据,并清空指针# 正常序列self.indexs = np.arange(len(self.dataset))self.cursor = 0if self.shuffle:# 打乱序列np.random.shuffle(self.indexs)return selfdef __next__(self):# 预期,在这里,返回一个batch数据,batch是从dataset中随机抓取的# 抓batch个数据前,先抓batch个indexbegin = self.cursorend   = self.cursor + self.batch_sizeif end > len(self.dataset):# 如果end超出范围了,表示已经迭代到底了raise StopIteration()self.cursor = endbatched_data = []for index in self.indexs[begin:end]:item = self.dataset[index]batched_data.append(item)return batched_data# example usage
images = [[f"images{i}", i] for i in range(20)]
dataset = ImageDataset(images)
loader = DataLoader(dataset, 5)
for index, batched_data in enumerate(loader):print(f"第{index}个批次", batched_data)

在上面的示例代码中,ImageDataset类接收原始数据作为参数,并提供了__len____getitem__方法用于获取数据集长度和某一条数据。DataLoader类接收一个Dataset对象和batch_size参数以及shuffle参数,batch_size参数用于控制每个batch的数量,shuffle参数用于指定是否打乱数据集的顺序,其中__iter__方法用于打乱数据并清空指针,__next__方法用于返回一个batch大小的数据。

5.argparse模块

5.1 命令行参数

在Python中,我们可以通过sys模块中的argv属性来获取命令行中传递进来的参数。sys.argv是一个包含了所有命令行参数的列表,其中第一个元素是脚本文件的名称,后面的元素是命令行中传递进来的参数。

例如,我们可以通过以下代码来获取命令行中传递进来的参数:

import sys# 获取命令行中的所有参数
args = sys.argv# 输出所有参数
print(args)

如果我们在命令行中执行 python test.py arg1 arg2,则上述代码的输出将会是 [‘test.py’, ‘arg1’, ‘arg2’]。

除了使用sys.argv来获取命令行参数外,还可以使用argparse模块来更方便地解析命令行参数。argparse模块提供了一种标准的方式来定义命令行参数和选项,并能够自动生成帮助信息和错误信息。

5.2 argparse解析参数

使用argparse模块解析参数通常包含以下步骤:

  • 创建ArgumentParser对象:通过argparse.ArgumentParser()创建一个解析器对象。
  • 添加参数:使用add_augment()方法向解析器对象中添加需要解析的参数。argparse可以处理多种不同类型的参数。
  • 解析参数:调用parse_args()方法对命令行参数进行解析。该方法会返回一个Namespace对象,包含解析后的参数。
  • 使用参数:使用解析后的参数执行需要的操作

下面是一个简单的示例,演示了如何使用argparse解析多种参数:

import argparseparser = argparse.ArgumentParser(description='示例程序')# 添加一个可选参数
parser.add_argument('--a', type=int, default=1, help='整数类型的可选参数')args = parser.parse_args()print('a =', args.a)

5.3 argparse处理的参数类型

argparse可以处理的参数类型包括:

  1. 位置参数(positional arguments):不需要指定参数名称,但需要按照指定的顺序依次传入参数。
parser.add_argument("b", type=float, help="浮点数类型的位置参数")
  1. 可选参数(optionalarguments):需要指定参数名称,可以不传入该参数。
parser.add_argument("--a", type=int, help="整数类型的可选参数")
  1. 布尔类型参数(boolean flags):表示选项是否被设置,通常用于打开或关闭某些功能。
parser.add_argument("--verbose", action="store_true", help="是否需要打印详细信息")

参数的数据类型可以指定为多种基本数据类型

import argparseparser = argparse.ArgumentParser()
parser.add_argument("--num", type=int, help="an integer argument")
parser.add_argument("--float", type=float, help="a float argument")
parser.add_argument("--str", type=str, help="a string argument")
args = parser.parse_args()print(args.num)
print(args.float)
print(args.str)

参数的指定也可以设置默认值,当用户没有指定参数时,将使用默认值

import argparseparser = argparse.ArgumentParser()
parser.add_argument("--arg1", default="default", help="argument 1")
args = parser.parse_args()print(args.arg1)

我们还可以为参数设置限制条件,比如参数的取值范围等

import argparseparser = argparse.ArgumentParser()
parser.add_argument("--num", type=int, help="an integer argument", choices=[1, 2, 3])
args = parser.parse_args()print(args.num)

在命令行端口进行调用时我们可以使用--help参数打印帮助信息,将会显示所有的命令行参数选项以及它们的描述。

6.作业

6.1 作业9

内容:设计一个自己的模块,并可以通过setup.py打包和发布。

本次作业参考文章实战教程:如何将自己的Python包发布到PyPI上并结合chatGPT进行了相关学习,具体实现如下。

6.1.1 创建目录结构

创建一个测试项目文件夹project_demo,在该项目下,创建一个待发布的包目录package_wish,并在项目文件夹下创建setup.pyLICENSEREADME.md,此时,目录结构如下:

project_demo└─package_wish└─__init__.py└─LICENSE└─README.md└─setup.py 

6.1.2 准备文件

README.md是关于项目的描述文件,一般包含怎样安装项目、使用项目等

A simple module for image preprocess

LICENSE为许可证,就是软件包的一些条款等

Copyright (c) 2018 The Python Packaging AuthorityPermission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

setup.py是setuptools的构建脚本,用于描述项目,打包的时候会用到这个文件。它告诉PyPI我们的项目叫什么名字,是什么版本,依赖哪些库,可以在哪些版本的Python上运行,等等。注意源代码文件夹的包目录名字和setup.py里面配置的包名(name)要一致

from setuptools import setup, find_packageswith open("README.md", "r") as f:long_description = f.read()setup(name='package_wish',  # 包名version='1.0.0',packages=find_packages(),url='https://blog.csdn.net/qq_40672115/article/details/127012332?spm=1001.2014.3001.5502',license='MIT',author='Z',author_email='1941981477@qq.com',description=long_description,classifiers=['Development Status :: 3 - Alpha','Intended Audience :: Developers','License :: OSI Approved :: MIT License','Programming Language :: Python :: 3','Programming Language :: Python :: 3.5','Programming Language :: Python :: 3.6','Programming Language :: Python :: 3.7','Programming Language :: Python :: 3.8',],install_requires=['numpy','opencv-python']
)

6.1.3 编写核心代码

我在package_wish包目录下,创建了一个Preprocess.py文件,用于完成图像预处理操作,示例代码如下:

import numpy as np
import cv2class ImageProcess:'''图像处理类'''def __init__(self, img_path, dst_width, dst_height) -> None:self.img_path = img_pathself.dst_width = dst_widthself.dst_height = dst_heightdef preprocess(self):'''返回预处理后的图像以及仿射变换矩阵M'''img = cv2.imread(self.img_path)scale = min((self.dst_width / img.shape[1]), (self.dst_height / img.shape[0]))ox = (-scale * img.shape[1] + self.dst_width)  / 2oy = (-scale * img.shape[0] + self.dst_height) / 2M  = np.array([[scale, 0, ox],[0, scale, oy]], dtype=np.float32)img_pre = cv2.warpAffine(img, M, dsize=[self.dst_width, self.dst_height], flags=cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT, borderValue=(114,114,114))return img_pre, M

6.1.4 生成分发档案

首先确保拥有setuptoolswheel,安装指令如下:

pip install setuptools wheel

setup.py位于的同一目录运行此命令:

python setup.py sdist bdist_wheel

上面的命令会在dist目录下生成一个tar.gz的源码包和一个.whl的Wheel包

6.1.5 发布包到PyPI

接着就需要上传你的包了,但在此之前,你得先去https://pypi.org/注册一个属于你自己的账号,记住用户名和密码,后续发布包的时候会用到。

首先需要安装一个包twine

pip install twine

安装完成之后,运行下面的命令上传包,期间会让你输入用户名和密码

twine upload dist/*

你在终端看到与此类似的输出

在这里插入图片描述

6.1.6 验证发布的包

上传完成后会显示success,我们可以直接在PyPI上查看,如下:

在这里插入图片描述

下面我们通过pip安装包并进行测试验证其有效性,执行指令:

pip install package-wish

如下图所示:

在这里插入图片描述

我们写个简单的示例代码进行验证,示例代码如下:

import package_wish.Preprocess as Preprocess
import cv2img_path = "./bus.jpg"
dst_width = 640
dst_height = 640imageprocess = Preprocess.ImageProcess(img_path, dst_width, dst_height)img_pre, M = imageprocess.preprocess()
print(f"M = {M}")
cv2.imshow("img_pre", img_pre)
cv2.waitKey(0)

输出结果如下:

M = [[ 0.5925926  0.        80.       ][ 0.         0.5925926  0.       ]]   

在这里插入图片描述

6.2 作业10

内容:实现mnist的dataset和dataloader

示例代码如下:

import numpy as np
import matplotlib.pyplot as pltclass MNISTDataset:def __init__(self, images_path, labels_path):self.images_path = images_pathself.labels_path = labels_pathself.images, self.labels = self.load_mnist_data()def __len__(self):return len(self.images)def __getitem__(self, index):image, label = self.images[index], self.labels[index]return image, labeldef load_mnist_data(self):# load labelswith open(self.labels_path, "rb") as f:magic_number, num_of_items = np.frombuffer(f.read(8), dtype=">i", count=2, offset=0)labels = np.frombuffer(f.read(), dtype=np.uint8, count=-1, offset=0)# load imageswith open(self.images_path, "rb") as f:magic_number, num_of_images, rows, cols = np.frombuffer(f.read(16), dtype=">i", count=4, offset=0)pixels = np.frombuffer(f.read(), dtype=np.uint8, count=-1, offset=0)images_matrix = pixels.reshape(num_of_images, rows, cols)return images_matrix, labelsclass MNISTDataLoader:def __init__(self, dataset, batch_size, shuffle=True):self.dataset = datasetself.batch_size = batch_sizeself.shuffle = shuffledef __iter__(self):self.indexs = np.arange(len(self.dataset))if self.shuffle:np.random.shuffle(self.indexs)self.cursor = 0return selfdef __next__(self):begin = self.cursorend   = self.cursor + self.batch_sizeif end > len(self.dataset):raise StopIteration()self.cursor = endbatched_data = []for index in self.indexs[begin:end]:item = self.dataset[index]batched_data.append(item)return batched_datadef show_mnist_image(images, labels, n=1):for i in range(n):image = images[i]label = labels[i]plt.title(f"label is {label}")plt.imshow(image)plt.show()# example usage
dataset = MNISTDataset("mnist/t10k-images-idx3-ubyte", "mnist/t10k-labels-idx1-ubyte")
loader  = MNISTDataLoader(dataset, batch_size=32, shuffle=True)
for i, batched_data in enumerate(loader):if i == 0:images, labels = list(zip(*batched_data))print(len(batched_data))show_mnist_image(images, labels)        

6.3 作业11

内容:使用argparse,写一个程序,读取图像,并将图像缩放到指定的大小后存储。输入和输出通过参数传递,缩放尺寸也是。

参考https://github.com/ultralytics/yolov5/blob/master/detect.py的参数解析,示例代码如下:

import cv2
import argparsedef run(img_path = './bus.jpg',resize_w = 320,resize_h = 320,view_img = False,save_img = False
):img = cv2.imread(img_path)img_resize = cv2.resize(img, (resize_w, resize_h))if view_img:cv2.imshow("result", img_resize)cv2.waitKey(0)if save_img:cv2.imwrite("result.jpg", img_resize)print("save done")def parse_opt():parser = argparse.ArgumentParser()parser.add_argument('--img_path', type=str, default= './bus.jpg', help='image file path')parser.add_argument('--resize_w', type=int, default=320, help='resize width')parser.add_argument('--resize_h', type=int, default=320, help='resize height')parser.add_argument('--view-img', action='store_true', help='show result image')parser.add_argument('--save-img', action='store_true', help='save result image')opt = parser.parse_args()print(vars(opt))return optdef main(opt):run(**vars(opt))if __name__ == "__main__":opt = parse_opt() # reference:https://github.com/ultralytics/yolov5/blob/master/detect.pymain(opt)

总结

本次课程首先对python作用域进行了相关了解,然后对python中的脚本、模块和包的相关概念进行了学习,对import导入模块进行了分析。对第三方库numpy、cv2、matplotlib进行了简单的学习。通过Dataset和DataLoader完成了对MNIST数据集的解析工作,参考相关文章利用setup.py完成了包的发布和应用,并利用argparse模块进行了命令行参数的解析工作。Python基础课程应该告一段落了,期待下后续的deep learning和3D基础课程😄