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

成品效果

输入: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")
}

完整项目

效果测试

 

Docker 部署项目

其实该镜像有问题,golang 版本低,dnn 函数出错。

自制 gocv 环境镜像

# 拉取 centos7 镜像
docker pull centos:7

# 启动镜像
docker run --name="centos7" -it -d --rm -p 8082:8082 centos:7

# 进入容器
docker exec -it centos7 bash

# 安装基础命令
yum install -y wget gcc make

# 安装 go 和 安装 cmake 在网上有很多教程

# 安装 gocv
go get -u -d gocv.io/x/gocv && cd $GOPATH/pkg/mod/gocv.io/x/gocv@v0.28.0 && make install

// 如果报错找不到 opencv4.pc ,不要紧,设置变量
echo 'export PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig' >>/etc/profile
source /etc/profile

// 如果报错找不到 XXX.so 文件,不要紧,就在 /usr/local/lib64 目录下
vim /etc/ld.so.conf.d/opencv.conf
添加一行 /usr/local/lib
:wq保存退出后执行 ldconfig 重载配置

完成环境安装之后验证环境:

[root@494d90036f52 ~]# source /etc/profile
[root@494d90036f52 ~]# go version
go version go1.16 linux/amd64
[root@494d90036f52 ~]# cd $GOPATH
[root@494d90036f52 gowork]# cd pkg/mod/gocv.io/x/gocv\@v0.28.0/
[root@494d90036f52 gocv@v0.28.0]# go run ./cmd/version/main.go 
gocv version: 0.28.0
opencv lib version: 4.5.3
[root@494d90036f52 gocv@v0.28.0]# 

ok,exit 退出准备 docker commit 打包镜像,其实应该用 Dockerfile 文件构建镜像的,但是有很多不可控的因素和一些奇奇怪怪的报错,因此我为简便起见使用 docker commit 指令。

# 查看当前运行的容器
docker ps

# 打包镜像
docker commit centos7 gocv:latest

# 查看打包后的镜像
docker images

现在已经有 go 和 gocv 环境的镜像了,现在构建项目的 Dockerfile :

FROM gocv:latest
WORKDIR $GOPATH/src/simple-gocv-web
ADD . ./
ENV TZ=Asia/Shanghai
ENV GO111MODULE=on
ENV GOPROXY="https://goproxy.io"
RUN source /etc/profile && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \
go build -o simple-gocv-web .
EXPOSE 8083
ENTRYPOINT  ["./simple-gocv-web"]

打包项目镜像

docker build -t demo .

 运行项目

docker run -d -it --rm -p 8083:8083 demo

运行报错:

terminate called after throwing an instance of 'cv::Exception'
  what():  OpenCV(4.5.3) /tmp/opencv/opencv-4.5.3/modules/dnn/src/torch/THDiskFile.cpp:496: error: (-2:Unspecified error) cannot open <models\eccv16\composition_vii.t7> in mode r  in function 'THDiskFile_new'

 无法加载模型,可能是没有识别到相对路径,尝试修改为绝对路径:

重新打包运行:

postman 测试:成功! 

可能是反斜杠 “/” 与双斜杠 ”\\“ 不兼容的问题,upload 文件夹采用相对路径时却能正常识别,而 models 文件夹无法识别。

网页测试:成功!

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

余衫马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值