成品效果
输入:jpg/png 格式图片
输出:风格迁移后的图片
目录结构
# tree
├─controller // 控制器
├─models // 风格迁移模型
│ ├─eccv16
│ └─instance_norm
├─router // 路由转发
├─static // 静态资源
├─sys // 系统配置
├─templates // html 模板
├─upload // 上传或者生成的图片
│ └─images
│ ├─input
│ └─output
└─utils // 封装的工具
简单构建 Web 网页
使用 Element 的上传组件和图片组件:
index.tmpl
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- import CSS -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- import axios -->
<!-- <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> -->
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
<div id="app">
<el-upload class="upload-demo" ref="upload" action="http://127.0.0.1:8080/upload" :on-success="handleSuccess" :on-preview="handlePreview" :on-remove="handleRemove" :limit="1" :file-list="fileList" list-type="picture" :auto-upload="false">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload" v-loading.fullscreen.lock="fullscreenLoading" element-loading-text="拼命加载中">转换图片</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过8Mbit</div>
</el-upload>
<div class="demo-image__lazy" style="margin-top:30px;">
<el-image v-for="url in urls" :key="url" :src="url" lazy></el-image>
</div>
</div>
</body>
<script>
new Vue({
el: '#app',
data() {
return {
fileList: [],
urls: [],
fullscreenLoading: false
};
},
methods: {
// 提交表单
submitUpload() {
this.$refs.upload.submit();
this.fullscreenLoading = true;
},
// 移除图片
handleRemove(file, fileList) {
console.log(file, fileList);
},
// 预览略缩图
handlePreview(file) {
console.log(file);
},
// 超出限制
handleExceed(files, fileList) {
this.$message.warning(`当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
},
// 加载成功
handleSuccess(response, file, fileList){
// console.log(response.data);
this.urls = response.urls;
this.fullscreenLoading = false;
}
}
})
</script>
</html>
封装系统变量
package sys
const OUT_PATH string = "D:\\GoFiles\\simple-gocv-web\\upload\\images\\output\\"
const IN_PATH string = "D:\\GoFiles\\simple-gocv-web\\upload\\images\\input\\"
const IMG_URL string = "http://127.0.0.1:8080/upload/images/output/"
var MODELS_NAME = []string{
"D:\\GoFiles\\simple-gocv-web\\models\\eccv16\\composition_vii.t7",
"D:\\GoFiles\\simple-gocv-web\\models\\eccv16\\la_muse.t7",
"D:\\GoFiles\\simple-gocv-web\\models\\eccv16\\starry_night.t7",
"D:\\GoFiles\\simple-gocv-web\\models\\eccv16\\the_wave.t7",
"D:\\GoFiles\\simple-gocv-web\\models\\instance_norm\\candy.t7",
"D:\\GoFiles\\simple-gocv-web\\models\\instance_norm\\feathers.t7",
"D:\\GoFiles\\simple-gocv-web\\models\\instance_norm\\la_muse.t7",
"D:\\GoFiles\\simple-gocv-web\\models\\instance_norm\\mosaic.t7",
"D:\\GoFiles\\simple-gocv-web\\models\\instance_norm\\the_scream.t7",
"D:\\GoFiles\\simple-gocv-web\\models\\instance_norm\\udnie.t7",
}
工具类
uuidUtil :保证图片文件名不重复。
package utils
import uuid "github.com/satori/go.uuid"
/*
生成全局唯一 uuid
*/
func GetUUID() string {
return uuid.NewV4().String()
}
saveImgUtil :保存图片返回访问路径
package utils
import (
"image"
"image/jpeg"
"os"
. "simple-web/sys"
)
func SaveImg(src image.Image) string {
// 写入新文件
imgId := GetUUID() + ".jpg"
path := OUT_PATH + imgId
f, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE, os.ModePerm)
defer f.Close()
jpeg.Encode(f, src, nil)
// 返回图片路径
return IMG_URL + imgId
}
dnnUtil :进行图片风格迁移
package utils
import (
"fmt"
"image"
"log"
. "simple-web/sys"
"gocv.io/x/gocv"
)
func GetTransferResult(deviceID string) []string {
var res []string
for i := 0; i < len(MODELS_NAME); i++ {
res = append(res, DNNStyleTransfer(deviceID, MODELS_NAME[i]))
}
return res
}
func DNNStyleTransfer(deviceID string, model string) string {
if len(deviceID) == 0 || len(model) == 0 {
log.Fatal("How to run:\ndnn-style-transfer [videosource] [modelfile] ([backend] [device])")
}
// 使用默认参数
backend := gocv.NetBackendDefault
target := gocv.NetTargetCPU
// 读取的图片位置
img := gocv.IMRead(deviceID, gocv.IMReadColor)
defer img.Close()
// 打开下载好的 DNN 风格迁移模型
net := gocv.ReadNet(model, "")
if net.Empty() {
log.Fatalf("Error reading network model from : %v\n", model)
}
defer net.Close()
net.SetPreferableBackend(gocv.NetBackendType(backend))
net.SetPreferableTarget(gocv.NetTargetType(target))
fmt.Printf("Start reading device: %v\n", deviceID)
// 将 image Mat 转换为 DNN 风格迁移模型可以分析的 640x480 Blob
blob := gocv.BlobFromImage(img, 1.0, image.Pt(640, 480), gocv.NewScalar(103.939, 116.779, 123.68, 0), false, false)
// 把转换成的 Blob 放进转换器
net.SetInput(blob, "")
// 通过网络运行一个向前传递
probMat := net.Forward("")
sz := probMat.Size()
dims := sz[2] * sz[3]
out := gocv.NewMatWithSize(480, 640, gocv.MatTypeCV8UC3)
// 取 blob 并从中获取可显示的 image Mat 图像
for i := 0; i < dims; i++ {
r := probMat.GetFloatAt(0, i)
r += 103.939
g := probMat.GetFloatAt(0, i+dims)
g += 116.779
b := probMat.GetFloatAt(0, i+dims*2)
b += 123.68
out.SetUCharAt(0, i*3, uint8(r))
out.SetUCharAt(0, i*3+1, uint8(g))
out.SetUCharAt(0, i*3+2, uint8(b))
}
// 保存图片,得到返回路径
i, err := out.ToImage()
if err != nil {
log.Fatal(err)
}
path := SaveImg(i)
probMat.Close()
blob.Close()
out.Close()
return path
}
控制器
控制文件上传和返回结果。
package controller
import (
"log"
"net/http"
. "simple-web/sys"
. "simple-web/utils"
"github.com/gin-gonic/gin"
)
// 首页
func Index(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", nil)
}
/*
上传图片并实现风迁移,渲染到页面
*/
func Upload(c *gin.Context) {
// 单文件
file, _ := c.FormFile("file")
log.Println(file.Filename)
// 上传文件至指定目录
dst := IN_PATH + GetUUID()
err := c.SaveUploadedFile(file, dst)
if err != nil {
log.Println(err)
}
outDst := GetTransferResult(dst)
log.Println(outDst)
// 渲染
/*c.HTML(http.StatusOK, "index.tmpl", gin.H{
".bodyText": outDst,
})*/
// 返回结果
c.JSON(http.StatusOK, gin.H{
"status": "success",
"urls": outDst,
})
}
定义路由
package router
import (
"net/http"
. "simple-web/controller"
"github.com/gin-gonic/gin"
)
/*
InitRouter 路由初始化
*/
func InitRouter() *gin.Engine {
router := gin.Default()
// 加载 templates 文件夹下所有的 tmpl
router.LoadHTMLGlob("templates/*")
router.GET("/index", Index)
// 文件上传
router.POST("/upload", Upload)
// ============== 静态资源/文件 ==============
// 加载静态资源,例如网页的css、js
router.Static("/static", "./static")
// 加载静态资源,一般是上传的资源,例如用户上传的图片
router.StaticFS("/upload", http.Dir("upload"))
// 加载单个静态文件
// r.StaticFile("/favicon.ico", "./static/favicon.ico")
return router
}
main 入口
package main
import (
. "simple-web/router"
)
func main() {
r := InitRouter()
// Listen and Server in 0.0.0.0:8080
r.Run(":8080")
}
完整项目
效果测试