FlanChanXwO commited on
Commit
8079d5c
·
verified ·
1 Parent(s): 58f2885

Upload docs/INFERENCE.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. docs/INFERENCE.md +152 -3
docs/INFERENCE.md CHANGED
@@ -1,3 +1,152 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:a1d9f97f474c43b51e84cd78be7f2baee422c61a1f8df8b0ac76a9e18a55fa2b
3
- size 4417
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 推理指南 (INFERENCE.md)
2
+
3
+ > **English summary**: Model I/O — input `[batch,3,64,160]` float32 RGB (values 0..1),
4
+ > output `[batch,4,10]` logits; argmax per position gives the 4 digits. Preprocessing is only
5
+ > three steps: convert to RGB, resize to 160x64 (bilinear), divide by 255. No grayscale, no
6
+ > denoise, no mean/std normalization. Runs in ~10-30ms on CPU. Go integration uses
7
+ > `onnxruntime_go` with NCHW layout (all R, then all G, then all B).
8
+
9
+ ## 规格速览
10
+
11
+ | 项 | 值 |
12
+ |---|---|
13
+ | 输入名 | `input` |
14
+ | 输入形状 | `[batch, 3, 64, 160]` |
15
+ | 输入类型 | float32, 值域 [0,1] (RGB) |
16
+ | 输出名 | `logits` |
17
+ | 输出形状 | `[batch, 4, 10]` |
18
+ | 解码 | 每位置 argmax → 数字 |
19
+ | ONNX opset | 18 |
20
+
21
+ **预处理只有 3 步,无其他**:
22
+ 1. 转 RGB
23
+ 2. 缩放到 160x64(双线性)
24
+ 3. 除以 255
25
+
26
+ 不做灰度、不做去噪、不做归一化均值方差。
27
+
28
+ ## Python
29
+
30
+ ```bash
31
+ pip install onnxruntime pillow numpy
32
+ ```
33
+
34
+ ```python
35
+ import numpy as np
36
+ import onnxruntime as ort
37
+ from PIL import Image
38
+
39
+ _SESSION = None
40
+
41
+ def get_session(model_path="model.onnx"):
42
+ global _SESSION
43
+ if _SESSION is None:
44
+ _SESSION = ort.InferenceSession(model_path, providers=["CPUExecutionProvider"])
45
+ return _SESSION
46
+
47
+ def solve_captcha(img_bytes_or_path, model_path="model.onnx"):
48
+ sess = get_session(model_path)
49
+ im = Image.open(img_bytes_or_path).convert("RGB").resize((160, 64), Image.BILINEAR)
50
+ x = np.asarray(im, dtype=np.float32) / 255.0
51
+ x = x.transpose(2, 0, 1)[None] # (1,3,64,160)
52
+ logits = sess.run(None, {"input": x})[0] # (1,4,10)
53
+ return "".join(str(int(logits[0, p].argmax())) for p in range(4))
54
+
55
+ # 用法
56
+ code = solve_captcha("captcha.png") # "4821"
57
+ ```
58
+
59
+ ## Go (onnxruntime_go)
60
+
61
+ 依赖: `github.com/yalue/onnxruntime_go` + onnxruntime 共享库 (.dylib/.so)。
62
+
63
+ ```go
64
+ package main
65
+
66
+ import (
67
+ "bytes"
68
+ "fmt"
69
+ "image"
70
+ _ "image/png"
71
+
72
+ "golang.org/x/image/draw"
73
+ ort "github.com/yalue/onnxruntime_go"
74
+ )
75
+
76
+ const (
77
+ inW, inH = 160, 64
78
+ nDigits = 4
79
+ )
80
+
81
+ type Solver struct {
82
+ sess *ort.Session[float32]
83
+ in *ort.Tensor[float32]
84
+ out *ort.Tensor[float32]
85
+ }
86
+
87
+ func NewSolver(modelPath, libPath string) (*Solver, error) {
88
+ if libPath != "" {
89
+ ort.SetSharedLibraryPath(libPath)
90
+ }
91
+ if err := ort.InitializeEnvironment(); err != nil {
92
+ return nil, err
93
+ }
94
+ in, err := ort.NewEmptyTensor[float32](ort.NewShape(1, 3, inH, inW))
95
+ if err != nil { return nil, err }
96
+ out, err := ort.NewEmptyTensor[float32](ort.NewShape(1, nDigits, 10))
97
+ if err != nil { in.Destroy(); return nil, err }
98
+ sess, err := ort.NewSession[float32](modelPath,
99
+ []string{"input"}, []string{"logits"},
100
+ []*ort.Tensor[float32]{in}, []*ort.Tensor[float32]{out})
101
+ if err != nil { in.Destroy(); out.Destroy(); return nil, err }
102
+ return &Solver{sess, in, out}, nil
103
+ }
104
+
105
+ func (s *Solver) Solve(png []byte) (string, error) {
106
+ src, _, err := image.Decode(bytes.NewReader(png))
107
+ if err != nil { return "", err }
108
+ // RGB 缩放到 160x64 (双线性)
109
+ dst := image.NewRGBA(image.Rect(0, 0, inW, inH))
110
+ draw.ApproxBiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
111
+
112
+ data := s.in.GetData()
113
+ idx := 0
114
+ for y := 0; y < inH; y++ {
115
+ for x := 0; x < inW; x++ {
116
+ r, g, b, _ := dst.At(x, y).RGBA()
117
+ data[idx+0] = float32(r>>8) / 255.0
118
+ data[idx+1] = float32(g>>8) / 255.0
119
+ data[idx+2] = float32(b>>8) / 255.0
120
+ idx += 3
121
+ }
122
+ }
123
+ if err := s.sess.Run(); err != nil { return "", err }
124
+
125
+ got := s.out.GetData()
126
+ code := make([]byte, nDigits)
127
+ for p := 0; p < nDigits; p++ {
128
+ best := 0
129
+ for c := 1; c < 10; c++ {
130
+ if got[p*10+c] > got[p*10+best] { best = c }
131
+ }
132
+ code[p] = '0' + byte(best)
133
+ }
134
+ return string(code), nil
135
+ }
136
+ ```
137
+
138
+ > 注意: `onnxruntime_go.NewSession` 从文件读取模型字节。若用 `go:embed`
139
+ > 内嵌模型,改用 `NewSessionWithONNXData` 传字节。
140
+
141
+ ## 与 southplus-cli 集成
142
+
143
+ 项目内已有 `internal/captcha/onnx_solver.go` 骨架,需按上面的 Go 示例适配:
144
+ - 输入 3 通道 (非 1)
145
+ - 去掉旧 CTC/去噪逻辑
146
+ - 输出 4 位置 argmax (非 CTC 解码)
147
+
148
+ ## 性能
149
+
150
+ - 单张推理: 约 5-15ms (CPU, M1/现代 x86)
151
+ - 全流程 (加载 + 预处理 + 推理): 约 10-30ms
152
+ - 无 GPU 依赖, 内存占用 < 50MB