kendryte / nncase

Open deep learning compiler stack for Kendryte AI accelerators ✨
Apache License 2.0
752 stars 183 forks source link

kmodel部署效果差 #1246

Closed lishengyang30 closed 2 months ago

lishengyang30 commented 2 months ago

镜像文件:CanMV-K230_micropython_v0.7_sdk_v1.6_nncase_v2.8.3.img.gz 编译 compile_options.preprocess = False [Uploading issue.zip…]()

在pc上使用int16精度onnx和kmodel相似度达到98.9%,校正集是内存连续的,但是在k230上kmodel进行目标检测计算出框的坐标混乱,不准去,甚至有负值

curioyang commented 2 months ago

@lishengyang30 压缩包有问题,重新上传一下

lishengyang30 commented 2 months ago

@lishengyang30 压缩包有问题,重新上传一下

issue.zip

lishengyang30 commented 2 months ago

除了目标检测还有关键点检测,总共13个关键点,指针上三个点,其余是刻度值

lishengyang30 commented 2 months ago

除了目标检测还有关键点检测,总共13个关键点,指针上三个点,其余是刻度值

yb 关键点预测的置信度很低,也有负值

PermissionDenied7335 commented 2 months ago

根据你的模型结构,1x3x640x640输入,1xNx8400输出,没估计错的话是yolov8系列里的模型吧 如果是yolov8,那么它的输入格式应该是NCHW,RGB,取值直接从0-255映射到0-1

def preprocess(self, im):
    """
    Prepares input image before inference.

    Args:
        im (torch.Tensor | List(np.ndarray)): BCHW for tensor, [(HWC) x B] for list.
    """
    not_tensor = not isinstance(im, torch.Tensor)
    if not_tensor:
        im = np.stack(self.pre_transform(im))
        im = im[..., ::-1].transpose((0, 3, 1, 2))  # BGR to RGB, BHWC to BCHW, (n, 3, h, w)
        im = np.ascontiguousarray(im)  # contiguous
        im = torch.from_numpy(im)

    im = im.to(self.device)
    im = im.half() if self.model.fp16 else im.float()  # uint8 to fp16/32
    if not_tensor:
        im /= 255  # 0 - 255 to 0.0 - 1.0
    return im

code from ultralytic/engine/predictor.py , class BasePredictor     但是在你的代码中,首先缩放时没有用letterbox,不过这一点问题倒也不大

#生成的数据需要做的预处理 ≈ onnx预处理 - 根据预处理参数设置的,包含在kmodel中预处理
#onnx预处理:bgr,padding,reisze,transpose,normalization,dequantize,3维度转4维度
#kmodel中包含的预处理:rgb->bgr,dequantize,normalization
img_data = Image.open(img_paths[i]).convert('RGB')
#为了省事,这里没有用padding
img_data = img_data.resize((shape[3], shape[2]), Image.BILINEAR)
img_data = np.asarray(img_data, dtype=np.float32)
img_data = np.transpose(img_data, (2, 0, 1))
data.append([img_data[np.newaxis, ...]])

_code from onnx2kmodel.py , function generatedata     最大的问题在于模型预处理配置

compile_options.preprocess = False
if compile_options.preprocess:
    compile_options.input_type = "float32"  # "uint8" "float32"
    compile_options.input_shape = [1,3, 640, 640]
    compile_options.input_range = [0, 255]
    compile_options.input_layout = "NCHW"  # "NHWC"
    compile_options.swapRB = True
    compile_options.mean = [130.66777943, 122.26035665, 119.29115591]
    compile_options.std = [57.31256191 ,56.19649718, 57.98391517]
    compile_options.letterbox_value = 0
    compile_options.output_layout = "NCHW"  # "NHWC"

_code from onnx2kmodel.py , function compilekmodel

