计算一组Tensor的直方图C算法实现

本文介绍了Tensor量化过程中的KL散度算法,用于衡量信息损失。通过读取和处理150528维度的列向量数据,绘制并分析原始直方图,然后进行数据平移以验证直方图分布的影响。讨论了数据分布对于AI训练和推理的重要性,并展示了不同直方图平移效果。

Tensor量化中,计算信息度损失的的一个重要算法叫做KL散度算法,关于其介绍请参考博客:

模型量化中的KL散度扫盲_papaofdoudou的博客-优快云博客_kl三度

本文介绍其程序实现:

首先构造一组TENSOR向量,维度为150528的列向量。

观察其原始的直方图分布,其分布特点如下图所示:

程序实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <math.h>
#include <sys/ioctl.h>
 
#define DBG(fmt, ...)   do { printf("%s line %d, "fmt"\n", __func__, __LINE__, ##__VA_ARGS__); } while (0)
#define min(x,y) ({ \
    typeof(x) _x = (x);    \
    typeof(y) _y = (y);    \
    (void) (&_x == &_y);    \
    _x < _y ? _x : _y; })
 
int get_tensor_from_txt_file(char *file_path, float **buf)
{
    int len = 0;
    static float *memory = NULL;
    static int max_len = 10 * 1024 * 1024;
    FILE *fp = NULL;
 
    if(memory == NULL)
    {
        memory = (float*) malloc(max_len * sizeof(float));
    }
    
    if((fp = fopen(file_path, "r")) == NULL)
    {
        DBG("open tensor error.");
        exit(-1);
    }
 
    while(!feof(fp))
    {
        fscanf(fp, "%f", &memory[len ++]);
    }
 
    *buf = (float*)malloc(len * sizeof(float));
    if(len == 0 || *buf == NULL)
    {
        DBG("read tensor error, len %d, *buf %p", len, *buf);
        exit(-1);
    }
    memcpy(*buf, memory, len * sizeof(float));
 
    fclose(fp);
 
    return len;
}
 
int main(int argc, char **argv)
{
    FILE *file;
 
    DBG("in");
 
    if(argc != 3)
    {
        DBG("input error, you should use this program like that: program tensor binsnum.");
        exit(-1);
    }
 
    int tensor0_len;
    float *tensor0_dat;
    int bins = atoi(argv[2]);
    
    tensor0_len = 0;
    tensor0_dat = NULL;
 
    tensor0_len = get_tensor_from_txt_file(argv[1], &tensor0_dat);
    DBG("tensor len %d.", tensor0_len);

    float absmax = 0.f;
    int i = 0;

    for(i = 0; i < tensor0_len + 1; i ++)
    {
        if(fabs(tensor0_dat[i]) > absmax)
            absmax = fabs(tensor0_dat[i]);
    }
    
    DBG("abs = %f.", absmax);

    int *histogram = malloc(bins * sizeof(int));
    float *histogram_norm = malloc(bins * sizeof(float));
    if(histogram == NULL || histogram_norm == NULL)
    {
        DBG("fatal error, malloc histogram failure.");
        exit(-1);
    }

    memset(histogram, 0x00, bins * sizeof(int));

    for(i = 0; i < tensor0_len; i ++)
    {
        if (tensor0_dat[i] == 0.f) continue;

        const int index = min((int)(fabs(tensor0_dat[i]) / absmax * bins), (bins - 1));
        histogram[index] += 1;
    }

    for(i = 0; i < bins; i ++)
    {
        DBG("histogram[%d] = %d.", i, histogram[i]);
    }

    //直方图归一化
    int sum = 0;

    for(i = 0; i < bins; i ++)
    {
        sum += histogram[i];
        histogram_norm[i] = 0.f;
    }

    for(i = 0; i < bins; i ++)
        histogram_norm[i] = (float)((float)histogram[i])/(float)sum;

    for(i = 0; i < bins; i ++)
    {
        DBG("histogram[%d] = %f.", i, histogram_norm[i]);
    }

    DBG("out");
    return 0;
}

运行:

3BINS:

 100BINS:

100BINS的数据可视化:

 和上面有所差异,原因可能是程序中绝对值的处理,将负值也作为正值处理了。为了验证,我们找到这批数据中的最小值,它是负的,我们将其加上一个偏移,正好变为0,这样所有的值都变为了正数,平移数据范围不会影响直方图的分布,所以我们就可以验证我们的猜测是否正确。

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <math.h>
#include <sys/ioctl.h>
 
