Dust8 的博客

读书百遍其义自见

0%

因为训练好的模型框架来源比较多, 每个框架部署的方式不一样,造成不便, 可以通过模型转换统一到同一格式. 国内的框架是飞桨paddle, 所以可以统一到paddle模型.

比如把torch 框架 ultralytics/yolov5 的模型转换为 paddle 的模型.

技术选型

转换路径: torch -> onnx -> paddle

百度提供PaddlePaddle/X2Paddle 工具来转换.由于版本的差异按照官方文档的代码转换方式全都报错,经过不同的尝试,最后导出成功.如果按官方文档的方式报错,可以尝试切换版本到已成功的环境.
如下所示:

1
2
3
4
5
onnx                    1.11.0
paddlepaddle 2.3.0
torch 1.10.1
torchvision 0.11.2
x2paddle 1.3.6

torchvisiontorch 的版本是需要相对应的,可以查看 pypi 上面的适配表.

目录结构:

1
2
3
4
5
6
7
8
9
10
11
$ tree -L 1
.
|-- 1.jpg
|-- pd_model
|-- t1.py
|-- t2p_utils.py
|-- test.py
|-- venv
|-- yolov5
|-- yolov5s.onnx
`-- yolov5s.pt

torch 转换为 onnx

已经成功的是v6.1版本,如果最新代码失败,可以切换到该版本尝试

1
2
3
4
5
6
# 下载代码,并安装依赖, 
git clone https://github.com/ultralytics/yolov5
cd yolov5
pip install -r requirements.txt # install

python yolov5/export.py --weights yolov5s.pt --include onnx

onnx 转换为 paddle

转换成功就保存在pd_model目录中

1
2
3
pip install x2paddle

x2paddle --framework=onnx --model=yolov5s.onnx --save_dir=pd_model

转换成功的目录

1
2
3
4
5
6
|-- inference_model
| |-- model.pdiparams
| |-- model.pdiparams.info
| `-- model.pdmodel
|-- model.pdparams
`-- x2paddle_code.py

参考链接

画线

cv.line()
pt1: 第一个点
pt2:是第二个点

1
2
3
4
5
6
7
8
9
10
11
cv.line(img, pt1, pt2, color[, thickness[, lineType[, shift]]]) ->img

import numpy as np
import cv2 as cv


# Draw a diagonal blue line with thickness of 5 px
cv.line(img,(0,0),(511,511),(255,0,0),5)

cv.imshow('frame',img)
k = cv.waitKey(0)

画矩形

cv.rectangle()
pt1: 矩形的顶点, 左上角的点
pt2: 与 pt1 相对的矩形的顶点, 右下角的点

1
2
3
4
cv.rectangle(img, pt1, pt2, color[, thickness[, lineType[, shift]]]) ->img
cv.rectangle(img, rec, color[, thickness[, lineType[, shift]]]) ->img

cv.rectangle(img,(384,0),(510,128),(0,255,0),3)

画圆

cv.circle()
center: 中心点坐标
radius: 圆半径

1
2
3
cv.circle(img, center, radius, color[, thickness[, lineType[, shift]]]) ->img

cv.circle(img,(447,63), 63, (0,0,255), -1)

画椭圆

cv.ellipse()

1
2
3
4
cv.ellipse(img, center, axes, angle, startAngle, endAngle, color[, thickness[, lineType[, shift]]]	) ->img
cv.ellipse(img, box, color[, thickness[, lineType]] ) ->img

cv.ellipse(img,(256,256),(100,50),0,0,180,255,-1)

画多边形

cv.polylines()
pts: 多边形曲线数组
isClosed: 指示绘制的多段线是否闭合的标志。 如果它们是闭合的,该函数会从每条曲线的最后一个顶点到它的第一个顶点绘制一条线。

在画不规则区域时用到过. 例如在统计人流时,因为统计的区域是不规则的,也许就是个凹凸这样的形状,需要在图像上画出统计的区域.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cv.polylines(img, pts, isClosed, color[, thickness[, lineType[, shift]]]	) ->img

# 要绘制多边形,首先需要顶点坐标。
# 将这些点放入一个形状为 ROWSx1x2 的数组中,其中 ROWS 是顶点数,它应该是 int32 类型。
# 在这里,我们用黄色绘制一个带有四个顶点的小多边形。
pts = np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)
pts = pts.reshape((-1,1,2))
cv.polylines(img,[pts],True,(0,255,255))

print(pts)
[[[10 5]]

[[20 30]]

[[70 20]]

[[50 10]]]

画字

cv.putText()
org: 图像中文本字符串的左下角

1
2
3
4
cv.putText(	img, text, org, fontFace, fontScale, color[, thickness[, lineType[, bottomLeftOrigin]]]	) ->	img

font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(img,'OpenCV',(10,500), font, 4,(255,255,255),2,cv.LINE_AA)

参考链接

jwt 可以有 2 类 token, 一类叫ACCESS_TOKEN用来请求接口, 一类叫REFRESH_TOKEN用来刷新ACCESS_TOKEN. ACCESS_TOKEN 时间比较短, 例如 10 分钟,REFRESH_TOKEN 比较久比如 7 天, 这样就可以保证 7 天内保持登录.
还有个参数ROTATE_REFRESH_TOKENS, 当刷新ACCESS_TOKENREFRESH_TOKEN也刷新,
这样可以一直保持登录,除非在REFRESH_TOKEN 时间内都没刷新.

后端

1
2
3
4
5
6
7
from datetime import timedelta

SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=30),
"REFRESH_TOKEN_LIFETIME": timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True, #
}

前端

封装 fetch 来处理发送 token, 刷新 token. 里面用了 chrome.storage 是浏览器扩展的接口, 自己用户换成 localStorage 就可以了

1
2
3
4
5
6
7
8
9
10
11
# api.js
import { baseUrl, http } from "./utils/fetchInstance.js";

async function login(data) {
return http(baseUrl + "/api/token/", {
method: "POST",
body: data,
});
}

export { login };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# utils/fetchInstance.js
let baseUrl = "http://127.0.0.1:8000";
let refreshTokenUrl = baseUrl + "/api/token/refresh/";
let isRefreshing = false; // 用于拦截鉴权失败的请求
let TOKEN_KEY = "token";

let getToken = async function () {
// 避免重复发起刷新
if (isRefreshing) return;

let items = await chrome.storage.local.get(TOKEN_KEY);
if (items[TOKEN_KEY]) {
items = items[TOKEN_KEY];
} else {
items = null;
}
return items;
};

let setToken = function (value) {
console.log("setToken:", value);
chrome.storage.local.set({ token: value });
};

let clearToken = function () {
chrome.storage.local.clear();
};

let refreshToken = async (authTokens) => {
isRefreshing = true;
try {
let init = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ refresh: authTokens.refresh }),
};
let response = await fetch(refreshTokenUrl, init);
let data = await response.json();
if (response.ok) {
await setToken(data);
} else {
clearToken();
}

isRefreshing = false;
return data;
} catch (error) {
isRefreshing = false;
return null;
}
};

async function checkStatus(response) {
if (response.ok) {
return response;
} else {
if (response.status === 401) {
let authTokens = await getToken();
if (authTokens) {
await refreshToken(authTokens);
}
}
return Promise.reject(response);
}
}

function parseJSON(response) {
return response.json();
}

async function http(url, option = {}) {
let headers = {
"Content-Type": "application/json",
};

option.headers = option.headers || headers;
option.mode = option.mode || "cors";

let authTokens = await getToken();
if (authTokens) {
option.headers["Authorization"] = `Bearer ${authTokens?.access}`;
}

option.method = (option.method || "get").toLocaleLowerCase();
if (
option.method === "post" ||
option.method === "put" ||
option.method === "delete"
) {
// 非get类请求传参时,需要将参数挂在body上
option.body = JSON.stringify(option.body);
}

return fetch(url, option)
.then(checkStatus)
.then(parseJSON)
.then((data) => data);
}

export { baseUrl, http };

后台有时会有需要复制一些内容,所以需要一键复制功能来方便复制.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.contrib import admin
from django.utils.html import format_html

from .models import Note


@admin.register(Note)
class NoteAdmin(admin.ModelAdmin):
list_display = [..., "one_click_copy" ]


@admin.display(description='操作')
def one_click_copy(self, obj):
# 需要https
return format_html(f"""
<a id="{obj.id}" data-value="{obj.serial_no}" href="javascript:;"
onclick=";let text=document.getElementById('{obj.id}').getAttribute('data-value');
navigator.clipboard.writeText(text);">一键复制</a>
""")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@admin.register(Note)
class NoteAdmin(admin.ModelAdmin):
list_display = [..., "one_click_copy" ]

@admin.display(description='操作')
def one_click_copy(self, obj):
return format_html(f"""
<a id="{obj.id}" data-value="{obj.serial_no}" href="javascript:;"
onclick=";
const oInput = document.createElement('textarea');
const text=window.location.protocol+'//'+window.location.host+'/core/note/detail/'
+document.getElementById('{obj.id}').getAttribute('data-value');
oInput.value = text;
oInput.readOnly = 'readOnly';
document.body.appendChild(oInput);
oInput.select();
document.execCommand('copy');
oInput.blur();
document.body.removeChild(oInput);">一键复制</a>
""")

参考链接

方法1. ModelAdmin.raw_id_fields

会弹出该外键的模型的列表选择

1
2
class ArticleAdmin(admin.ModelAdmin):
raw_id_fields = ("newspaper",)

方法2. ModelAdmin.autocomplete_fields

会让你输入搜索的关键字来选择搜索结果

1
2
3
4
5
6
class QuestionAdmin(admin.ModelAdmin):
ordering = ['date_created']
search_fields = ['question_text']

class ChoiceAdmin(admin.ModelAdmin):
autocomplete_fields = ['question']

参考链接