欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python - 批量生成幻影坦克图片

程序员文章站 2022-03-25 20:18:29
幻影坦克(Mirage Tank),《红色警戒2》以及《尤里的复仇》中盟军的一款伪装坦克,盟军王牌坦克之一。是爱因斯坦在德国黑森林中研发的一种坦克。 ......

说到幻影坦克,我就想起红色警戒里的……

幻影坦克(mirage tank),《红色警戒2》以及《尤里的复仇》中盟军的一款伪装坦克,盟军王牌坦克之一。是爱因斯坦在德国黑森林中研发的一种坦克。虽然它无法隐形,但它却可以利用先进的光线偏折原理可以伪装成树木(岩石或草丛)来隐藏自己。
在一些mod中,幻影坦克可以选择变换的树木,这样便可以和背景的树木融合,而不会令人生疑。

额!这是从什么百科ctrl+v过来的吗。我跟你说个p~ ubg
不过话说回来,里面有一句说到和背景融合,这大概就是这种图片的原理所在了。
一些聊天软件或网站总是以白色背景和黑色背景(夜间模式)显示图片,你在默认的白色背景下看到一张图(图a),但是点击放大却变成另一张图(图b)。这是因为查看详情使用的背景是黑色背景。

之前在网上看到用ps制作幻影坦克效果图的方法,了解到几个图层混合模式的公式,也录制过ps动作来自动化操作。但总感觉不够效率,作为极客嘛,当然是要用代码来完成这些事情。

Python - 批量生成幻影坦克图片


一、准备图片

  • 创建一个文件夹import,将你要处理的所有图片都放到这个文件夹里
  • 图片的命名方式:
    • 白色背景显示图a、黑色背景显示图b这种形式的,图b的文件名字是图a的名字加后缀_d
      例如,图a为1.png,图b则为1_d.png,与之配对成为一组即可
    • 表面是白色图片(图a),点击显示隐藏图片(图b)。这里并不需要你指定一张白色图片,不需要更改图片名字,程序找不到与之配对的后缀_d图片,会自动生成白色图片(图a)
    • 相反的,表面看是图片(图a),点击却消失成纯黑色(图b)。只需要在图片名字加后缀_black

二、python+pil代码实现过程

ⅰ. 初始化

注:脚本文件与 import文件夹在同一目录

  • 运行,导入模块,定义变量,创建导出目录export,并将工作目录切换到import

    # -*- coding: utf-8 -*-
    # python 3.7.2
    # 2019/04/21 by sryml.
    
    import os
    import math
    
    from timeit import timeit
    from concurrent.futures import threadpoolexecutor, processpoolexecutor
    from multiprocessing import cpu_count
    
    #
    import numba as nb
    import numpy as np
    
    from pil import image
    
    
    # ---
    import_folder = 'import'
    export_folder = 'export'
    image_files = []
    
    #
    align2_a = 0
    align2_b = 1
    align2_max = 'max'
    
    no_modift = 0
    stretch = 1
    constraint_ratio = 2
    
    # ---
    
    
    if __name__ == '__main__':
        if not os.path.exists(export_folder):
            os.makedirs(export_folder)
        os.chdir(import_folder)


ⅱ. 将所有要处理的图片文件添加到列表

  • 执行all_img2list()
    获取当前目录(import)所有文件,按名字升序排序。将后缀带_d的图b与图a配对一组,白图到原图,原图到黑图的图片也进行相关标记并存到一个列表。每个元组将生成一张幻影坦克图片

    def all_img2list():
        global image_files
        image_files= []
        imgs = os.listdir('./')
        imgs.sort(key= lambda i: os.path.splitext(i)[0])
    
        for i in imgs:
            name = os.path.splitext(i)
            imgb= name[0]+'_d' + name[1]
    
            if imgb in imgs:
                imgs.remove(imgb)
                img_group= (i,imgb)
            elif name[0][-6:].lower() == '_black':
                img_group= (i,'_black')
            else:
                img_group= (i,none)
    
            image_files.append(img_group)


