python的一些杂技(记)

python的工作路径关系

使用pycharm或者spyder的时候,当前目录os.getcwd()都是脚本所在的目录。而sys.path模块搜索目录不同,pycharm会默认将脚本所在目录以及项目的目录加入,而spyder不会。

一个常用的模块:检测并建立工作路径

if not os.path.exists(destination_path):
    os.makedirs(destination_path)

判断list为空

直接利用bool判断,list为空返回False,不为空返回True

几个常用的resize尺寸问题

PIL的Image里面,resize的写法是先宽后高

image.resize((width, height))

cv2也是

image = cv2.resize(image, (width, height))

而如果用numpy建立数组类的图片时,注意其维度是先高后宽,例中是建立了一个视频帧组成的图像包:

image_pack = np.empty((frame_len, height, width, 3), np.dtype('float32'))

Win和Ubuntu的os.listdir有区别

在Windows系统下面,使用以下代码,可以返回image_path内按文件名排序好的文件名列表image_name

image_name = os.listdir(image_path)

而Ubuntu系统中,直接调用这个,返回的文件名列表是乱序的,需要额外排一下序

image_name = os.listdir(image_path)
image_name = sorted(image_name, key=str)

另外还有一个问题,如果文件名是直接用数字,1,2,3,...,100,101这样的话,用上面这招也是有问题的。要么存的时候用001,002,...,100,101,要么用下面这个方法排序。

image_name.sort(key=lambda x: [int(x[:-4])])

或者用正则表达式,按照数字排序

import re
pic_path=os.listdir(pic_dir)
pic_path.sort(key=lambda i: int(re.match(r'(\d+)', i).group()))

OpenCV的API变化

cv2.findContours这个函数在某些版本里(比如4.2)返回三个值:

thresh, cnts, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

而某些版本(比如4.4)不再返回原图,只返回后面两个参数①轮廓的点集(contours)②各层轮廓的索引(hierarchy)。参考这个博客https://www.cnblogs.com/guobin-/p/10842486.html,他降到3.几也是可以的。

cnts, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

慎用try/except

在搞flyai测试的时候发现准确率低的离谱。基本可以确定是代码本身的问题。经过检查是因为读入的路径不对导致的cv读取图片失败,但是这个读取放在了一个try/except里面,一旦出现错误就返回一个'EMPTY'的label值。去掉这个try以后发现程序报错,锁定这个问题。至于为什么会有一丁点准确率,是因为测试程序会返回所有label都为EMPTY,总有在真值表里的EMPTY能对上。

python多线程运行

python运行for循环比较慢,但是for循环必须每次循环是相互独立的时候用多线程才行。我需要for循环从一个列表里逐次获取链接并下载链接的内容,每次是相互独立的,所以可以使用多线程来优化速度。
我需要爬取postcrossing的所有图片。首先,我需要遍历一个国家列表,对于每个国家,我要遍历这个国家的所有已发送postcard列表,下载图片链接。这里存在一个问题:每个国家的postcard列表是不一样长的。对于多线程优化,我认为较好的方法是针对每个国家下的postcard列表做优化,而非对整个国家列表做多线程优化。
具体而言,首先我利用for循环来遍历这个国家列表,其中df为所有国家的列表,code是国家代码,Postcards (sent)是已发送的明信片数量。所有存在的图片的id一定不大于已发送明信片数量,这个作为后面postcard列表的长度。

    for idx in range(len(df)):
        country_code = df.loc[idx]['Code']
        postcards_sent = df.loc[idx]['Postcards  (sent)']

之后的多线程办法参考了一个来源不详的nsfw数据集多线程下载的代码。首先做一个遍历明信片的数量上限

img_count = postcards_sent + 1

之后为每个线程分配需要遍历的子列表长度。相当于把总列表切成n份,分给n个线程各自去执行。

pre_thread_img_count = int(np.floor(img_count / worker_count))
if img_count % worker_count == 0:
    last_thread_img_count = pre_thread_img_count
else:
    last_thread_img_count = pre_thread_img_count + (img_count % worker_count)

再之后获取各个线程相对于总表的起止执行位置(就是每个子列表对于总列表的起止位置),target就是并行执行的函数,args是传给函数的参数。具体而言,相当于在worker_count次循环里,每次都触发了一下以args为参数的multi_downloader,这几个被触发的函数是齐头并进执行的。

thread_list = []
for i in range(1, worker_count + 1):
    if i != worker_count:
        start = pre_thread_img_count * (i - 1)
        end = pre_thread_img_count * i - 1
    else:
        start = pre_thread_img_count * (i - 1)
        end = start + last_thread_img_count - 1

    thread = threading.Thread(target=multi_downloader, args=(i, country_code, start, end))
    thread.start()
    thread_list.append(thread)

为什么在这里面要放个线程列表做记录?因为在之后需要执行一步检测线程终止的操作

for thread in thread_list:
    thread.join()

这的作用是使得在这个国家里的这几个下载线程都结束之后,再触发对下一个国家的for循环。不然的话,for循环所在的这个主线程会一股脑把所有的国家列表全部遍历,并且每个国家下都建立了n个下载子进程,与我们的设想不符。

join所完成的工作就是线程同步,即主线程任务结束之后,进入阻塞状态,一直等待其他的子线程执行结束之后,主线程再终止
关于join的作用,以及多线程之间的时间关系:https://www.cnblogs.com/cnkai/p/7504980.html