From 7dae9a0901ace02e0e2a8ecf187795a4cd4f94c6 Mon Sep 17 00:00:00 2001 From: Admin Date: Fri, 17 Jul 2026 11:02:00 +0000 Subject: [PATCH] fix: avoid GetOutputShape CGO crash by using fixed output dimensions --- internal/captcha/handler.go | 102 ++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 5 deletions(-) diff --git a/internal/captcha/handler.go b/internal/captcha/handler.go index 9be53f9..a0f07f5 100644 --- a/internal/captcha/handler.go +++ b/internal/captcha/handler.go @@ -311,13 +311,16 @@ func (h *Handler) Math(imageBase64 string) (result string, err error) { return "", err } - // 获取输出形状 - outputShape, err := sess.GetOutputShape(0) - if err != nil { - return "", fmt.Errorf("获取输出形状失败: %v", err) + // 不调用 GetOutputShape,直接从 output 大小推断 + // Math 模型输出固定为 [51, 1, 19] = 969 个元素 或 [19, 1, 51] = 969 个元素 + outputLen := len(output) + if outputLen == 0 { + return "", fmt.Errorf("模型输出为空") } - expr := decodeMath(output, outputShape) + // 推断输出形状:已知 numChars=51, batch=1, timesteps=19, total=969 + // 尝试两种格式 + expr := decodeMathFromOutput(output) if expr == "" { return "", fmt.Errorf("无法识别表达式") } @@ -388,6 +391,95 @@ func preprocessMath(img image.Image) ([]float32, error) { return pixels, nil } +func decodeMathFromOutput(output []float32) string { + // Math 模型输出固定: numChars=51, batch=1, timesteps=19, total=969 + // 可能的格式: [51, 1, 19] 或 [19, 1, 51] + numChars := 51 + batch := 1 + timesteps := 19 + totalElems := numChars * batch * timesteps // 969 + + if len(output) != totalElems { + // 尝试推断 timesteps + timesteps = len(output) / numChars + if timesteps <= 0 { + return "" + } + } + + // 尝试两种格式 + // 格式1: [T, B, C] = [19, 1, 51] + result1 := decodeMathFormat(output, timesteps, batch, numChars, true) + // 格式2: [C, B, T] = [51, 1, 19] + result2 := decodeMathFormat(output, timesteps, batch, numChars, false) + + // 返回更长的结果(更可能是正确的) + if len(result1) >= len(result2) { + return result1 + } + return result2 +} + +func decodeMathFormat(output []float32, timesteps, batch, numChars int, isTBC bool) string { + var data []float32 + + if isTBC { + // 格式: [T, B, C] -> [B, T, C] + data = make([]float32, batch*timesteps*numChars) + for t := 0; t < timesteps; t++ { + for b := 0; b < batch; b++ { + for c := 0; c < numChars; c++ { + srcIdx := t*batch*numChars + b*numChars + c + dstIdx := b*timesteps*numChars + t*numChars + c + if srcIdx < len(output) && dstIdx < len(data) { + data[dstIdx] = output[srcIdx] + } + } + } + } + } else { + // 格式: [C, B, T] -> [B, T, C] + data = make([]float32, batch*timesteps*numChars) + for c := 0; c < numChars; c++ { + for b := 0; b < batch; b++ { + for t := 0; t < timesteps; t++ { + srcIdx := c*batch*timesteps + b*timesteps + t + dstIdx := b*timesteps*numChars + t*numChars + c + if srcIdx < len(output) && dstIdx < len(data) { + data[dstIdx] = output[srcIdx] + } + } + } + } + } + + // CTC 解码 + result := "" + lastIdx := -1 + + for t := 0; t < timesteps; t++ { + maxIdx := 0 + maxProb := float32(-math.MaxFloat32) + + for c := 0; c < numChars; c++ { + idx := t*numChars + c + if idx < len(data) && data[idx] > maxProb { + maxProb = data[idx] + maxIdx = c + } + } + + if maxIdx != 0 && maxIdx != lastIdx { + if maxIdx-1 < len(mathChars) { + result += string(mathChars[maxIdx-1]) + } + } + lastIdx = maxIdx + } + + return result +} + func decodeMath(output []float32, outputShape []int64) string { // 原版逻辑: // if preds.shape[1] == 1: # Batch size is 1 at dim 1 implies [T, B, C]