ⅲ. 自动化处理,多进程任务分配

  • 执行automtank()
    不想让cpu满载运行,进程数量为cpu总核心减1,将列表里所有元组分成n等份集合的列表task_assign(n为进程数量)

    def automtank():
        cpu  = cpu_count()-1
        pool = processpoolexecutor(cpu) #max_workers=4
        l    = image_files
        f    = int(len(l)/cpu)
        task_assign = [l[n*f:] if (n+1)==cpu else l[n*f:(n+1)*f] for n in range(cpu)]
        results = list(pool.map(flashmakemtank, task_assign))
    
        pool.shutdown()
    
        print ('\n%d辆幻影坦克制作完成!' % len(image_files))
  • 每个进程对接到的任务列表进行多线程处理:flashmakemtank
    因为是图片算法处理,属于计算密集型,线程数量不需要太多。经过测试多线程还是有点效率提升的,线程数就设置为cpu核心数吧。

    def flashmakemtank(task):
        pool = threadpoolexecutor(cpu_count())
        results = list(pool.map(makemtank, task))
        pool.shutdown()


ⅳ. 盟军战车工厂

  • 每个线程都将它接到的任务 - 图片组丢给我们的盟军战车工厂:makemtank 来生产幻影坦克
  • 开头是打开图a和图b文件对象赋值给imgaimgb,判断到那些想要白图到原图效果的图片,则在内存中生成一张纯白色的图片对象赋值给imga原图到黑图则生成纯黑色图片对象赋值给imgb
  • 别以为这战车工厂看起来这么短,实际上算法都是通过调用函数获得返回结果,解释起来可有点费劲

    def makemtank(i_group):
        ratios= [0,0]
        align= []
        if not i_group[1]:
            imgb= image.open(i_group[0])
            imga= image.new('l',imgb.size,(255,))
        elif i_group[1]=='_black':
            imga= image.open(i_group[0])
            imgb= image.new('l',imga.size,(0,))
        else:
            imga= image.open(i_group[0])
            imgb= image.open(i_group[1])
            ratios= [0.5,-0.5] #明度比值
    
            # align2_max(取最大的宽和最大的高) align2_a(缩放到图a) align2_b(缩放到图b) 
            # no_modift(不修改)  stretch(拉伸)  constraint_ratio(约束比例)
            align= [align2_b, constraint_ratio]
    
        a_size,b_size= imga.size,imgb.size
        img_objs= [imga,imgb]
        for n,img in enumerate(img_objs):
            if img.mode== 'rgba':
                img= img.convert('rgb')
            img_array= np.array(img)
            if img.mode != 'l' and ( [(img_array[:,:,i]==img_array[:,:,2]).all() for i in range(2)]!= [true,true] ):
                img= desaturate(img_array) #去色
            else:
                img= img.convert('l')
    
            if align and (a_size!=b_size):
                img= imgalign(n,img,a_size,b_size,align) #图像对齐
    
            if ratios[n]:
                img= lightness(img,ratios[n]) #明度
            img_objs[n]= img
    
        imga,imgb = img_objs
    
        imga = invert(imga) #反相
        imgo = lineardodge(imga, imgb) #线性减淡(添加)
        imgr = divide(imgo, imgb) #划分
        imgr_mask = addmask(imgr, imgo) #添加透明蒙版
    
        name= os.path.splitext(i_group[0])[0]
        imgr_mask.save('../'+export_folder+'/' + name+'.png')
  • 图片对象打开完成之后呢,把它们放到一个列表里遍历它进行操作
  • 首先判断到图片模式是否为rgba,最后的a表示这张图片是带有透明通道的。而我们的幻影坦克原理就是利用的透明通道,怎能让它来胡搅蛮缠呢,速速将它转换为rgb模式
  • 接着将图像对象转为数组,判断这张图片如果不是灰度模式并且还没有去色的情况下,那就要对它进行去色操作了。
    去完色的再将它转为灰度模式。

    有些人可能对灰度去色有什么误解,灰度 ≠ 去色,这是重点。虽然它们的结果都是灰色的图片,但是算法不一样,呈现的图片对比度也不一样,直接转成灰度的坦克是没有灵魂的。rgb图片直接转灰度会丢失一些细节,所以要对它进行去色操作。下面的操作都是仿照ps的步骤来处理了

  • (1) 去色函数:desaturate
    • 公式:( max(r,g,b) + min(r,g,b) ) / 2
      每个像素取其rgb颜色中最大与最小值的均数
    • 这个函数接受一个数组参数

    例如某个像素rgb值(233,50,23),计算得出 (233+23) / 2 = 128,这时候此像素点三个通道都是同一个值(128,128,128)
    这个算法过程消耗的性能较多,像一张1000*1000的图片就得进行一百万次计算,因此我使用了numba.jit加速。
    对图片数组进行操作,使用argsort()将所有像素的rgb值从小到大排序并返回一个索引数组。
    uint8类型的值的范围在0~255,若计算出的值不在这范围则会抛出溢出错误,因此使用了int
    我创建了一个灰度图片数组data,将每一个对应像素的均值赋值给它,相当于去色后再转为灰度模式。
    最后返回由数组转换成的图片对象

    @nb.jit
    def desaturate(img_array):
        idx_array = img_array.argsort()
        width   = img_array.shape[1]
        height  = img_array.shape[0]
        data    = np.zeros((height,width),dtype=np.uint8)
        for x in range(height):
            for y in range(width):
                idx= idx_array[x,y]
                color_min= img_array[x,y, idx[0]]
                color_max= img_array[x,y, idx[2]]
                data[x,y]= round( (int(color_min) + int(color_max)) / 2 )
        return image.fromarray(data)


  • (2) 图像对齐:imgalign
    • 对齐方式(列表类型两个值)

      对齐目标 缩放图像
      align2_max 取最大的宽和最大的高 no_modift 不修改(缩小或仅画布)
      align2_a 图a stretch 拉伸
      align2_b 图b constraint_ratio 约束比例
      例如我要把图a对齐到图b且按比例缩放:mode = [align2_b, constraint_ratio]
    • 这个函数接受5个参数
      ①当前图片序号(0代表图a,1代表图b)
      ②当前图片对象
      ③ - ④图a和图b的尺寸
      ⑤对齐方式

    def imgalign(idx,img,a_size,b_size,mode):
        size= img.size
        old_size= (a_size,b_size)
    
        if mode[0]== align2_max:
            total_size= max(a_size[0], b_size[0]), max(a_size[1], b_size[1])
            if size != total_size:
                if mode[1]== stretch:
                    img= img.resize(total_size, image.antialias)
                else:
                    new_img= image.new('l',total_size, (255 if idx==0 else 0,))
                    diff= (total_size[0]-size[0],total_size[1]-size[1])
                    min_diff= min(diff[0],diff[1])
                    if min_diff != 0 and mode[1]:
                        idx= diff.index(min_diff)
                        scale= total_size[idx] / size[idx]
                        resize= [total_size[idx], round(size[1-idx]*scale)]
                        if idx:
                            resize.reverse()
                        img= img.resize(resize, image.antialias)
                    new_img.paste(img, [(total_size[i]-img.size[i])//2 for i in range(2)])
                    img= new_img
        elif idx != mode[0]:
            total_size= old_size[mode[0]]
            if mode[1]== stretch:
                img= img.resize(total_size, image.antialias)
            else:
                new_img= image.new('l',total_size, (255 if idx==0 else 0,))
                diff= (total_size[0]-size[0],total_size[1]-size[1])
                min_diff= min(diff[0],diff[1])
                if (min_diff > 0 and mode[1]) or (min_diff < 0):
                    idx= diff.index(min_diff)
                    scale= total_size[idx] / size[idx]
                    resize= [total_size[idx], round(size[1-idx]*scale)]
                    if idx:
                        resize.reverse()
                    img= img.resize(resize, image.antialias)
                new_img.paste(img, [(total_size[i]-img.size[i])//2 for i in range(2)])
                img= new_img
    
        return img


  • (3) 明度函数:lightness
    • 公式:255 * ratio + img * (1-ratio)
             0 * ratio + img * (1-ratio)
      为什么是两条公式呢,可以看到只有 255和 0的区别,一个是提高明度,一个是降低
    • 注意,明度 ≠ 亮度,用亮度做出来的坦克是畸形的。亮度对颜色0和255不会起任何作用,任你怎么加亮度,我白是白,黑仍然是黑。这又涉及到幻影坦克效果的原理了,图a每个像素值必须大于图b对应的像素值,否则将没有透明度效果。
    • 所以,最好的效果就是图a明度提高50%,图b降低50%
    • 这个函数接受2个参数
      ①图片对象
      ②明度比值(-1~1)
      尽量仿照ps的算法结果,提高明度的值为向下取整,降低明度为向上取整

      def lightness(img,ratio):
          if ratio>0:
              return img.point(lambda i: int(i*(1-ratio) + 255*ratio))
          return img.point(lambda i: math.ceil(i*(1+ratio)))
    • 实际上这是图层的不透明度混合公式,ps中,明度的实现就是在当前图层的上方创建一个白色或黑色图层,然后调整其透明度即可。所以,
      明度调  100% 相当于白色图层的不透明度为100%,显示纯白
      明度调 -100% 相当于黑色图层的不透明度为100%,显示纯黑。

看到这里,要暂停一下了。是不是感觉说了这么多都没有提到幻影坦克的详细原理,是的,只有当你理解了ps的不透明度混合公式,你才能理解后面的步骤。

  • (3-x) 重点!!推导幻影坦克的原理……
    • 这里需要用到ps的几个图层混合模式
    • 不透明度混合公式:img输出 = img上 * o + img下 * (1 - o)
      小字母o代表不透明度。想一想,把两张图片导入到ps,上面的图层命名为imga,下面的图层为imgb。
      当imga的不透明度为100%(o=1)时,根据图层混合公式得到img输出=imga,也就是完全显示上层图像。
      当imga的不透明度为0%(o=0)时,得到img输出=imgb,完全显示下层图像。
      当不透明度为50%,自然就看到了a与b的混合图像。

      但是我们要将这两张图给整进一张图里,然后在类似手机qq这种只有白色背景和黑色背景的环境下,分别显示出imga和imgb。听起来有点抽象,不要慌,我们来列方程。假设这张最终成果图为imgr

      imga = imgr * o + 255 * (1 - o) 白色背景下
      imgb = imgr * o +     0 * (1 - o) 黑色背景下(点击放大后)

      这时候imgr充当上图层(img上)。它有一个固定不透明度o,或者说是它的图层蒙版(imgo表示imgr的蒙版),蒙版的像素值为0~255的单通道灰度色值。填充为黑色0相当于图层的不透明度为0%,填充为白色相当于图层不透明度为100%。那么这个固定不透明度 o 实际上就是 ⑨ o = imgo / 255
      img下就是聊天软件中的白色背景和黑色背景两种可能了。

    现在来解一下方程,由②得:






    imgr = imgb / o

    将⑨ o = imgo / 255 代入得

    imgr = imgb / imgo * 255

    将③和⑨代入①得:






    imga = (imgb / imgo * 255) * (imgo / 255) + 255 * (1 - imgo / 255)

    imga = imgb / imgo * imgo / 255 * 255 + 255 * (1 - imgo / 255)


    imga = imgb + 2551 - 255(imgo / 255)

    imga = imgb + 255 - imgo


    imgo = (255 - imga) + imgb

    那么现在,imgb是我们已知的要在黑色背景下显示的图像,只要拿到imgo就可以得出成品图imgr了。
    (255 - imga) 这个是什么意思,就是ps中的反相操作啦。让我们回到代码操作

  • (4) 反相函数:invert
    • 公式:255 - img
      即对每个像素进行 255-像素值

      def invert(img):
          return img.point(lambda i: 255-i)

      反imga = invert(imga )
      然后这个反相后的imga(反imga)与imgb相加,即ps中的线性减淡模式

  • (5) 线性减淡(添加):lineardodge
    • 公式:img上 + img下

      def lineardodge(imga, imgb):
          size = imga.size
          imgo = image.new('l',size,(0,))
          pxa= imga.load()
          pxb= imgb.load()
          pxo= imgo.load()
          for x in range(size[0]):
              for y in range(size[1]):
                  pxo[x,y] = (pxa[x,y]+pxb[x,y],)
          return imgo

      至此得到 imgo = lineardodge(反imga, imgb)
      注:之前我们说过imga的所有像素值必须大于imgb。如果小于或等于,那么反相后加自身(或加比自身大的值)就是255了。因为imgo是成果图imgr的透明蒙版,imgo=255意味着不透明度为100%,就没有透明效果了。

      接着看方程式子③ imgr = imgb / imgo * 255,这便是ps的一种图层混合模式划分

  • (6) 划分:divide
    • 公式:img下 / img上 * 255
    • 几个注意的条件
      ①若混合色为黑色,基色非黑结果为白色、基色为黑结果为黑色(混合色是img上,基色是img下)
      ②若混合色为白色则结果为基色
      ③若混合色与基色相同则结果为白色
      不妨可以在ps中一试便知真假

      def divide(imgo, imgb):
          size = imgb.size
          imgr = image.new('l',size,(0,))
          pxb= imgb.load()
          pxo= imgo.load()
          pxr= imgr.load()
          for x in range(size[0]):
              for y in range(size[1]):
                  o=pxo[x,y]
                  b=pxb[x,y]
                  if o==0:
                      #如混合色为黑色,基色非黑结果为白色、基色为黑结果为黑色
                      color= (b and 255 or 0,)
                  elif o==255:
                      #混合色为白色则结果为基色
                      color=(b,)
                  elif o==b:
                      #混合色与基色相同则结果为白色
                      color=(255,)
                  else:
                      color=(round((b/o)*255),)
                  pxr[x,y] = color
          return imgr

      调用划分函数imgr = divide(imgo, imgb),终于,我们得到了梦寐以求的成果图imgr
      但不要忘了它的不透明度,把imgo添加为它的图层蒙版

  • (6) 最后:添加透明蒙版并保存
    def addmask(imgr,imgo):
        img = imgr.convert("rgba")
        img.putalpha(imgo)
        return img

    imgr_mask = addmask(imgr, imgo)

    name= os.path.splitext(i_group[0])[0]
    imgr_mask.save('../'+export_folder+'/' + name+'.png')
    保存在导出文件夹。。。
  • 个人感觉

    这个脚本生成的幻影坦克与ps做的相比就犹如真假美猴王一般,说到美猴王,我就想起……



三、完整代码文件

  • miragetank.py
# -*- coding: utf-8 -*-
# python 3.7.2
# 2019/04/21 by sryml.

import os
import math

from timeit import timeit
from concurrent.futures import threadpoolexecutor, processpoolexecutor
from multiprocessing import cpu_count

#
import numba as nb
import numpy as np

from pil import image


# ---
import_folder = 'import'
export_folder = 'export'
image_files = []

#
align2_a = 0
align2_b = 1
align2_max = 'max'

no_modift = 0
stretch = 1
constraint_ratio = 2

# ---




### 图像对齐
def imgalign(idx,img,a_size,b_size,mode):
    size= img.size
    old_size= (a_size,b_size)

    if mode[0]== align2_max:
        total_size= max(a_size[0], b_size[0]), max(a_size[1], b_size[1])
        if size != total_size:
            if mode[1]== stretch:
                img= img.resize(total_size, image.antialias)
            else:
                new_img= image.new('l',total_size, (255 if idx==0 else 0,))
                diff= (total_size[0]-size[0],total_size[1]-size[1])
                min_diff= min(diff[0],diff[1])
                if min_diff != 0 and mode[1]:
                    idx= diff.index(min_diff)
                    scale= total_size[idx] / size[idx]
                    resize= [total_size[idx], round(size[1-idx]*scale)]
                    if idx:
                        resize.reverse()
                    img= img.resize(resize, image.antialias)
                new_img.paste(img, [(total_size[i]-img.size[i])//2 for i in range(2)])
                img= new_img
    elif idx != mode[0]:
        total_size= old_size[mode[0]]
        if mode[1]== stretch:
            img= img.resize(total_size, image.antialias)
        else:
            new_img= image.new('l',total_size, (255 if idx==0 else 0,))
            diff= (total_size[0]-size[0],total_size[1]-size[1])
            min_diff= min(diff[0],diff[1])
            if (min_diff > 0 and mode[1]) or (min_diff < 0):
                idx= diff.index(min_diff)
                scale= total_size[idx] / size[idx]
                resize= [total_size[idx], round(size[1-idx]*scale)]
                if idx:
                    resize.reverse()
                img= img.resize(resize, image.antialias)
            new_img.paste(img, [(total_size[i]-img.size[i])//2 for i in range(2)])
            img= new_img
            
    return img


### 去色
@nb.jit
def desaturate(img_array):
    idx_array = img_array.argsort()
    width   = img_array.shape[1]
    height  = img_array.shape[0]
    data    = np.zeros((height,width),dtype=np.uint8)
    for x in range(height):
        for y in range(width):
            idx= idx_array[x,y]
            color_min= img_array[x,y, idx[0]]
            color_max= img_array[x,y, idx[2]]
            data[x,y]= round( (int(color_min) + int(color_max)) / 2 )
    return image.fromarray(data)
                

### 明度
def lightness(img,ratio):
    if ratio>0:
        return img.point(lambda i: int(i*(1-ratio) + 255*ratio))
    return img.point(lambda i: math.ceil(i*(1+ratio)))

    
### 反相
def invert(img):
    return img.point(lambda i: 255-i)
    

### 线性减淡(添加)
def lineardodge(imga, imgb):
    size = imga.size
    imgo = image.new('l',size,(0,))
    pxa= imga.load()
    pxb= imgb.load()
    pxo= imgo.load()
    for x in range(size[0]):
        for y in range(size[1]):
            pxo[x,y] = (pxa[x,y]+pxb[x,y],)
    return imgo

    
### 划分
def divide(imgo, imgb):
    size = imgb.size
    imgr = image.new('l',size,(0,))
    pxb= imgb.load()
    pxo= imgo.load()
    pxr= imgr.load()
    for x in range(size[0]):
        for y in range(size[1]):
            o=pxo[x,y]
            b=pxb[x,y]
            if o==0:
                #如混合色为黑色,基色非黑结果为白色、基色为黑结果为黑色
                color= (b and 255 or 0,)
            elif o==255:
                #混合色为白色则结果为基色
                color=(b,)
            elif o==b:
                #混合色与基色相同则结果为白色
                color=(255,)
            else:
                color=(round((b/o)*255),)
            pxr[x,y] = color
    return imgr

    
def addmask(imgr,imgo):
    img = imgr.convert("rgba")
    img.putalpha(imgo)
    return img



####
#### 将所有要处理的图片文件添加到列表
def all_img2list():
    global image_files
    image_files= []
    imgs = os.listdir('./')
    imgs.sort(key= lambda i: os.path.splitext(i)[0])
    
    for i in imgs:
        name = os.path.splitext(i)
        imgb= name[0]+'_d' + name[1]
        
        if imgb in imgs:
            imgs.remove(imgb)
            img_group= (i,imgb)
        elif name[0][-6:].lower() == '_black':
            img_group= (i,'_black')
        else:
            img_group= (i,none)
            
        image_files.append(img_group)
    

def makemtank(i_group):
    ratios= [0,0]
    align= []
    if not i_group[1]:
        imgb= image.open(i_group[0])
        imga= image.new('l',imgb.size,(255,))
    elif i_group[1]=='_black':
        imga= image.open(i_group[0])
        imgb= image.new('l',imga.size,(0,))
    else:
        imga= image.open(i_group[0])
        imgb= image.open(i_group[1])
        ratios= [0.5,-0.5] #明度比值
        
        # align2_max(取最大的宽和最大的高) align2_a(缩放到图a) align2_b(缩放到图b) 
        # no_modift(不修改)  stretch(拉伸)  constraint_ratio(约束比例)
        align= [align2_b, constraint_ratio]
        
    a_size,b_size= imga.size,imgb.size
    img_objs= [imga,imgb]
    for n,img in enumerate(img_objs):
        if img.mode== 'rgba':
            img= img.convert('rgb')
        img_array= np.array(img)
        if img.mode != 'l' and ( [(img_array[:,:,i]==img_array[:,:,2]).all() for i in range(2)]!= [true,true] ):
            img= desaturate(img_array) #去色
        else:
            img= img.convert('l')
            
        if align and (a_size!=b_size):
            img= imgalign(n,img,a_size,b_size,align) #图像对齐
                    
        if ratios[n]:
            img= lightness(img,ratios[n]) #明度
        img_objs[n]= img
        
    imga,imgb = img_objs
    
    imga = invert(imga) #反相
    imgo = lineardodge(imga, imgb) #线性减淡(添加)
    imgr = divide(imgo, imgb) #划分
    imgr_mask = addmask(imgr, imgo) #添加透明蒙版

    name= os.path.splitext(i_group[0])[0]
    imgr_mask.save('../'+export_folder+'/' + name+'.png')


    
def flashmakemtank(task):
    pool = threadpoolexecutor(cpu_count())
    results = list(pool.map(makemtank, task))
    pool.shutdown()
    
        
def automtank():
    cpu  = cpu_count()-1
    pool = processpoolexecutor(cpu) #max_workers=4
    l    = image_files
    f    = int(len(l)/cpu)
    task_assign = [l[n*f:] if (n+1)==cpu else l[n*f:(n+1)*f] for n in range(cpu)]
    results = list(pool.map(flashmakemtank, task_assign))

    pool.shutdown()
        
    print ('\n%d辆幻影坦克制作完成!' % len(image_files))

        
    
# ---

def fire():
    all_img2list()
    sec = timeit(lambda:automtank(),number=1)
    print ('time used: {} sec'.format(sec))
    s= input('\n按回车键退出...\n')



if __name__ == '__main__':
    if not os.path.exists(export_folder):
        os.makedirs(export_folder)
    os.chdir(import_folder)
    
    while true:
        s= input('>>> 按f进入坦克:')
        if s.upper()== 'f':
            print ('少女祈祷中...')
            fire() #开炮
            break
        elif not s:
            break