一直在红海项目里斗来斗去,没意思透了。今天给大家带来一个有门槛且蓝海的项目。
小程序增加流量主功能,广告控制,插屏广告,激励广告和原生广告。
小程序增加VIP付款功能,实现用户付费购买换装机会
搭建小程序,为用户提供换脸变装功能(下方有成品演示)。通过小程序流量主+付费的双重商业模式达到盈利。
只需一张照片即可变性、变装、换脸,既满足用户猎奇心理又为用户提供价值。最终还含泪把钱赚了。
(wx.webpackJsonp = wx.webpackJsonp || []).push([ [ 40 ], {
38: function(e, t, n) {
e.exports = {
info: "info__info___X70yB",
card: "info__card___3xdZ9",
divide: "info__divide___v08Y7",
row: "info__row___2nsah",
name: "info__name___jan45",
text: "info__text___1k7kM",
avatar: "info__avatar___2iC2D",
arrow: "info__arrow___3e6e4",
logout: "info__logout___GZ02v",
input: "info__input___1W59S",
placeholder: "info__placeholder___11H_Q"
};
},
434: function(e, t, n) {
"use strict";
n.r(t);
var a = n(7), r = n(2), c = n.n(r), s = n(5), i = n(10), o = n(1), u = n(3), b = n(22), l = n(20), j = n(50), p = n(127), f = n(57), O = n(80), d = n(16), _ = n(31), x = n(13), h = n(39), m = n(4), g = n.n(m), k = n(38), v = n.n(k), N = n(60), w = n.n(N), y = n(8), S = n(153), C = n(19), P = n(66), T = n(18), F = n(79), I = n(0), M = function(e) {
if (null == e || 0 === e.length) return "点击绑定";
var t = e;
if (t.includes("-") && (t = t.split("-")[1]), t.length < 11) return t;
var n = t.substring(0, 3), a = t.substring(t.length - 4);
return "".concat(n, "****").concat(a);
}, B = function() {
var e = Object(u.useState)(!1), t = Object(i.a)(e, 2), n = t[0], a = t[1], r = Object(u.useState)(!1), m = Object(i.a)(r, 2), k = m[0], N = m[1], B = Object(u.useState)(""), D = Object(i.a)(B, 2), A = D[0], G = D[1], J = Object(b.c)(function(e) {
return e.account;
}), Z = Object(b.b)();
Object(T.c)({
type: "app"
}), Object(u.useEffect)(function() {
G(J.nickName || "");
}, [ J.nickName ]);
var z = Object(d.h)(), E = function() {
var e = Object(s.a)(c.a.mark(function e(t) {
var n, a, r, s, i;
return c.a.wrap(function(e) {
for (;;) switch (e.prev = e.next) {
case 0:
if ("getPhoneNumber:ok" === t.detail.errMsg) {
e.next = 3;
break;
}
return Object(_.e)("bindPhone"), e.abrupt("return");
case 3:
return n = t.detail, a = n.encryptedData, r = n.iv, s = n.code, Object(y.d)(), e.next = 7,
Object(F.b)();
case 7:
if (0 === (i = e.sent).code) {
e.next = 12;
break;
}
return Object(y.c)(), i.code > 0 && y.a.error("绑定失败"), e.abrupt("return");
case 12:
return e.next = 14, Object(h.a)({
type: Object(d.a)(),
openId: i.data,
code: s,
encryptData: a,
iv: r
});
case 14:
if ("ok" !== e.sent.err) {
e.next = 21;
break;
}
return Object(y.c)(), e.next = 19, Q();
case 19:
e.next = 22;
break;
case 21:
Object(y.c)();
case 22:
case "end":
return e.stop();
}
}, e);
}));
return function(t) {
return e.apply(this, arguments);
};
}(), H = function() {
var e = Object(s.a)(c.a.mark(function e(t) {
return c.a.wrap(function(e) {
for (;;) switch (e.prev = e.next) {
case 0:
return e.next = 2, Object(h.i)(t);
case 2:
if ("ok" === e.sent.err) {
e.next = 5;
break;
}
return e.abrupt("return");
case 5:
return e.next = 7, Q(t);
case 7:
case "end":
return e.stop();
}
}, e);
}));
return function(t) {
return e.apply(this, arguments);
};
}(), Q = function() {
var e = Object(s.a)(c.a.mark(function e(t) {
var n;
return c.a.wrap(function(e) {
for (;;) switch (e.prev = e.next) {
case 0:
return e.next = 2, Object(h.c)();
case 2:
"ok" === (n = e.sent).err && n.res ? Z(Object(f.c)(n.res)) : t && Z(Object(f.c)(t));
case 4:
case "end":
return e.stop();
}
}, e);
}));
return function(t) {
return e.apply(this, arguments);
};
}(), W = function() {
var e = Object(s.a)(c.a.mark(function e() {
var t, n, a, r, s, i;
return c.a.wrap(function(e) {
for (;;) switch (e.prev = e.next) {
case 0:
return e.next = 2, g.a.chooseImage({
sizeType: [ "original" ],
count: 1
});
case 2:
if (t = e.sent, null != (n = t.tempFilePaths) && 0 !== n.length) {
e.next = 6;
break;
}
return e.abrupt("return");
case 6:
if (a = n[0], r = a.split("?"), !((s = r.length > 0 ? r[0] : "").length <= 0)) {
e.next = 11;
break;
}
return e.abrupt("return");
case 11:
return Object(P.a)(500), Object(y.d)(), e.next = 15, Object(S.a)(s);
case 15:
if (e.sent) {
e.next = 19;
break;
}
return Object(y.c)(), e.abrupt("return");
case 19:
return e.next = 21, Object(x.x)(s);
case 21:
if ("ok" === (i = e.sent).err && null != i.res) {
e.next = 25;
break;
}
return Object(y.c)(), e.abrupt("return");
case 25:
H({
headImageSign: i.res.sign
}), Object(y.c)();
case 27:
case "end":
return e.stop();
}
}, e);
}));
return function() {
return e.apply(this, arguments);
};
}();
return Object(I.jsxs)(C.b, {
className: v.a.info,
children: [ Object(I.jsxs)(o.j, {
className: v.a.card,
children: [ Object(I.jsxs)(o.j, {
className: v.a.row,
onClick: W,
children: [ Object(I.jsx)(o.g, {
className: v.a.name,
children: "头像"
}), Object(I.jsx)(o.c, {
className: v.a.avatar,
src: Object(l.b)(J.headImageSign),
mode: "aspectFill"
}), Object(I.jsx)(o.c, {
className: v.a.arrow,
src: w.a,
mode: "aspectFit"
}) ]
}), Object(I.jsx)(o.j, {
className: v.a.divide
}), Object(I.jsxs)(o.j, {
className: v.a.row,
onClick: function() {
return N(!0);
},
children: [ Object(I.jsx)(o.g, {
className: v.a.name,
children: "昵称"
}), Object(I.jsx)(o.g, {
className: v.a.text,
children: J.nickName
}), Object(I.jsx)(o.c, {
className: v.a.arrow,
src: w.a,
mode: "aspectFit"
}) ]
}), Object(I.jsx)(o.j, {
className: v.a.divide
}), Object(I.jsxs)(O.a, {
className: v.a.row,
openType: z ? "getPhoneNumber" : void 0,
onGetPhoneNumber: E,
onClick: function() {
z || Object(_.e)("bindPhone");
},
children: [ Object(I.jsx)(o.g, {
className: v.a.name,
children: "手机号"
}), Object(I.jsx)(o.g, {
className: v.a.text,
children: M(J.phoneNum)
}), Object(I.jsx)(o.c, {
className: v.a.arrow,
src: w.a,
mode: "aspectFit"
}) ]
}) ]
}), Object(I.jsxs)(j.a, {
className: v.a.logout,
onClick: function() {
return a(!0);
},
children: [ "退出登录", n ]
}), Object(I.jsx)(p.a, {
visible: n,
title: "确定要退出登录吗?",
content: "点击确定退出登录",
onClose: function() {
return a(!1);
},
onOk: function() {
wx.clearStorageSync();
Z(Object(f.b)()), g.a.navigateBack();
}
}), Object(I.jsx)(p.a, {
width: "80%",
align: "center",
visible: k,
onClose: function() {
return N(!1);
},
title: "修改昵称",
content: Object(I.jsx)(o.d, {
value: A,
placeholder: "请输入昵称",
className: v.a.input,
placeholderClass: v.a.placeholder,
onInput: function(e) {
return G(e.detail.value);
}
}),
onOk: function() {
if (0 === A.length) return y.a.error("请输入昵称"), !1;
J.nickName != A && H({
nickName: A
});
}
}) ]
});
};
B.enableShareTimeline = !0, B.enableShareAppMessage = !0, Page(Object(a.createPageConfig)(B, "pages/user/info", {
root: {
cn: []
}
}, {
navigationBarTitleText: "个人资料",
enableShareAppMessage: !0,
enableShareTimeline: !0
} || {}));
}
}, [ [ 434, 0, 1, 2, 3 ] ] ]);
今天直入主题,主要讲一讲如何训练AMP模型以及如何应用到DeepFaceLive之中。
0
点击并拖拽以移动
模型的训练
训练主要可以分两种方式:常规训练和复用训练
常规训练
常规训练流程如下:
0
点击并拖拽以移动
这个流程就是我们之前讲的常规流程,只是训练的模型换成了AMP,并且还少了一些步骤。因为最终我们将要应用到DeepFaceLive中,所以常规流程中的应用模型和合成视频就不需要了。
这种训练方式就单次应用来说会省很多时间。但是如果需要训练不同的对象,那么每次重新开始就会比较费时间。 所以从长期使用的角度来说,使用复用训练会更好。大量素材训练的复用模型合成效果也会比较好。
复用训练
复用训练流程如下:
0
点击并拖拽以移动
这个流程的操作思路是先对大量人脸进行训练,然后再训练具体的人。为了实现这个操作需要如下步骤。
将软件自带RTM WF faceset数据集放到源目录的aligned下面。
点击“ 6) 训练AMP模型 源对源 train AMP SRC-SRC.bat ” 开始训练,训练个几百万次。
删除模型文件夹中的_AMP_inter_dst.npy文件
然后按执行上图中的2-8步骤。
0
点击并拖拽以移动
注意和常规步骤相比,这里多了Xseg泛型和SRC-SRC的处理。应用Xseg泛型是为了使用默认的遮罩模型给src和dst应用遮罩,SRC-SRC是给模型打基础。
模型的应用
训练的差不多了,就可以使用这个模型了。训练的环节主要由DeepFaceLab完成,而应用主要是在DeepFaceLive上。DeepFaceLive可以笼统的称为直播换脸软件,本质上这个软件只是实现了实时换脸的功能,真正直播推流还需要其他软件配合。所以说这个Live理解为实时比较合适。DeepFaceLive我们可以称之为实时换脸软件。
模型的应用可以分为两部分:导出和导入。
模型导出
导出的过程其实就做了tf2onnx的转换,具体操作是:
先点击 6) 导出AMP模型 export AMP as dfm.bat 批处理文件
选择需要导出的模型,回车,回车,等待片刻。
0
点击并拖拽以移动
0
点击并拖拽以移动
成功导出的模型,放在model文件夹下面,后缀为.dfm 。
模型导入
导入模型也非常简,就是拷贝“amp_AMP_model.dfm” 文件到DeepFaceLive相应的文件夹即可。
具体路径为:DeepFaceLive\userdata\dfm_models
0
点击并拖拽以移动
模型导入成功之后就可以启用实时换脸软件了
0
点击并拖拽以移动
软件启动,稍作设置就是这个样子了。
0
点击并拖拽以移动
这是使用软件自带的模型的效果,使用我们自己训练的模型,只需要修改Face Swapper 下面的Model即可。
DeepFaceLive已经是高度集成的可视化软件了,操作基本没啥难度,所以功夫主要还是在DeepFaceLab这一边。
下一篇介绍一下DeepFaceLive的具体操作。
文中提到的复用模型,我的素材和遮罩已经优化完了,训练到200万后会放到tonyhub中,因为素材比较多200万只是起步,我会继续练,继续更新。大家也可以自己在公共素材的基础上去优化,然后慢慢训练。