首先,preprocess置为False,预处理被关闭了,之后的配置将不会生效; 其次,即使preprocess=True,input_type="float32"会对模型部署造成不便,虽然在加载校准集时将uint8转为float32也可以完成编译,不会报错,但会导致推理之前必须从uint8到float32进行强制类型转换,消耗端侧CPU时间; 然后,swapRB=True,这是完全不必要也不能要的,图片读取时默认就是RGB顺序,YoloV8的Channel维度也是RGB顺序,一致,不要交换; 最后,归一化参数不当,归一化运算公式为(data-Mean)/Std,所以,要么把mean全部置0,std全部置255,从而映射到0-1,要么在input_type=uint8的前提下把mean全部置0,std全部置1,指定input_range=[0,1]即可自动映射

PermissionDenied7335 commented 2 months ago

镜像文件:CanMV-K230_micropython_v0.7_sdk_v1.6_nncase_v2.8.3.img.gz 编译 compile_options.preprocess = False Uploading issue.zip…

在pc上使用int16精度onnx和kmodel相似度达到98.9%,校正集是内存连续的,但是在k230上kmodel进行目标检测计算出框的坐标混乱,不准去,甚至有负值

另外,98.9%对于yolo而言很低。yolo需要输出0-640的坐标,即使把后面0-1的置信度抹没了都能有95%以上的相似度,我这测试时99.9999%都有明显的精度损失(但还算好使)

curioyang commented 2 months ago

你判断内存是否连续的地方都错了 image 测试来看数据是不连续的 image

从 @PermissionDenied7335 截图的第一段代码中也能看到,官方都是会进行numpy连续性重排的

curioyang commented 2 months ago

@lishengyang30 更新后的代码

def generate_data(shape, batch, calib_dir):
    #获取所有校正集图片名称
    img_paths = [os.path.join(calib_dir, p) for p in os.listdir(calib_dir)]
    data = []
    for i in range(batch):
        assert i < len(img_paths), "calibration images not enough."
        #生成的数据需要做的预处理 ≈ onnx预处理 - 根据预处理参数设置的,包含在kmodel中预处理
        #onnx预处理:bgr,padding,reisze,transpose,normalization,dequantize,3维度转4维度
        #kmodel中包含的预处理:rgb->bgr,dequantize,normalization
        img_data = Image.open(img_paths[i]).convert('RGB')
        #为了省事,这里没有用padding
        img_data = img_data.resize((shape[3], shape[2]), Image.BILINEAR)
        img_data = np.asarray(img_data, dtype=np.float32)
        img_data = np.transpose(img_data, (2, 0, 1))
        print(f"C-contiguous: {img_data.flags['C_CONTIGUOUS']}")
        img_data=np.ascontiguousarray(img_data)
        print(f"C-contiguous: {img_data.flags['C_CONTIGUOUS']}")
        data.append([img_data[np.newaxis, ...]])

这个函数代码不全,自己补一下

lishengyang30 commented 2 months ago

你判断内存是否连续的地方都错了 image 测试来看数据是不连续的 image

从 @PermissionDenied7335 截图的第一段代码中也能看到,官方都是会进行numpy连续性重排的

首先表示感谢,没有想到在那个地方就要连续性重拍,只想到append之后连续性重排就行了,在AI demo实例上没有看到连续性重排,是image.Image读取单个图片的时候或者pl.get_frame时,已经默认是连续的的了?

lishengyang30 commented 2 months ago

根据你的模型结构,1x3x640x640输入,1xNx8400输出,没估计错的话是yolov8系列里的模型吧 如果是yolov8,那么它的输入格式应该是NCHW,RGB,取值直接从0-255映射到0-1

def preprocess(self, im):
    """
    Prepares input image before inference.

    Args:
        im (torch.Tensor | List(np.ndarray)): BCHW for tensor, [(HWC) x B] for list.
    """
    not_tensor = not isinstance(im, torch.Tensor)
    if not_tensor:
        im = np.stack(self.pre_transform(im))
        im = im[..., ::-1].transpose((0, 3, 1, 2))  # BGR to RGB, BHWC to BCHW, (n, 3, h, w)
        im = np.ascontiguousarray(im)  # contiguous
        im = torch.from_numpy(im)

    im = im.to(self.device)
    im = im.half() if self.model.fp16 else im.float()  # uint8 to fp16/32
    if not_tensor:
        im /= 255  # 0 - 255 to 0.0 - 1.0
    return im

