import cv2
import numpy as np
import math
import os
import struct
from tqdm import tqdm
from glob import glob
PALETTE = np.random.randint(0, 255, 255, 3, dtype=np.uint32)
模型输入尺寸(W, H),用于把模型坐标缩放回原图
MODEL_IN_W = 608
MODEL_IN_H = 736
zh
imagespath = "/ai/DataSets/OD_FSD_zh/TI_test/rm/4/image/"
imagespath = "/ai/DataSets/OD_FSD_zh/TI_test/ppchen/DVR-20250804152834-2382380-PLR/image/"
savepath = "/ai/zhdata/multiyolov5_point_v2/test_images/out"
zh1 = 0
def readTensor(tensorFile):
global zh1
tensor = open(tensorFile,'rb')
infer_data = np.fromfile(tensor, dtype=np.int32)
print(infer_data.shape)
soltnum = int(len(infer_data) / 20)
im0 = cv2.imread(imagespath + tensorFile.split('/')-1:-8 + '.bmp')
if im0 is None:
print("读取图片失败:", tensorFile)
return
h0, w0 = im0.shape:2
sx = w0 / float(MODEL_IN_W)
sy = h0 / float(MODEL_IN_H)
point_all =\[\]
for i in range(soltnum):
point_dict1={}
x1 = max(int(infer_data20\*i+6 * sx), 0)
y1 = max(int(infer_data20\*i+7 * sy), 0)
x2 = max(int(infer_data20\*i+8 * sx), 0)
y2 = max(int(infer_data20\*i+9 * sy), 0)
x3 = max(int(infer_data20\*i+10 * sx), 0)
y3 = max(int(infer_data20\*i+11 * sy), 0)
x4 = max(int(infer_data20\*i+12 * sx), 0)
y4 = max(int(infer_data20\*i+13 * sy), 0)
zh = struct.unpack('!f',int(bin(infer_data20\*i+4)2:,2).to_bytes(4,byteorder='big'))
point_dict1"conf" = struct.unpack('!f',int(bin(infer_data20\*i+4)2:,2).to_bytes(4,byteorder='big'))0
point_dict1"isOccupied" = struct.unpack('!f',int(bin(infer_data20\*i+14)2:,2).to_bytes(4,byteorder='big'))0
point_dict1"isVIP" = struct.unpack('!f',int(bin(infer_data20\*i+15)2:,2).to_bytes(4,byteorder='big'))0
point_dict1"iswoman" = struct.unpack('!f',int(bin(infer_data20\*i+16)2:,2).to_bytes(4,byteorder='big'))0
point_dict1"isdisabled" = struct.unpack('!f',int(bin(infer_data20\*i+17)2:,2).to_bytes(4,byteorder='big'))0
point_dict1"ischarging" = struct.unpack('!f',int(bin(infer_data20\*i+18)2:,2).to_bytes(4,byteorder='big'))0
point_dict1"step" = struct.unpack('!f',int(bin(infer_data20\*i+19)2:,2).to_bytes(4,byteorder='big'))0
point_dict1"name" = str(struct.unpack('!f',int(bin(infer_data20\*i+5)2:,2).to_bytes(4,byteorder='big'))0)
point_dict1"delrule" = 0
point_dict1"pointx" = x1,x2,x3,x4
point_dict1"pointy" = y1,y2,y3,y4
if x3 > 1000 or x4 > 1000 or x1 > 1000 or x2 > 1000 or y3 > 1000 or y4 > 1000 or y1 > 1000 or y2 > 1000:
zh1 +=1
kk = struct.unpack('!f',int(bin(infer_data20\*i+17)2:,2).to_bytes(4,byteorder='big'))0
print("数据解析错误"+ tensorFile + str(x3) + '--'+ str(zh1))
break
point_all.append(point_dict1)
end = len(point_all)
for i in range(len(point_all)):
if point_alli"delrule" == 0:
for j in range(i+1,end):
#简单就是求入口顶点之间的距离
xi1 = point_alli'pointx'0
yi1 = point_alli'pointy'0
xi2 = point_alli'pointx'1
yi2 = point_alli'pointy'1
xj1 = point_allj'pointx'0
yj1 = point_allj'pointy'0
xj2 = point_allj'pointx'1
yj2 = point_allj'pointy'1
if (abs(xi1 - xj1) + abs(yi1 - yj1)) < 40 or (abs(xi2 - xj2) + abs(yi2 - yj2)) < 40:
point_allj"delrule" = 1
for i in range(len(point_all)):
if point_alli"delrule" == 0:
line1 = point_all\[i'pointx'0,point_alli'pointy'0,point_alli'pointx'3,point_alli'pointy'3]
line2 = point_all\[i'pointx'1,point_alli'pointy'1,point_alli'pointx'2,point_alli'pointy'2]
vec1 =line1\[2-line10,line13-line11]
vec2 =line2\[2-line20,line23-line21]
#计算向量的点积和模长
dot_product = vec10 * vec20 + vec11 * vec21
m1 = math.sqrt(vec10**2 + vec11**2) + 0.000000000001
m2 = math.sqrt(vec20**2 + vec21**2) + 0.000000000001
val = dot_product/(m1 * m2)
if val > 1:
val = 1
if val < -1:
val = -15
radians = math.acos(val)
du = math.degrees(radians)
if du > 20:
point_alli"delrule" = 2
if 1:
for point_i in point_all:
if point_i"delrule" == 0:
if point_i"conf" > 0.45:#0.45
print(point_i"conf")
cv2.putText(im0, f'{point_i"conf":.3f}',
(point_i"pointx"0 + 6, point_i"pointy"0 + 6),
cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 255, 0),2) #置信度
cv2.putText(im0, point_i"name",
(point_i"pointx"0 + 6, point_i"pointy"0 + 30),
cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 255, 0),2) #类别
if float(point_i"isOccupied")> 0.1: #0.5
cv2.putText(im0, "Occ :" + f'{point_i"isOccupied":.3f}',
(point_i"pointx"0 + 6, point_i"pointy"0 + 54),
cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 255, 0),2) #是否被占用
if float(point_i"isVIP") > 0.5:
cv2.putText(im0, "VIP :" + f'{point_i"isVIP":.3f}',
(point_i"pointx"0 + 6, point_i"pointy"0 + 78),
cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 255, 0),2) #是否VIP车位
if float(point_i"iswoman") > 0.5:
cv2.putText(im0, "woman :" + f'{point_i"iswoman":.3f}',
(point_i"pointx"0 + 6, point_i"pointy"0 + 102),
cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 255, 0),2) #是否女性车位
if float(point_i"isdisabled") > 0.5:
cv2.putText(im0, "disab :" + f'{point_i"isdisabled":.3f}',
(point_i"pointx"0 + 6, point_i"pointy"0 + 126),
cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 255, 0),2) #是否残疾人车位
if float(point_i"ischarging") > 0.5:
cv2.putText(im0, "charg :" + f'{point_i"ischarging":.3f}',
(point_i"pointx"0 + 6, point_i"pointy"0 + 150),
cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 255, 0),2) #是否充电车位
if float(point_i"step") > 0.5:
cv2.putText(im0, "step :" + f'{point_i"step":.3f}',
(point_i"pointx"0 + 6, point_i"pointy"0 + 174),
cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 255, 0),2) #是否阶梯形车位
cv2.arrowedLine(im0, (point_i"pointx"0, point_i"pointy"0),(point_i"pointx"1, point_i"pointy"1), (0, 255, 0), 1, cv2.LINE_AA)
cv2.arrowedLine(im0, (point_i"pointx"1, point_i"pointy"1),(point_i"pointx"2, point_i"pointy"2), (255, 255, 0), 1, cv2.LINE_AA)
cv2.arrowedLine(im0, (point_i"pointx"2, point_i"pointy"2),(point_i"pointx"3, point_i"pointy"3), (255, 255, 0), 1, cv2.LINE_AA)
cv2.arrowedLine(im0, (point_i"pointx"3, point_i"pointy"3),(point_i"pointx"0, point_i"pointy"0), (255, 255, 0), 1, cv2.LINE_AA)
else:
cv2.putText(im0, f'{point_i"conf":.3f}',
(point_i"pointx"0 + 6, point_i"pointy"0 + 6),
cv2.FONT_HERSHEY_PLAIN, 2, (0, 0, 255),3)
cv2.arrowedLine(im0, (point_i"pointx"0, point_i"pointy"0),(point_i"pointx"1, point_i"pointy"1), (0, 0, 255), 1, cv2.LINE_AA)
cv2.arrowedLine(im0, (point_i"pointx"1, point_i"pointy"1),(point_i"pointx"2, point_i"pointy"2), (0, 0, 255), 1, cv2.LINE_AA)
cv2.arrowedLine(im0, (point_i"pointx"2, point_i"pointy"2),(point_i"pointx"3, point_i"pointy"3), (0, 0, 255), 1, cv2.LINE_AA)
cv2.arrowedLine(im0, (point_i"pointx"3, point_i"pointy"3),(point_i"pointx"0, point_i"pointy"0), (0, 0, 255), 1, cv2.LINE_AA)
if point_i"delrule" == 1:
cv2.putText(im0, f'{point_i"conf":.3f}',
(point_i"pointx"0 + 6, point_i"pointy"0 + 6),
cv2.FONT_HERSHEY_PLAIN, 2, (0, 0, 255),3)
cv2.arrowedLine(im0, (point_i"pointx"0, point_i"pointy"0),(point_i"pointx"1, point_i"pointy"1), (0, 0, 0), 1, cv2.LINE_AA)
cv2.arrowedLine(im0, (point_i"pointx"1, point_i"pointy"1),(point_i"pointx"2, point_i"pointy"2), (0, 0, 0), 1, cv2.LINE_AA)
cv2.arrowedLine(im0, (point_i"pointx"2, point_i"pointy"2),(point_i"pointx"3, point_i"pointy"3), (0, 0, 0), 1, cv2.LINE_AA)
cv2.arrowedLine(im0, (point_i"pointx"3, point_i"pointy"3),(point_i"pointx"0, point_i"pointy"0), (0, 0, 0), 1, cv2.LINE_AA)
if point_i"delrule" == 2:
cv2.putText(im0, f'{point_i"conf":.3f}',
(point_i"pointx"0 + 6, point_i"pointy"0 + 6),
cv2.FONT_HERSHEY_PLAIN, 2, (0, 0, 255),3)
cv2.arrowedLine(im0, (point_i"pointx"0, point_i"pointy"0),(point_i"pointx"1, point_i"pointy"1), (0, 0, 0), 1, cv2.LINE_AA)
cv2.arrowedLine(im0, (point_i"pointx"1, point_i"pointy"1),(point_i"pointx"2, point_i"pointy"2), (0, 0, 0), 1, cv2.LINE_AA)
cv2.arrowedLine(im0, (point_i"pointx"2, point_i"pointy"2),(point_i"pointx"3, point_i"pointy"3), (0, 0, 0), 1, cv2.LINE_AA)
cv2.arrowedLine(im0, (point_i"pointx"3, point_i"pointy"3),(point_i"pointx"0, point_i"pointy"0), (0, 0, 0), 1, cv2.LINE_AA)
zh = "/ai/TopViewMul/4/psd_out/" + tensorFile.split('/')-1:-8 + '.bmp'
print(zh)
cv2.imwrite(zh, im0)
zh
for binpath in tqdm(glob(os.path.join("/ai/DataSets/OD_FSD_zh/TI_test/rm/2/psd/",'*.bin'))):
readTensor(binpath)
num=0
for binpath in tqdm(glob(os.path.join("/ai/TopViewMul/4/out_480_544_2039/psd/",'*.bin'))):
readTensor(binpath)
num+=1
if num==100:
print('exit')
exit(0)
从你现在这张"修改后"的图看,框已经不集中在左上角了,但仍明显偏向左边。这类现象更像是可视化坐标系和真实图像尺寸不一致,而不是解码本身的问题。
关键原因(结合你的工程):
- 下位机推理的输入会被强制 resize 到模型输入尺寸
在 main.c 的 readInput() 里,直接把原始 BMP resize 到 ioBufDesc->inWidth/Height:
- 也就是说,模型输出坐标一定是"模型输入尺寸坐标系",而不是原图尺寸坐标系。
- draw_tensor2psd.py 直接把坐标画到原图
脚本没有做任何缩放,默认坐标就是原图像素。
如果原图是大分辨率(比如 1472×1216),而模型输入是 608×736,那么坐标会被压缩到左上角区域。
- 你这套模型的输入尺寸其实是 W=608, H=736
- train_608_736.py 里 img-size 标注为 HW,即 H=736, W=608
- TIDL 配置 inWidth=608 inHeight=736 也一致
如果你在可视化里当成 736×608(W=736),横向缩放会变小,框会偏左。
结论
现在剩下的主要问题很可能在可视化脚本:
需要把输出点坐标从"模型输入尺寸坐标系"缩放到"原图尺寸坐标系"。
你可以在 draw_tensor2psd.py 里这样做(逻辑示意):
假设模型输入尺寸(一定要用 W=608, H=736)
in_w, in_h = 608, 736
原图尺寸
h0, w0 = im0.shape:2
sx = w0 / in_w
sy = h0 / in_h
x1 = int(x1 * sx); y1 = int(y1 * sy)
x2 = int(x2 * sx); y2 = int(y2 * sy)
x3 = int(x3 * sx); y3 = int(y3 * sy)
x4 = int(x4 * sx); y4 = int(y4 * sy)
或者更简单:
把 im0 换成和模型输入一致的 608×736 图,这样就无需缩放。
为什么这很符合你的现象
- 修改后"不再聚左上角",说明解码坐标已基本正确
- 但仍偏左,说明坐标系仍然比原图小(典型就是 608×736 画在大图)
如果你方便,再补充两点我可以进一步确认:
- 这张图的原始 BMP 实际尺寸(宽、高)
- 你想把结果画在原图还是模型输入图
我也可以直接帮你把 draw_tensor2psd.py 改成自适应缩放版本。
已改好 draw_tensor2psd.py,现在会把模型输出坐标缩放回原图再画:
- 增加 MODEL_IN_W/ MODEL_IN_H(默认 608×736)
- 读取原图尺寸后计算 sx/sy,对四个点坐标做缩放
- 增加了图片读取失败的保护
你现在只需要确认:
模型输入尺寸是否确实是 W=608, H=736(从你的 TIDL 配置和训练脚本看是对的)。
如果不是,改这两个常量就行。
建议你再跑一张图验证位置是否对齐。
如果还有偏移,我可以继续根据你的原图尺寸和输入尺寸做更精细的映射(比如考虑裁剪或 letterbox)。
下面给你一套到公司后可直接照做的调试思路,按优先级从"最可能"到"次可能"排:
1) 先确认坐标系是否一致(最常见)
目标:验证"模型输出坐标"是基于哪张图的尺寸。
检查点:
- 原图尺寸 W0,H0(BMP 实际大小)
- 模型输入尺寸 Win,Hin(TIDL inWidth/inHeight)
- draw_tensor2psd.py 里 MODEL_IN_W/H 是否与 Win/Hin 一致
- 训练脚本 img-size 标注为 HW,所以输入是 W=608, H=736,不要弄反
快速验证:
- 把 draw_tensor2psd.py 临时改成直接画在 resize 后的图上(把原图 resize 到 608×736 再画),如果这样位置正确,说明问题就是"原图坐标系映射",不是模型输出问题。
2) 判断是不是"缩放比例 + 误差"
如果框整体偏左/偏上/偏小:
- 打印 sx = W0/Win, sy = H0/Hin
- 观察是否 水平和垂直比例不同(W0/H0 不是 608/736 的等比)
- 如果图像原始比例和模型输入比例不一致,不做 letterbox,直接 resize,会造成几何扭曲;你现在的映射应与这个扭曲一致(用不同的 sx/sy),否则会偏。
3) 检查是否存在 letterbox / padding
你当前下位机预处理是 直接 resize,没有 letterbox。
但如果你在上位机或训练时用了 letterbox,且模型输出是基于 letterbox 坐标,那么必须考虑 padding 偏移。
验证方法:
- 在下位机 readInput() 里确认是 resizeImage(你当前确实是 resize)
- 如果以后切换成 letterbox,draw_tensor2psd.py 需增加 padX/padY 的反向补偿
4) 校验 bin 输出是否是"输入尺度"
最直接的方法:
- 在下位机输出前打印 一个 bbox 的点坐标范围(比如 min/max)
- 如果输出点范围接近 Win/Hin,说明坐标是输入尺度
- 如果输出点范围接近 W0/H0,说明已经是原图尺度,那你就不该再缩放
5) 排除 W/H 颠倒
极易踩坑:
一秒验证:
- 把 MODEL_IN_W/H 交换试一次
如果交换后框明显"更接近",说明之前 W/H 搞反了。
6) 逐步定位到"一个样本"
建议用一张固定样本,不要批量。
把这 4 个值记下来:
- 原图大小 W0/H0
- 模型输入 Win/Hin
- 任意一个输出点 x1,y1
- 画到图上后的 x1',y1'
确认映射公式:
x' = x1 * W0/Win
y' = y1 * H0/Hin
7) 如果仍不对,排查输出解码
可能是:
- 某个 head 的 inWidth/inHeight 仍然没设置
- 输出张量顺序/通道顺序与模型不一致(虽然你已确认 head 索引,但通道组织也可能变)
- 量化 scale 不一致导致数值偏差(一般不会全局偏移这么明显)
验证:
- 打印 yolov5params.inWidth\[\]/inHeight\[\](每个 head)
- 打印 ioBufDesc.outWidth/outHeight/outNumChannels 是否与训练期模型一致
如果你明天运行后仍不对,直接把以下三项发我,我可以继续定位:
-
原图尺寸 W0×H0
-
模型输入尺寸 Win×Hin
-
一张可视化结果(最好能同时给出原图和画图后的图)
我可以继续帮你一步步定位到底是"坐标系问题"还是"解码问题"。