Gin + GoCv + Element 简单搭建图片风格迁移网站

成品效果

输入:jpg/png 格式图片

输出:风格迁移后的图片

目录结构

# tree

├─controller // 控制器
├─models // 风格迁移模型
│  ├─eccv16
│  └─instance_norm
├─router // 路由转发
├─static // 静态资源
├─sys // 系统配置
├─templates // html 模板
├─upload // 上传或者生成的图片
│  └─images
│      ├─input
│      └─output
└─utils // 封装的工具

简单构建 Web 网页

使用 Element 的上传组件和图片组件:

Gin + GoCv + Element 简单搭建图片风格迁移网站

Gin + GoCv + Element 简单搭建图片风格迁移网站

Gin + GoCv + 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")
}

完整项目

Gin + GoCv + Element 简单搭建图片风格迁移网站

效果测试

Gin + GoCv + Element 简单搭建图片风格迁移网站

Gin + GoCv + Element 简单搭建图片风格迁移网站

Gin + GoCv + Element 简单搭建图片风格迁移网站 Gin + GoCv + Element 简单搭建图片风格迁移网站

 

 

上一篇:Gin - 数据绑定


下一篇:GitHub标星1w的Java架构师必备技能,Java岗