#define DBG(fmt, ...)   do { printf("%s line %d, "fmt"\n", __func__, __LINE__, ##__VA_ARGS__); } while (0)
#define min(x,y) ({ \
    typeof(x) _x = (x);    \
    typeof(y) _y = (y);    \
    (void) (&_x == &_y);    \
    _x < _y ? _x : _y; })
 
int get_tensor_from_txt_file(char *file_path, float **buf)
{
    int len = 0;
    static float *memory = NULL;
    static int max_len = 10 * 1024 * 1024;
    FILE *fp = NULL;
 
    if(memory == NULL)
    {
        memory = (float*) malloc(max_len * sizeof(float));
    }
    
    if((fp = fopen(file_path, "r")) == NULL)
    {
        DBG("open tensor error.");
        exit(-1);
    }
 
    while(!feof(fp))
    {
        fscanf(fp, "%f", &memory[len ++]);
    }
 
    *buf = (float*)malloc(len * sizeof(float));
    if(len == 0 || *buf == NULL)
    {
        DBG("read tensor error, len %d, *buf %p", len, *buf);
        exit(-1);
    }
    memcpy(*buf, memory, len * sizeof(float));
 
    fclose(fp);
 
    return len;
}
 
int main(int argc, char **argv)
{
    FILE *file;
 
    DBG("in");
 
    if(argc != 3)
    {
        DBG("input error, you should use this program like that: program tensor binsnum.");
        exit(-1);
    }
 
    int tensor0_len;
    float *tensor0_dat;
    int bins = atoi(argv[2]);
    
    tensor0_len = 0;
    tensor0_dat = NULL;
 
    tensor0_len = get_tensor_from_txt_file(argv[1], &tensor0_dat);
    DBG("tensor len %d.", tensor0_len);

    float absmax = 0.f;
    float min = 0.f;
    int i = 0;

    for(i = 0; i < tensor0_len + 1; i ++)
	{
        tensor0_dat[i] += 87.939552;
	}

    for(i = 0; i < tensor0_len + 1; i ++)
    {
        if(fabs(tensor0_dat[i]) > absmax)
            absmax = fabs(tensor0_dat[i]);

        if(tensor0_dat[i] < min)
            min = tensor0_dat[i];
    }
    
    DBG("abs = %f, min %f.", absmax, min);

    int *histogram = malloc(bins * sizeof(int));
    float *histogram_norm = malloc(bins * sizeof(float));
    if(histogram == NULL || histogram_norm == NULL)
    {
        DBG("fatal error, malloc histogram failure.");
        exit(-1);
    }

    memset(histogram, 0x00, bins * sizeof(int));

    for(i = 0; i < tensor0_len; i ++)
    {
        if (tensor0_dat[i] == 0.f) continue;

        const int index = min((int)(fabs(tensor0_dat[i]) / absmax * bins), (bins - 1));
        histogram[index] += 1;
    }

    for(i = 0; i < bins; i ++)
    {
        DBG("histogram[%d] = %d.", i, histogram[i]);
    }

    //直方图归一化
    int sum = 0;

    for(i = 0; i < bins; i ++)
    {
        sum += histogram[i];
        histogram_norm[i] = 0.f;
    }

    for(i = 0; i < bins; i ++)
        histogram_norm[i] = (float)((float)histogram[i])/(float)sum;

    for(i = 0; i < bins; i ++)
    {
        printf("%f\n", histogram_norm[i]);
    }

    DBG("out");
    return 0;
}

这次直方图曲线和本篇开头的直方图符合了:

数据可视化部分的PYTHON代码:

import numpy as np
import linecache
import matplotlib.pyplot as plt

filename = "output.tensor"
cols = 1 # number of column
divided_ch = ' ' # divided_character between numbers

def dat_to_matrix(filename):
    file = open(filename)
    lines = file.readlines()
    rows = len(lines)
    # print(rows)
    # print(lines)
    datamat = np.zeros(rows)
    row = 0

    for line in lines:
        line = line.strip().split(divided_ch) # strip remove block space in line
        datamat[row:] = line[:]
        row += 1

    return datamat


data = dat_to_matrix(filename)
# print(data)
X=np.linspace(0,1,100)      # X轴坐标数据
plt.figure(figsize=(8,6))   # 定义图的大小
plt.plot(X,data)            # 绘制曲线图
plt.show()

总结:

1.直方图平移后会被压缩,但是曲线的变化趋势不变,可以作计算导数的思想实验来验证这一点。

