phpwind-captcha-ocr / docs /en /INFERENCE.md
FlanChanXwO's picture
docs: focus model card on PHPWind captcha OCR
3739346 verified
|
Raw
History Blame Contribute Delete
4.01 kB
# Inference Guide
## Scope
Use this guide only for PHPWind deployments you own or are explicitly
authorized to test. The examples perform local image inference only; they do
not automate account login flows or bypass access controls.
## Specification
| Item | Value |
|---|---|
| Input name | `input` |
| Input shape | `[batch, 3, 64, 160]` |
| Input type | float32, range [0,1] (RGB) |
| Output name | `logits` |
| Output shape | `[batch, 4, 10]` |
| Decode | argmax per position → digit |
| ONNX opset | 18 |
Preprocessing is only three steps: RGB → resize to 160x64 (bilinear) → divide by 255.
No grayscale, no denoising, no mean/std normalization.
## Python
```bash
pip install onnxruntime pillow numpy
```
```python
import numpy as np
import onnxruntime as ort
from PIL import Image
_SESSION = None
def get_session(model_path="model.onnx"):
global _SESSION
if _SESSION is None:
_SESSION = ort.InferenceSession(model_path, providers=["CPUExecutionProvider"])
return _SESSION
def solve_captcha(img_bytes_or_path, model_path="model.onnx"):
sess = get_session(model_path)
im = Image.open(img_bytes_or_path).convert("RGB").resize((160, 64), Image.BILINEAR)
x = np.asarray(im, dtype=np.float32) / 255.0
x = x.transpose(2, 0, 1)[None] # (1,3,64,160)
logits = sess.run(None, {"input": x})[0] # (1,4,10)
return "".join(str(int(logits[0, p].argmax())) for p in range(4))
code = solve_captcha("captcha.png") # "4821"
```
## Go (onnxruntime_go)
Deps: `github.com/yalue/onnxruntime_go` + the onnxruntime shared library (.dylib/.so).
```go
package main
import (
"bytes"
"image"
_ "image/png"
"golang.org/x/image/draw"
ort "github.com/yalue/onnxruntime_go"
)
const (
inW, inH = 160, 64
nDigits = 4
)
type Solver struct {
sess *ort.Session[float32]
in *ort.Tensor[float32]
out *ort.Tensor[float32]
}
func NewSolver(modelPath, libPath string) (*Solver, error) {
if libPath != "" { ort.SetSharedLibraryPath(libPath) }
if err := ort.InitializeEnvironment(); err != nil { return nil, err }
in, err := ort.NewEmptyTensor[float32](ort.NewShape(1, 3, inH, inW))
if err != nil { return nil, err }
out, err := ort.NewEmptyTensor[float32](ort.NewShape(1, nDigits, 10))
if err != nil { in.Destroy(); return nil, err }
sess, err := ort.NewSession[float32](modelPath,
[]string{"input"}, []string{"logits"},
[]*ort.Tensor[float32]{in}, []*ort.Tensor[float32]{out})
if err != nil { in.Destroy(); out.Destroy(); return nil, err }
return &Solver{sess, in, out}, nil
}
func (s *Solver) Solve(png []byte) (string, error) {
src, _, err := image.Decode(bytes.NewReader(png))
if err != nil { return "", err }
dst := image.NewRGBA(image.Rect(0, 0, inW, inH))
draw.ApproxBiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
data := s.in.GetData()
idx := 0
// NCHW layout: all R, then all G, then all B
for y := 0; y < inH; y++ { for x := 0; x < inW; x++ { r,_,_,_ := dst.At(x,y).RGBA(); data[idx]=float32(r>>8)/255; idx++ } }
for y := 0; y < inH; y++ { for x := 0; x < inW; x++ { _,g,_,_ := dst.At(x,y).RGBA(); data[idx]=float32(g>>8)/255; idx++ } }
for y := 0; y < inH; y++ { for x := 0; x < inW; x++ { _,_,b,_ := dst.At(x,y).RGBA(); data[idx]=float32(b>>8)/255; idx++ } }
if err := s.sess.Run(); err != nil { return "", err }
got := s.out.GetData()
code := make([]byte, nDigits)
for p := 0; p < nDigits; p++ {
best := 0
for c := 1; c < 10; c++ { if got[p*10+c] > got[p*10+best] { best = c } }
code[p] = '0' + byte(best)
}
return string(code), nil
}
```
> Note: `onnxruntime_go.NewSession` reads the model from a file. If you embed it with
> `go:embed`, use `NewSessionWithONNXData` with the bytes instead.
## Performance
- Single inference: ~5-15ms (CPU, M1/modern x86)
- Full flow (load + preprocess + infer): ~10-30ms
- No GPU needed, memory < 50MB