code from ultralytic/engine/predictor.py , class BasePredictor     但是在你的代码中,首先缩放时没有用letterbox,不过这一点问题倒也不大

#生成的数据需要做的预处理 ≈ onnx预处理 - 根据预处理参数设置的,包含在kmodel中预处理
#onnx预处理:bgr,padding,reisze,transpose,normalization,dequantize,3维度转4维度
#kmodel中包含的预处理:rgb->bgr,dequantize,normalization
img_data = Image.open(img_paths[i]).convert('RGB')
#为了省事,这里没有用padding
img_data = img_data.resize((shape[3], shape[2]), Image.BILINEAR)
img_data = np.asarray(img_data, dtype=np.float32)
img_data = np.transpose(img_data, (2, 0, 1))
data.append([img_data[np.newaxis, ...]])

_code from onnx2kmodel.py , function generatedata     最大的问题在于模型预处理配置

compile_options.preprocess = False
if compile_options.preprocess:
    compile_options.input_type = "float32"  # "uint8" "float32"
    compile_options.input_shape = [1,3, 640, 640]
    compile_options.input_range = [0, 255]
    compile_options.input_layout = "NCHW"  # "NHWC"
    compile_options.swapRB = True
    compile_options.mean = [130.66777943, 122.26035665, 119.29115591]
    compile_options.std = [57.31256191 ,56.19649718, 57.98391517]
    compile_options.letterbox_value = 0
    compile_options.output_layout = "NCHW"  # "NHWC"

_code from onnx2kmodel.py , function compilekmodel

首先,preprocess置为False,预处理被关闭了,之后的配置将不会生效; 其次,即使preprocess=True,input_type="float32"会对模型部署造成不便,虽然在加载校准集时将uint8转为float32也可以完成编译,不会报错,但会导致推理之前必须从uint8到float32进行强制类型转换,消耗端侧CPU时间; 然后,swapRB=True,这是完全不必要也不能要的,图片读取时默认就是RGB顺序,YoloV8的Channel维度也是RGB顺序,一致,不要交换; 最后,归一化参数不当,归一化运算公式为(data-Mean)/Std,所以,要么把mean全部置0,std全部置255,从而映射到0-1,要么在input_type=uint8的前提下把mean全部置0,std全部置1,指定input_range=[0,1]即可自动映射

回答很详细,十分感谢,对于归一化我还有疑问,input_range和均值、方差的设定有关系吗?官方文档上有一句话:人脸检测kmodel实际输入从sensor中获取,数据类型为 uint8 ,所以input_type = 'uint8',input_range = [0,255]或[0,1]均可。

PermissionDenied7335 commented 2 months ago

有关系的,传感器的uint8数据先根据input_range映射到float32,再根据mean和std归一化

curioyang commented 2 months ago

你判断内存是否连续的地方都错了 image 测试来看数据是不连续的 image 从 @PermissionDenied7335 截图的第一段代码中也能看到,官方都是会进行numpy连续性重排的

首先表示感谢,没有想到在那个地方就要连续性重拍,只想到append之后连续性重排就行了,在AI demo实例上没有看到连续性重排,是image.Image读取单个图片的时候或者pl.get_frame时,已经默认是连续的的了?

之前没怎么出现过这种问题,不确定是不是因为numpy或者PIL版本导致,加上肯定是没啥问题的

lishengyang30 commented 2 months ago

使用onnx,框的位置和关键点的位置都没有问题,但是转换kmodel时,可以识别物体,框的位置正确,但是关键点的位置错误,有负值

PermissionDenied7335 commented 2 months ago

先关闭量化,生成cpu模型,用模拟器跑出来看有没有问题

curioyang commented 2 months ago

使用onnx,框的位置和关键点的位置都没有问题,但是转换kmodel时,可以识别物体,框的位置正确,但是关键点的位置错误,有负值

问题有解决么,你的场景应该都是上面这种仪器吧? 校正集如果都是这种比较统一的 quant_method 就用NoClip 试试