2.对于AI训练和推理来说,数据本身的分布形状比数据本身要重要的多.

当OFFSET过大的时候,以山峰高度为例,相当于我们选取的基础海平面太低,以至于无法体现地表山峰的高度趋势了,这个时候,计算的直方图会被压缩。如下图将OFFSET从87改为870

产生的直方图为,对比上图,可以看到图像形状没有变化,但是被压缩了。

证明很简单,设n>m>0.a > 0

\frac{m}{n} \ ?\ \frac{a+m}{a+n}

?应该是什么呢?先通分。

\\ \frac{m(a+n)}{n(a+n)} ? \frac{n(a+m)}{n(a+n)} \\ \because m(a+n)=ma+mn<na+mn=n(a+m) \\ \therefore \frac{m}{n} \ < \ \frac{a+m}{a+n}

总结:

很多时候,量化重要的一步是找出tensor的值域边界,得到每层tensor值的上下边界,在此基础上确定threhold。

参考博客:

使用NCNN的INT8量化方式进行推理_papaofdoudou的博客-优快云博客_int8量化 ncnn


结束 

<think>好的,我现在需要帮助用户了解如何使用YOLOv5和CRNN进行图像预处理的方法和实现步骤。用户之前询问了车牌识别的预处理工具,现在转向具体模型的预处理流程。首先,我需要结合用户提供的引用,尤其是引用[4]中提到的YOLOv5和LPRNet的车牌识别系统,但用户现在问的是YOLOv5和CRNN的组合,可能用于车牌识别或其他文本识别任务。 首先,我需要明确YOLOv5和CRNN各自的角色。YOLOv5负责目标检测,即定位图像中的车牌位置,而CRNN用于识别车牌中的字符。预处理在这两个模型中可能有所不同,需要分别说明。 接下来,回顾用户提供的引用,引用[2]提到了模糊检测的重要性,可能在预处理中加入模糊检测步骤,确保输入质量。引用[4]给出了YOLOv5的环境配置和依赖,这可能对用户设置环境有帮助。但用户现在需要的是预处理的具体方法和代码示例,而不仅仅是环境配置。 用户的真实需求可能是如何为YOLOv5和CRNN准备输入数据,包括图像增强、归一化、尺寸调整等。需要分步骤讲解,从YOLOv5的检测预处理到CRNN的识别预处理,可能涉及不同的处理流程。 需要检查YOLOv5的预处理步骤,通常包括归一化、resize到固定尺寸(如640x640),以及数据增强方法如Mosaic。CRNN的预处理可能包括灰度化、二值化、尺寸调整(高度固定,宽度按比例),以及可能的序列处理。 在代码示例方面,需要提供YOLOv5检测部分的预处理代码,可能使用其自带的dataset.py中的处理方式,然后展示如何裁剪车牌区域,再输入到CRNN进行预处理。CRNN部分可能需要使用OpenCV进行图像处理,如灰度化、归一化,以及转换为张量。 另外,用户可能关心如何将两个模型的预处理流程整合,确保数据在两者之间正确传递。比如,YOLOv5检测到车牌后,如何裁剪并调整到CRNN所需的输入格式。 还需要注意引用相关的内容,比如引用[2]提到的模糊检测,可能在预处理前加入模糊判断,使用拉普拉斯算子或快速傅里叶变换,确保输入清晰。但用户的问题主要是关于YOLOv5和CRNN的预处理,可能需要简要提及这一点作为优化建议。 最后,确保代码示例正确,使用用户提到的工具如OpenCV,并参考引用[4]中的环境配置,比如OpenCV版本和PyTorch版本。同时,提供实际应用中的注意事项,如尺寸调整的影响、归一化的必要性等。 总结步骤: 1. YOLOv5目标检测的预处理:包括图像尺寸调整、归一化、数据增强。 2. 使用YOLOv5检测车牌区域,并裁剪。 3. CRNN的预处理:灰度化、二值化、尺寸调整、归一化、序列处理。 4. 整合两者的预处理流程,提供代码示例。 5. 可能的优化,如模糊检测(引用[2])。 6. 引用相关文献,如使用OpenCV(引用[1][4])和YOLOv5配置(引用[4])。</think>以下是结合YOLOv5目标检测与CRNN字符识别的图像预处理全流程详解(含代码实现): --- ### 一、整体处理流程 ```mermaid graph TD A[原始图像] --> B(YOLOv5预处理) --> C[车牌定位] --> D(CRNN预处理) --> E[字符识别] ``` --- ### 二、YOLOv5目标检测预处理 #### 1. **核心预处理步骤** - **尺寸标准化**:缩放到$640×640$像素(保持长宽比,填充灰边) - **归一化**:像素值从$[0,255]$映射到$[0,1]$ - **数据增强**:Mosaic增强、HSV色彩空间扰动(需在训练时启用) #### 2. **代码实现(基于YOLOv5源码)** ```python import torch from utils.datasets import LoadImages # YOLOv5内置预处理管道 def yolov5_preprocess(img_path): dataset = LoadImages(img_path, img_size=640, auto=True) for path, img, im0s, vid_cap in dataset: # img经过自动缩放/归一化/通道转换(BGR→RGB) img = torch.from_numpy(img).to(device) img = img.float() # uint8→float32 img /= 255.0 # 归一化 if img.ndimension() == 3: img = img.unsqueeze(0) return img, im0s # 示例使用 input_tensor, origin_img = yolov5_preprocess("car.jpg") ``` #### 3. **关键参数说明** - `img_size=640`:YOLOv5最优输入尺寸[^4] - `auto=True`:自动计算最佳填充比例 - **输出格式**:$[batch, channel, height, width]$ --- ### 三、CRNN字符识别预处理 #### 1. **车牌区域精处理流程** ```mermaid graph LR A[裁剪车牌] --> B{灰度化} --> C[二值化] --> D[高度归一化] --> E[宽度缩放] ``` #### 2. **代码实现(结合OpenCV)** ```python import cv2 import numpy as np def crnn_preprocess(roi_image, target_height=32): # 模糊检测(引用[2]方法) gray = cv2.cvtColor(roi_image, cv2.COLOR_BGR2GRAY) if cv2.Laplacian(gray, cv2.CV_64F).var() < 50: print("警告:检测到模糊车牌区域") # 自适应二值化 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) # 对比度受限直方图均衡化 enhanced = clahe.apply(gray) thresh = cv2.adaptiveThreshold(enhanced, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) # 尺寸归一化(固定高度,等比宽度) h, w = thresh.shape ratio = target_height / h resized_w = int(w * ratio) resized = cv2.resize(thresh, (resized_w, target_height), interpolation=cv2.INTER_AREA) # 转换为CRNN输入格式 (1, height, width) tensor = resized / 255.0 tensor = tensor[np.newaxis, ...].astype(np.float32) return torch.from_numpy(tensor) # 使用示例 plate_roi = origin_img[y1:y2, x1:x2] # YOLOv5检测的坐标 crnn_input = crnn_preprocess(plate_roi) ``` #### 3. **参数优化建议** - **高度选择**:CRNN常用32px,需与训练数据一致 - **宽度动态计算**:保持字符比例防止形变 - **二值化优化**:对低光照场景可改用OTSU算法 --- ### 四、完整处理链示例 ```python # 完整流程示例 # 1. YOLOv5检测 model = torch.hub.load('ultralytics/yolov5', 'custom', path='plate_detect.pt') results = model(input_tensor) # 2. 提取车牌区域 det = results.xyxy[0][0] # 取置信度最高的检测框 x1, y1, x2, y2 = map(int, det[:4]) plate_roi = origin_img[y1:y2, x1:x2] # 3. CRNN预处理 crnn_input = crnn_preprocess(plate_roi) # 4. CRNN推理(示例代码) crnn_model = CRNN(...) # 需加载预训练模型 predictions = crnn_model(crnn_input) ``` --- ### 五、关键问题解决方案 #### 1. **图像尺寸对齐问题** - **YOLOv5**:使用`letterbox`函数自动填充灰边 - **CRNN**:动态计算宽度,避免字符挤压变形[^4] #### 2. **光照不均处理** ```python # CLAHE增强对比度(引用[4]方案) clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8)) ``` #### 3. **多车牌处理** ```python # 遍历所有检测结果 for det in results.xyxy[0]: if det[5] == 0: # 假设车牌类别ID=0 process_single_plate(det) ``` --- ### 六、性能优化建议 1. **GPU加速**:使用`torch.cuda`加速张量运算 2. **批处理**:YOLOv5支持批量输入提升吞吐量 3. **预处理融合**:将CRNN预处理步骤移植到Dataloader中 [^1]: 该项目展示了深度学习模型与OpenCV的整合方法 [^2]: 模糊检测可提升预处理鲁棒性 [^4]: YOLOv5官方推荐预处理配置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

papaofdoudou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值