gomyck-tools 1.5.6__py3-none-any.whl → 1.5.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ctools/authcode_validator.py +379 -372
- ctools/ml/__init__.py +4 -0
- ctools/ml/interface/__init__.py +4 -0
- ctools/ml/interface/image_processor.py +42 -0
- ctools/ml/model_loader.py +36 -0
- ctools/util/file_crypto.py +92 -0
- {gomyck_tools-1.5.6.dist-info → gomyck_tools-1.5.7.dist-info}/METADATA +5 -4
- {gomyck_tools-1.5.6.dist-info → gomyck_tools-1.5.7.dist-info}/RECORD +11 -9
- ctools/ml/image_process.py +0 -121
- ctools/ml/img_extractor.py +0 -59
- ctools/ml/ppi.py +0 -275
- {gomyck_tools-1.5.6.dist-info → gomyck_tools-1.5.7.dist-info}/WHEEL +0 -0
- {gomyck_tools-1.5.6.dist-info → gomyck_tools-1.5.7.dist-info}/licenses/LICENSE +0 -0
- {gomyck_tools-1.5.6.dist-info → gomyck_tools-1.5.7.dist-info}/top_level.txt +0 -0
ctools/ml/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2026/1/20 15:02'
|
|
5
|
+
|
|
6
|
+
import base64
|
|
7
|
+
from io import BytesIO
|
|
8
|
+
|
|
9
|
+
from PIL import Image
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ImageProcessor:
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def image_show(base64_str):
|
|
16
|
+
"""base64字符串转PIL Image并显示"""
|
|
17
|
+
from io import BytesIO
|
|
18
|
+
import base64
|
|
19
|
+
img_data = base64.b64decode(base64_str)
|
|
20
|
+
img = Image.open(BytesIO(img_data))
|
|
21
|
+
img.show()
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def image_to_base64(img: Image.Image, format='PNG') -> str:
|
|
25
|
+
"""PIL Image -> base64 字符串"""
|
|
26
|
+
buffer = BytesIO()
|
|
27
|
+
img.save(buffer, format=format)
|
|
28
|
+
return base64.b64encode(buffer.getvalue()).decode('utf-8')
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def crop_to_base64(img: Image.Image, coordinate) -> str:
|
|
32
|
+
"""
|
|
33
|
+
img: PIL Image
|
|
34
|
+
coordinate: [xmin, ymin, xmax, ymax] 图像坐标
|
|
35
|
+
return: 裁剪区域的 base64
|
|
36
|
+
"""
|
|
37
|
+
if not isinstance(coordinate, (list, tuple)) or len(coordinate) != 4:
|
|
38
|
+
raise ValueError("coordinate 必须是长度为 4 的 list 或 tuple")
|
|
39
|
+
|
|
40
|
+
xmin, ymin, xmax, ymax = [int(c) for c in coordinate]
|
|
41
|
+
cropped = img.crop((xmin, ymin, xmax, ymax))
|
|
42
|
+
return ImageProcessor.image_to_base64(cropped)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2026/2/3 16:07'
|
|
5
|
+
|
|
6
|
+
import tempfile
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from ctools.util.file_crypto import FileCrypto
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TempDecryptedDir:
|
|
14
|
+
"""
|
|
15
|
+
解密到临时目录的上下文工具
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, encrypted_file: str, password: str):
|
|
19
|
+
self.encrypted_file = encrypted_file
|
|
20
|
+
self.password = password
|
|
21
|
+
self.temp_dir: Optional[Path] = None
|
|
22
|
+
|
|
23
|
+
def __enter__(self) -> str:
|
|
24
|
+
self.temp_dir = Path(
|
|
25
|
+
tempfile.mkdtemp(prefix="secure_model_")
|
|
26
|
+
)
|
|
27
|
+
FileCrypto.decrypt_to_directory(
|
|
28
|
+
encrypted_file=self.encrypted_file,
|
|
29
|
+
output_dir=str(self.temp_dir),
|
|
30
|
+
password=self.password
|
|
31
|
+
)
|
|
32
|
+
return str(self.temp_dir)
|
|
33
|
+
|
|
34
|
+
def __exit__(self, exc_type, exc, tb):
|
|
35
|
+
if self.temp_dir and self.temp_dir.exists():
|
|
36
|
+
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2026/2/3 16:07'
|
|
5
|
+
|
|
6
|
+
import tarfile
|
|
7
|
+
import tempfile
|
|
8
|
+
import hashlib
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
11
|
+
import secrets
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FileCrypto:
|
|
15
|
+
"""
|
|
16
|
+
文件 / 目录 加密解密工具
|
|
17
|
+
- AES-256-GCM
|
|
18
|
+
- 支持目录整体加密
|
|
19
|
+
"""
|
|
20
|
+
NONCE_SIZE = 12
|
|
21
|
+
KEY_SIZE = 32
|
|
22
|
+
@staticmethod
|
|
23
|
+
def derive_key(password: str) -> bytes:
|
|
24
|
+
"""
|
|
25
|
+
从口令派生固定长度 key(简化版,工程可控)
|
|
26
|
+
"""
|
|
27
|
+
return hashlib.sha256(password.encode("utf-8")).digest()
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def encrypt_directory(
|
|
31
|
+
src_dir: str,
|
|
32
|
+
output_file: str,
|
|
33
|
+
password: str
|
|
34
|
+
) -> None:
|
|
35
|
+
"""
|
|
36
|
+
加密整个目录
|
|
37
|
+
"""
|
|
38
|
+
src_dir = Path(src_dir).resolve()
|
|
39
|
+
output_file = Path(output_file).resolve()
|
|
40
|
+
|
|
41
|
+
with tempfile.NamedTemporaryFile(suffix=".tar", delete=False) as tmp_tar:
|
|
42
|
+
tar_path = Path(tmp_tar.name)
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
# 1. 打包目录
|
|
46
|
+
with tarfile.open(tar_path, "w") as tar:
|
|
47
|
+
tar.add(src_dir, arcname=".")
|
|
48
|
+
|
|
49
|
+
# 2. 加密
|
|
50
|
+
key = FileCrypto.derive_key(password)
|
|
51
|
+
aesgcm = AESGCM(key)
|
|
52
|
+
nonce = secrets.token_bytes(FileCrypto.NONCE_SIZE)
|
|
53
|
+
|
|
54
|
+
data = tar_path.read_bytes()
|
|
55
|
+
encrypted = aesgcm.encrypt(nonce, data, None)
|
|
56
|
+
|
|
57
|
+
output_file.write_bytes(nonce + encrypted)
|
|
58
|
+
|
|
59
|
+
finally:
|
|
60
|
+
tar_path.unlink(missing_ok=True)
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def decrypt_to_directory(
|
|
64
|
+
encrypted_file: str,
|
|
65
|
+
output_dir: str,
|
|
66
|
+
password: str
|
|
67
|
+
) -> None:
|
|
68
|
+
"""
|
|
69
|
+
解密文件并解包到指定目录
|
|
70
|
+
"""
|
|
71
|
+
encrypted_file = Path(encrypted_file).resolve()
|
|
72
|
+
output_dir = Path(output_dir).resolve()
|
|
73
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
|
|
75
|
+
raw = encrypted_file.read_bytes()
|
|
76
|
+
nonce = raw[:FileCrypto.NONCE_SIZE]
|
|
77
|
+
ciphertext = raw[FileCrypto.NONCE_SIZE:]
|
|
78
|
+
|
|
79
|
+
key = FileCrypto.derive_key(password)
|
|
80
|
+
aesgcm = AESGCM(key)
|
|
81
|
+
|
|
82
|
+
data = aesgcm.decrypt(nonce, ciphertext, None)
|
|
83
|
+
|
|
84
|
+
with tempfile.NamedTemporaryFile(suffix=".tar", delete=False) as tmp_tar:
|
|
85
|
+
tmp_tar.write(data)
|
|
86
|
+
tar_path = Path(tmp_tar.name)
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
with tarfile.open(tar_path, "r") as tar:
|
|
90
|
+
tar.extractall(output_dir)
|
|
91
|
+
finally:
|
|
92
|
+
tar_path.unlink(missing_ok=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gomyck-tools
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.7
|
|
4
4
|
Summary: A tools collection for python development by hao474798383
|
|
5
5
|
Author-email: gomyck <hao474798383@163.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -32,21 +32,22 @@ Requires-Dist: redis==7.1.0
|
|
|
32
32
|
Requires-Dist: uvloop>=0.22.1
|
|
33
33
|
Requires-Dist: pyyaml>=6.0.3
|
|
34
34
|
Requires-Dist: uiautomation>=2.0.20
|
|
35
|
+
Requires-Dist: req>=1.0.0
|
|
35
36
|
Provides-Extra: kwc
|
|
36
37
|
Requires-Dist: imageio>=2.37.0; extra == "kwc"
|
|
37
38
|
Requires-Dist: wordcloud>=1.9.4; extra == "kwc"
|
|
38
39
|
Requires-Dist: jieba==0.42.1; extra == "kwc"
|
|
39
|
-
Requires-Dist: paddlepaddle==3.
|
|
40
|
+
Requires-Dist: paddlepaddle==3.3.0; extra == "kwc"
|
|
40
41
|
Provides-Extra: cut
|
|
41
42
|
Requires-Dist: jieba==0.42.1; extra == "cut"
|
|
42
|
-
Requires-Dist: paddlepaddle==3.
|
|
43
|
+
Requires-Dist: paddlepaddle==3.3.0; extra == "cut"
|
|
43
44
|
Provides-Extra: ml
|
|
44
45
|
Requires-Dist: scikit-learn>=1.7.1; extra == "ml"
|
|
45
46
|
Requires-Dist: pandas>=2.3.2; extra == "ml"
|
|
46
47
|
Requires-Dist: lxml>=6.0.1; extra == "ml"
|
|
47
48
|
Requires-Dist: xlrd>=2.0.2; extra == "ml"
|
|
48
49
|
Provides-Extra: xj-plate-server
|
|
49
|
-
Requires-Dist: paddlepaddle==3.
|
|
50
|
+
Requires-Dist: paddlepaddle==3.3.0; extra == "xj-plate-server"
|
|
50
51
|
Dynamic: license-file
|
|
51
52
|
|
|
52
53
|
# Gomyck-Tools
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
ctools/__init__.py,sha256=7EtnbO7mi1iYQeyU4CLusYrwO96_dC7eR7jboyWAqvg,1089
|
|
2
2
|
ctools/application.py,sha256=QFLbwmjznz2HAwVyph9PhzMpJUU5R82eMaVcw5UDSMk,15899
|
|
3
3
|
ctools/aspect.py,sha256=lXrpeu_F3w6v2Hu2yOwQIRGqfDN25_H-1YyW6fPt_mw,1667
|
|
4
|
-
ctools/authcode_validator.py,sha256=
|
|
4
|
+
ctools/authcode_validator.py,sha256=YGQFxNcGNO5Att2fC1LuH0GfmVoRPC5eef82OEC0_fg,10722
|
|
5
5
|
ctools/call.py,sha256=TFFC8PqvCu0PS0XelmV4QXdXezQiUsEacxg3RgKvdwE,1572
|
|
6
6
|
ctools/cdate.py,sha256=OhKAaQfo2Rxd3Jx3g9AfPsaISRoLkstqZdaGT4ZZr_I,3096
|
|
7
7
|
ctools/cdebug.py,sha256=_mihZRCEx_bi7Kv_QPjP4MPLNFrl-GR1Y_irTgOP7OU,4021
|
|
@@ -48,9 +48,10 @@ ctools/database/database.py,sha256=IQzPw2fOJh0Xn_OdSMeEOXBEV9YCRDoszg-dwJPXTTU,7
|
|
|
48
48
|
ctools/geo/__init__.py,sha256=OkUaZv5ckkXJFNbRyFZqkX5m3GxTueEGEBU99_jJQNE,98
|
|
49
49
|
ctools/geo/coord_trans.py,sha256=UWCU1wnrTDU1aLSVAwmiHOeH7Pu-Dp8lDLAngDG48NM,3761
|
|
50
50
|
ctools/geo/douglas_rarefy.py,sha256=bJo6TwNxPa-7-8MOi8MULxeqnz4cvIJN-oXqBDWNAVM,4883
|
|
51
|
-
ctools/ml/
|
|
52
|
-
ctools/ml/
|
|
53
|
-
ctools/ml/
|
|
51
|
+
ctools/ml/__init__.py,sha256=hVR5_6Nh7y5VL-3oijXYuquxm754pB1o4nbiBjWb9aM,97
|
|
52
|
+
ctools/ml/model_loader.py,sha256=U7DK3azEPZJ97g9swV9hFHQ887SD5F4V40uMvBySsKA,915
|
|
53
|
+
ctools/ml/interface/__init__.py,sha256=hVR5_6Nh7y5VL-3oijXYuquxm754pB1o4nbiBjWb9aM,97
|
|
54
|
+
ctools/ml/interface/image_processor.py,sha256=mzUuURjv9Ktqgu2ovL0nxcyt2nTDH3-aiVUSDz-g2eM,1172
|
|
54
55
|
ctools/office/__init__.py,sha256=wum34b8YJg0qD7uKdDEbozSE8RIxWqTVa44CCIZyqPU,98
|
|
55
56
|
ctools/office/cword.py,sha256=bIthKmf0oBqjcdkrU5hFDAPp66ZrjeMNzjIxOlMPeCc,837
|
|
56
57
|
ctools/office/word_fill.py,sha256=ZoTxz0-agEy5atIRWqYmQZz82nVr2zz9JebSTMkulIo,18214
|
|
@@ -70,6 +71,7 @@ ctools/util/cklock.py,sha256=uS1g8H7jtQ5dINKe-k23osQ1sjvZSZrkXGwH6qKcgNo,3234
|
|
|
70
71
|
ctools/util/compile_util.py,sha256=Nybh3vnkurIKnPnubdYzigjnzFu4GaTMKPvqFdibxmE,510
|
|
71
72
|
ctools/util/config_util.py,sha256=FBacCqQ1kPtRl1VEz0IsAuxI0zdpqUiuBv11G8kiDLE,1230
|
|
72
73
|
ctools/util/env_config.py,sha256=L98G9LPdpD7Yl5XbA_-KfkcA4mDunQoKiYtK5w7QR-s,1790
|
|
74
|
+
ctools/util/file_crypto.py,sha256=qgNBrqTcl61V0fVMOz3JID91FfbDcwd4uyp01vuE7sw,2258
|
|
73
75
|
ctools/util/html_soup.py,sha256=rnr8M3gn3gQGo-wNaNFXDjdzp8AAkv9o4yqfIIfO-zw,1567
|
|
74
76
|
ctools/util/http_util.py,sha256=cx0FRnPLFdJ0mF9UYphl40SZj68fqG30Q0udku9hZIE,769
|
|
75
77
|
ctools/util/image_process.py,sha256=nqJOi2p8wLe8wRsfkH99MyEYSjE9i4fthxBJwrrZVB8,835
|
|
@@ -85,8 +87,8 @@ ctools/web/ctoken.py,sha256=qsxrQY7IWgpPe5HyiDDnf50vsRX27QXMWPzam7bGu_Y,2646
|
|
|
85
87
|
ctools/web/download_util.py,sha256=v0JTXiED1bvoWFfwfd-LD5s7_aoRQ0lCkaGwSnSp7WI,1954
|
|
86
88
|
ctools/web/params_util.py,sha256=eJDV3PSq-ZHb8UZf6xqs8kOhbyZzits1H9yPoUBIDXg,828
|
|
87
89
|
ctools/web/upload_util.py,sha256=z1QQCi4SFx08jrAQH5-Y_ShiM4MghuD_5Qz6V9KK_4U,1076
|
|
88
|
-
gomyck_tools-1.5.
|
|
89
|
-
gomyck_tools-1.5.
|
|
90
|
-
gomyck_tools-1.5.
|
|
91
|
-
gomyck_tools-1.5.
|
|
92
|
-
gomyck_tools-1.5.
|
|
90
|
+
gomyck_tools-1.5.7.dist-info/licenses/LICENSE,sha256=X25ypfH9E6VTht2hcO8k7LCSdHUcoG_ALQt80jdYZfY,547
|
|
91
|
+
gomyck_tools-1.5.7.dist-info/METADATA,sha256=ahdr3pJFSRaeugXRe8qzwMYVgns8YA6XhQfRLNJhB-o,1983
|
|
92
|
+
gomyck_tools-1.5.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
93
|
+
gomyck_tools-1.5.7.dist-info/top_level.txt,sha256=-MiIH9FYRVKp1i5_SVRkaI-71WmF1sZSRrNWFU9ls3s,7
|
|
94
|
+
gomyck_tools-1.5.7.dist-info/RECORD,,
|
ctools/ml/image_process.py
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import random
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
from PIL import Image, ImageDraw, ImageFont
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def preprocess(img, img_size):
|
|
8
|
-
mean = [0.485, 0.456, 0.406]
|
|
9
|
-
std = [0.229, 0.224, 0.225]
|
|
10
|
-
img = resize(img, img_size)
|
|
11
|
-
img = img[:, :, ::-1].astype('float32') # RGB->BGR
|
|
12
|
-
img = normalize(img, mean, std)
|
|
13
|
-
img = img.transpose((2, 0, 1)) # hwc -> chw
|
|
14
|
-
#show_preprocess(img, mean, std)
|
|
15
|
-
return img[np.newaxis, :]
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def resize(img, target_size):
|
|
19
|
-
"""
|
|
20
|
-
img: numpy.ndarray (H,W,3) BGR or RGB
|
|
21
|
-
return: numpy.ndarray (target_size, target_size, 3)
|
|
22
|
-
"""
|
|
23
|
-
img = Image.fromarray(img)
|
|
24
|
-
img = img.resize((target_size, target_size), Image.BILINEAR)
|
|
25
|
-
return np.array(img)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def normalize(img, mean, std):
|
|
29
|
-
img = img / 255.0
|
|
30
|
-
mean = np.array(mean)[np.newaxis, np.newaxis, :]
|
|
31
|
-
std = np.array(std)[np.newaxis, np.newaxis, :]
|
|
32
|
-
img -= mean
|
|
33
|
-
img /= std
|
|
34
|
-
return img
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def show_preprocess(chw_img, mean, std):
|
|
38
|
-
"""
|
|
39
|
-
chw_img: (3, H, W), float32, normalized
|
|
40
|
-
"""
|
|
41
|
-
img = chw_img.copy()
|
|
42
|
-
# 1. CHW -> HWC
|
|
43
|
-
img = img.transpose(1, 2, 0)
|
|
44
|
-
# 2. de-normalize
|
|
45
|
-
img = img * std + mean
|
|
46
|
-
img = img * 255.0
|
|
47
|
-
# 3. clamp + uint8
|
|
48
|
-
img = np.clip(img, 0, 255).astype(np.uint8)
|
|
49
|
-
Image.fromarray(img).show()
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def draw_bbox(img, result, threshold=0.5, save_name='res.jpg', scale_factor=None, im_size=320, class_names=None):
|
|
53
|
-
draw = ImageDraw.Draw(img)
|
|
54
|
-
|
|
55
|
-
if scale_factor is not None:
|
|
56
|
-
h_scale, w_scale = scale_factor[0]
|
|
57
|
-
else:
|
|
58
|
-
h_scale = w_scale = 1.
|
|
59
|
-
|
|
60
|
-
# 类别颜色随机但固定
|
|
61
|
-
category_colors = {}
|
|
62
|
-
if class_names is not None:
|
|
63
|
-
for cls in class_names:
|
|
64
|
-
category_colors[cls] = tuple(random.randint(0, 255) for _ in range(3))
|
|
65
|
-
|
|
66
|
-
# 字体
|
|
67
|
-
try:
|
|
68
|
-
font = ImageFont.truetype("arial.ttf", 15)
|
|
69
|
-
except:
|
|
70
|
-
font = ImageFont.load_default()
|
|
71
|
-
|
|
72
|
-
for res in result:
|
|
73
|
-
cat_id, score, bbox = res[0], res[1], res[2:]
|
|
74
|
-
if score < threshold:
|
|
75
|
-
continue
|
|
76
|
-
|
|
77
|
-
# 归一化 bbox -> 模型输入尺寸
|
|
78
|
-
xmin = bbox[0] * im_size
|
|
79
|
-
ymin = bbox[1] * im_size
|
|
80
|
-
xmax = bbox[2] * im_size
|
|
81
|
-
ymax = bbox[3] * im_size
|
|
82
|
-
|
|
83
|
-
# 模型输入尺寸 -> 原图
|
|
84
|
-
xmin = xmin / w_scale
|
|
85
|
-
xmax = xmax / w_scale
|
|
86
|
-
ymin = ymin / h_scale
|
|
87
|
-
ymax = ymax / h_scale
|
|
88
|
-
|
|
89
|
-
# 类别名和颜色
|
|
90
|
-
if class_names is not None:
|
|
91
|
-
class_name = class_names[int(cat_id)]
|
|
92
|
-
color = category_colors[class_name]
|
|
93
|
-
text = f"{class_name}:{score:.2f}"
|
|
94
|
-
|
|
95
|
-
# 获取文字尺寸,兼容所有版本 Pillow
|
|
96
|
-
try:
|
|
97
|
-
text_width, text_height = font.getsize(text) # 旧版 / 大部分版本
|
|
98
|
-
except AttributeError:
|
|
99
|
-
# Pillow 9.2+ 推荐用 getbbox
|
|
100
|
-
bbox_font = font.getbbox(text)
|
|
101
|
-
text_width = bbox_font[2] - bbox_font[0]
|
|
102
|
-
text_height = bbox_font[3] - bbox_font[1]
|
|
103
|
-
|
|
104
|
-
text_origin = (xmin, max(0, ymin - text_height)) # 框上方显示
|
|
105
|
-
draw.text(text_origin, text, fill=color, font=font)
|
|
106
|
-
else:
|
|
107
|
-
color = 'red'
|
|
108
|
-
|
|
109
|
-
# 画矩形框
|
|
110
|
-
draw.rectangle([xmin, ymin, xmax, ymax], outline=color, width=2)
|
|
111
|
-
|
|
112
|
-
img.save(save_name)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def image_show(base64_str):
|
|
116
|
-
"""base64字符串转PIL Image并显示"""
|
|
117
|
-
from io import BytesIO
|
|
118
|
-
import base64
|
|
119
|
-
img_data = base64.b64decode(base64_str)
|
|
120
|
-
img = Image.open(BytesIO(img_data))
|
|
121
|
-
img.show()
|
ctools/ml/img_extractor.py
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# -*- coding: UTF-8 -*-
|
|
3
|
-
__author__ = 'haoyang'
|
|
4
|
-
__date__ = '2026/1/20 15:02'
|
|
5
|
-
|
|
6
|
-
import base64
|
|
7
|
-
from io import BytesIO
|
|
8
|
-
|
|
9
|
-
class ClassRegionBase64ExtractorPIL:
|
|
10
|
-
def __init__(self, class_names, target_classes=None, threshold=0.5):
|
|
11
|
-
"""
|
|
12
|
-
class_names: 模型类别列表
|
|
13
|
-
target_classes: 只截取的类别名列表,None 表示全部
|
|
14
|
-
threshold: 置信度阈值
|
|
15
|
-
"""
|
|
16
|
-
self.class_names = class_names
|
|
17
|
-
self.target_classes = target_classes
|
|
18
|
-
self.threshold = threshold
|
|
19
|
-
|
|
20
|
-
@staticmethod
|
|
21
|
-
def image_to_base64(img, format='PNG'):
|
|
22
|
-
"""
|
|
23
|
-
PIL Image -> base64 字符串
|
|
24
|
-
"""
|
|
25
|
-
buffer = BytesIO()
|
|
26
|
-
img.save(buffer, format=format)
|
|
27
|
-
return base64.b64encode(buffer.getvalue()).decode('utf-8')
|
|
28
|
-
|
|
29
|
-
def extract(self, img, results, scale_factor=None, im_size=320):
|
|
30
|
-
"""
|
|
31
|
-
img: PIL Image
|
|
32
|
-
results: 模型输出 [[cat_id, score, xmin, ymin, xmax, ymax], ...]
|
|
33
|
-
scale_factor: np.array([[h_scale, w_scale]]) 或 None
|
|
34
|
-
im_size: 模型输入尺寸
|
|
35
|
-
return: List[Dict] -> [{"class": class_name, "score": score, "base64": base64_str}, ...]
|
|
36
|
-
"""
|
|
37
|
-
outputs = []
|
|
38
|
-
for res in results:
|
|
39
|
-
cat_id, score, bbox = res[0], res[1], res[2:]
|
|
40
|
-
if score < self.threshold or cat_id > len(self.class_names) - 1:
|
|
41
|
-
continue
|
|
42
|
-
class_name = self.class_names[int(cat_id)]
|
|
43
|
-
if self.target_classes is not None and class_name not in self.target_classes:
|
|
44
|
-
continue
|
|
45
|
-
xmin = bbox[0]
|
|
46
|
-
ymin = bbox[1]
|
|
47
|
-
xmax = bbox[2]
|
|
48
|
-
ymax = bbox[3]
|
|
49
|
-
# 裁剪
|
|
50
|
-
pil_img_threadsafe = img.copy()
|
|
51
|
-
cropped = pil_img_threadsafe.crop((xmin, ymin, xmax, ymax))
|
|
52
|
-
# 转 base64
|
|
53
|
-
b64_str = self.image_to_base64(cropped)
|
|
54
|
-
outputs.append({
|
|
55
|
-
"class": class_name,
|
|
56
|
-
"score": float(score),
|
|
57
|
-
"base64": b64_str
|
|
58
|
-
})
|
|
59
|
-
return outputs
|