关于nft:NFT-制作保姆级进阶教程批量图片盲盒头像IP-造型等合成

5次阅读

共计 3924 个字符,预计需要花费 10 分钟才能阅读完成。

背景阐明

无论是这样的:

还是这样的:

其实都是由背景、角色、组件拼接而成的图片。

假如用以下素材去生成 NFT 盲盒:

  • 背景:10 种不同的色彩
  • 角色:10 种不同的肤色
  • 配饰:

    • 10 种帽子
    • 10 种衣服
    • 10 种手持物品

这么简略的 50 个素材,就曾经能够拼接成 10*10*10*10*10 = 10 万种不同的组合。

保姆级教程

以虚构我的项目案例手把手入门。示例我的项目代码下载:https://download.csdn.net/download/jslygwx/77829790

假如我的项目状况:

  • 共计合成 10 万张图片
  • 有 8 种类型素材(背景、角色、肤色、物品、配饰之类的,共计 8 种)
  • 每种类型再提供 10 张素材图片
  • 罕见水平设定(总计概率 100%)
  1 / 100,
  2 / 100,
  3 / 100,
  4 / 100,
  10 / 100,
  10 / 100,
  16 / 100,
  17 / 100,
  18 / 100,
  19 / 100

就轻易设置这么样个概率作为演示。(后续进阶教程再说特定罕见水平设定和个性化素材配置的相干内容)

生成组合

从简略例子着手,比如说 8 种部件类型,就按 0~7 的程序别离建设文件夹,示例:

  • 0 背景图
  • 1 角色
  • 2 角色肤色
  • 3 角色衣服
  • 4 角色帽子
  • 。。。。。。
// 设置总数 10 万
const TOTAL = 1e5;

// 类型定义,别离为 0 1 2 3 4 5 6 7
const TYPE = new Array(8).fill(0).map((_, i) => i);

// 部件定义,别离为 0 1 2 3 ... 9
// 如果超过 10,能够用字母 a b c d e 持续排序
const COMP = new Array(10).fill(0).map((_, i) => i);

// 对应 0 ~ 9 号部件生成的概率
const PR = [
  1 / 100,
  2 / 100,
  3 / 100,
  4 / 100,
  10 / 100,
  10 / 100,
  16 / 100,
  17 / 100,
  18 / 100,
  19 / 100
];

const START_TIME = new Date();
const RESULTS = new Set<string>();
// 打乱并生成
const END_TIME = new Date();
console.log(`Spent: ${END_TIME.getTime() - START_TIME.getTime()}ms`);
console.log(JSON.stringify([...RESULTS], null, 2));

以上算法(见我的项目示例代码),生成 500 万条数据应该也是在 1s 以内。

执行后果:

Spent: 34ms
[
  "99696789",
  "64766443",
  "44467446",
  //。。。10 万条数据
]

为什么应用随机种子。因为这样能保障所有的组合,都正好合乎概率,而且保障了程序运行的性能。

如果感觉生成的后果不够乱序,能够在上述标记排序的地位批改排序规定,或者多执行几次 sort() 打乱。

生成图片(繁多,一张)

其中以 99696789 为例阐明:

  • 背景:9 ——第 10 种一般素材
  • 角色:9 ——第 10 种一般素材
  • 肤色:6 ——第 7 种一般素材
  • ……

将各个素材从底向上拼接成图片,一个 NFT 即为生成。

生成图片:

node generate.js 99001122

生成单张图片,大概在 0.5~1.5 秒左右。

批量生成 NFT 图片

这里咱们能够用到多线程和音讯队列。

本地先启动 Redis(能够清空 db0)。跑一遍该脚本,把 10 万条工作插入到队列:

import * as fs from 'fs';
import * as path from 'path';
import {WhiteQ} from 'whiteq';

const wq = new WhiteQ();

const file = fs.readFileSync(path.join(__dirname, '../100k.log'), 'utf-8');

const tasks = file.split('\n');

wq.addJobs(
  'tasks',
  tasks.map((t) => ({name: t, data: t}))
)
  .then(() => {process.exit(0);
  })
  .catch(console.error);

而后用 PM2 执行该脚本去多线程执行生成工作:

const {WhiteQ} = require('whiteq');
const {execSync} = require('child_process');
const path = require('path');

const wq = new WhiteQ();
wq.worker('tasks', async (job) => {console.log(job.name);
  const result = execSync(`node ${path.join(__dirname, 'generate.js')} ${job.name}`, {encoding: 'utf-8'});
  if (result.includes('Time spent')) {return true;}
  console.log(result);
  return false;
});

实测 10 万张图片生成的话,大概 5 个小时左右;一万张图片大概不到半个小时。

图片压缩

能够把原有 Redis 记录清空,并从新插入工作队列。

应用 pngquant 压缩,能将本来 3MB 左右的 png 图片压缩到 100~300KB。

批量重命名

压缩过的图片都带有 -fs8.png 的文件后缀,如果想要去掉,则能够应用批量重命名的形式:

const {WhiteQ} = require('whiteq');
const {execSync} = require('child_process');
const path = require('path');

const wq = new WhiteQ();
wq.worker('tasks', async (job) => {console.log(job.name);
  execSync(`rm -rf ${path.join(__dirname, `../output/${job.name}.png`)}`);
  execSync(`mv ${path.join(__dirname, `../output/${job.name}-fs8.png`)} ${path.join(__dirname, `../output/${job.name}.png`)}`
  );
  return true;
});

进阶教程

你能够对于部件的类型,每种部件元素的数量,以及各个元素掉落概率都有定制化的要求,那么则能够参考这个生成脚本来生成组合:

const TOTAL = 1e5;
const START_TIME = new Date();

const PR = {
  '1background': [
    8.8 / 100,
    9.0 / 100,
    8.7 / 100,
    9.1 / 100,
    9.0 / 100,
    8.9 / 100,
    9.2 / 100,
    8.8 / 100,
    8.5 / 100,
    10 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100
  ],
  '2skin': [
    9.1 / 100,
    9.2 / 100,
    9.3 / 100,
    9.0 / 100,
    9.1 / 100,
    8.4 / 100,
    9.7 / 100,
    8.5 / 100,
    9.9 / 100,
    8.8 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100
  ],
  '3clothes': [
    9.1 / 100,
    9.2 / 100,
    9.3 / 100,
    9.0 / 100,
    9.1 / 100,
    8.4 / 100,
    9.7 / 100,
    8.5 / 100,
    9.9 / 100,
    8.8 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100
  ],
  '4necklace': [
    9.1 / 100,
    10.5 / 100,
    10.1 / 100,
    10.6 / 100,
    9.1 / 100,
    9.0 / 100,
    10.7 / 100,
    9.2 / 100,
    9.9 / 100,
    10.8 / 100,
    1 / 100
  ],
  '5bag': [
    9.6 / 100,
    10.5 / 100,
    10.1 / 100,
    10.6 / 100,
    9.1 / 100,
    9.5 / 100,
    10.7 / 100,
    9.2 / 100,
    9.9 / 100,
    10.8 / 100
  ],
  '6head': [
    9.1 / 100,
    9.3 / 100,
    10.1 / 100,
    9.4 / 100,
    9.1 / 100,
    9.0 / 100,
    9.4 / 100,
    9.2 / 100,
    9.0 / 100,
    9.4 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100
  ],
  '7glasses': [
    9.1 / 100,
    9.2 / 100,
    9.3 / 100,
    9.2 / 100,
    9.1 / 100,
    8.8 / 100,
    9.7 / 100,
    8.9 / 100,
    8.9 / 100,
    8.8 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100
  ],
  '8accessories': [
    8.1 / 100,
    9.2 / 100,
    8.3 / 100,
    9.0 / 100,
    9.1 / 100,
    8.4 / 100,
    9.7 / 100,
    8.5 / 100,
    8.9 / 100,
    8.8 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100,
    1 / 100
  ]
};



const END_TIME = new Date();
console.log(`Spent: ${END_TIME.getTime() - START_TIME.getTime()}ms`);
console.log([...RESULTS].join('\n'));

其实比拟看一下代码的差别就晓得了。很容易了解。

生成胜利后,公布即可。

须要我的项目领导请 pm 分割。

正文完
 0