Yolov3-spp系列 | yolov3spp的训练验证与测试过程

简介: Yolov3-spp系列 | yolov3spp的训练验证与测试过程

1. 训练过程


1.1 参考代码

代码如下:


def train_one_epoch(model, optimizer, data_loader, device, epoch, epochs,
                    print_freq, accumulate, img_size,
                    grid_min, grid_max, gs,
                    multi_scale=False, warmup=True):
    """
    Args:
        data_loader: len = 1430 1430个batch_size=1个epochs分成一块块的batch_size
        print_freq: 每50个batch在logger中更新
        accumulate: 1、多尺度训练时accumulate个batch改变一次图片的大小
                    2、每训练accumulate*batch_size张图片更新一次权重和学习率
                    第一个epoch  accumulate=1
        img_size: 训练图像的大小
        grid_min, grid_max: 在给定最大最小输入尺寸范围内随机选取一个size(size为32的整数倍)
        gs: grid_size
        warmup: 用在训练第一个epoch时,这个时候的训练学习率要调小点,慢慢训练
    Returns:
        mloss: 每个epch计算的mloss [box_mean_loss, obj_mean_loss, class_mean_loss, total_mean_loss]
        now_lr: 每个epoch之后的学习率
    """
    model.train()
    metric_logger = utils.MetricLogger(delimiter="  ")
    metric_logger.add_meter('lr', utils.SmoothedValue(window_size=1, fmt='{value:.6f}'))
    header = 'Epoch: [{}/{}]'.format(epoch, epochs)
    # 模型训练开始第一轮采用warmup训练 慢慢训练
    lr_scheduler = None
    if epoch == 1 and warmup is True:  # 当训练第一轮(epoch=1)时,启用warmup训练方式,可理解为热身训练
        warmup_factor = 1.0 / 1000
        warmup_iters = min(1000, len(data_loader) - 1)
        lr_scheduler = utils.warmup_lr_scheduler(optimizer, warmup_iters, warmup_factor)
        accumulate = 1  # 慢慢训练,每个batch都改变img大小,每个batch都改变权重
    # amp.GradScaler: 混合精度训练
    # GradScaler: 在反向传播前给 loss 乘一个 scale factor,所以之后反向传播得到的梯度都乘了相同的 scale factor
    # scaler: GradScaler对象用来自动做梯度缩放
    # https://blog.csdn.net/l7H9JA4/article/details/114324414?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161944073216780357273770%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=161944073216780357273770&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-1-114324414.pc_search_result_cache&utm_term=amp.GradScaler%28enabled%3Denable_amp%29
    enable_amp = True if "cuda" in device.type else False
    scaler = amp.GradScaler(enabled=enable_amp)
    # mean losses [box_mean_loss, obj_mean_loss, class_mean_loss, total_mean_loss]
    mloss = torch.zeros(4).to(device)
    now_lr = 0.  # 本batch的lr
    nb = len(data_loader)  # number of batches
    # imgs: [batch_size, 3, img_size, img_size]
    # targets: [num_obj, 6] , that number 6 means -> (img_index, obj_index, x, y, w, h)
    # paths: list of img path
    # 这里调用一次datasets.__len__; batch_size次datasets.__getitem__; 再执行1次datasets.collate_fn
    for i, (imgs, targets, paths, _, _) in enumerate(metric_logger.log_every(data_loader, print_freq, header)):
        # ni 统计从epoch0开始的所有batch数
        ni = i + nb * epoch  # number integrated batches (since train start)
        # imgs: [4, 3, 736, 736]一个batch的图片
        # targets(真实框): [22, 6] 22: num_object  6: batch中第几张图(0,1,2,3),类别,x,y,w,h
        imgs = imgs.to(device).float() / 255.0  # 对imgs进行归一化 uint8 to float32, 0 - 255 to 0.0 - 1.0
        targets = targets.to(device)
        # Multi-Scale
        if multi_scale:
            # 每训练accumulate个batch(batch_size*accumulate张图片),就随机修改一次输入图片大小
            # 由于label已转为相对坐标,故缩放图片不影响label的值
            if ni % accumulate == 0:  #  adjust img_size (67% - 150%) every 1 batch
                # 在给定最大最小输入尺寸范围内随机选取一个size(size为32的整数倍)
                img_size = random.randrange(grid_min, grid_max + 1) * gs  # img_size = 320~736
            sf = img_size / max(imgs.shape[2:])  # scale factor
            # 如果图片最大边长不等于img_size, 则缩放一个batch图片,并将长和宽调整到32的整数倍
            if sf != 1:
                # gs: (pixels) grid size
                ns = [math.ceil(x * sf / gs) * gs for x in imgs.shape[2:]]  # new shape (stretched to 32-multiple)
                imgs = F.interpolate(imgs, size=ns, mode='bilinear', align_corners=False)
        # 混合精度训练上下文管理器,如果在CPU环境中不起任何作用
        with amp.autocast(enabled=enable_amp):
            # pred: tensor格式 list列表 存放三个tensor 对应的是三个yolo层的输出
            # 例如[batch_size, 3, 23, 23, 25]  [batch_size, 3, 46, 46, 25]  [batch_size, 3, 96, 96, 25]
            # [batch_size, anchor_num, grid_h, grid_w, xywh + obj + classes]
            # 可以看出来这里的预测值是三个yolo层每个grid_cell(每个grid_cell有三个预测值)的预测值,后面肯定要进行正样本筛选
            pred = model(imgs)
            # dict格式 存放三分tensor 每个tensor对应一个loss的键值对
            # loss的顺序(键)为: 'box_loss', 'obj_loss', 'class_loss'
            # targets(数据增强后的真实框): [21, 6] 21: num_object  6: batch中第几张图(0,1,2,3)+类别+x+y+w+h
            loss_dict = compute_loss(pred, targets, model)
            losses = sum(loss for loss in loss_dict.values())  # 三个相加
            # reduce losses over all GPUs for logging purpose
            loss_dict_reduced = utils.reduce_dict(loss_dict)
            losses_reduced = sum(loss for loss in loss_dict_reduced.values())
            loss_items = torch.cat((loss_dict_reduced["box_loss"],
                                    loss_dict_reduced["obj_loss"],
                                    loss_dict_reduced["class_loss"],
                                    losses_reduced)).detach()
            mloss = (mloss * i + loss_items) / (i + 1)  # update mean losses
            # 如果losses_reduced无效,则输出对应图片信息
            if not torch.isfinite(losses_reduced):
                print('WARNING: non-finite loss, ending training ', loss_dict_reduced)
                print("training image path: {}".format(",".join(paths)))
                sys.exit(1)
            losses *= 1. / accumulate  # scale loss
        # 1、backward 反向传播 scale loss 先将梯度放大 防止梯度消失
        scaler.scale(losses).backward()
        # optimize
        # 每训练accumulate*batch_size张图片更新一次权重
        if ni % accumulate == 0:
            # 2、scaler.step() 首先把梯度的值unscale回来.
            # 如果梯度的值不是 infs 或者 NaNs, 那么调用optimizer.step()来更新权重,
            # 否则,忽略step调用,从而保证权重不更新(不被破坏)
            scaler.step(optimizer)
            # 3、准备着,看是否要增大scaler 不一定更新  看scaler.step(optimizer)的结果,需要更新就更新
            scaler.update()
            # 正常更新权重
            optimizer.zero_grad()
        metric_logger.update(loss=losses_reduced, **loss_dict_reduced)
        now_lr = optimizer.param_groups[0]["lr"]
        metric_logger.update(lr=now_lr)
        # 每训练accumulate*batch_size张图片更新一次学习率(只在第一个epoch) warmup=True 才会执行
        if ni % accumulate == 0 and lr_scheduler is not None:
            lr_scheduler.step()
    return mloss, now_lr


1.2 简要分析

在yolov3spp的训练过程中,其重点只是利用网络的预测的三层特征层,来进行一个正负样本的匹配与损失计算,并结算的损失是根据中心点xy预测的偏移量已经以指数的形式来预测匹配到的anchor与targe的偏移量。


其中所使用到的iou计算不一样,对于边界框偏移量来说,正常使用iou/giou/diou/ciou来进行计算,但是对于正负样本匹配部分来说,选择那一个anchor与对应的target来进行匹配,是根据wh_iou,也就是重叠率来简单的进行计算的。也就是说,只需要根据两个预测框与目标框的宽高,而不需要知道其具体的位置信息来进行一个在大小上的匹配。


所以,整个训练过程是根据匹配好的anchor来进行拟合偏移量,这没有设置到非极大值抑制处理,也没有使用到coco的评价指标,只是计算了平均损失与当前的一个学习率。


2. 验证过程


2.1 参考代码

代码如下:


@torch.no_grad()
def evaluate(model, data_loader, coco=None, device=None):
    """
    Args:
        coco: coco api
    Returns:
    """
    n_threads = torch.get_num_threads()  # 8线程
    # FIXME remove this and make paste_masks_in_image run on the GPU
    torch.set_num_threads(1)
    cpu_device = torch.device("cpu")
    model.eval()
    metric_logger = utils.MetricLogger(delimiter="  ")
    header = "Test: "
    if coco is None:
        coco = get_coco_api_from_dataset(data_loader.dataset)
    iou_types = _get_iou_types(model)  # ['bbox']
    coco_evaluator = CocoEvaluator(coco, iou_types)
    # 这里调用一次datasets.__len__; batch_size次datasets.__getitem__; 再执行1次datasets.collate_fn
    # 调用__len__将dataset分为batch个批次; 再调用__getitem__取出(增强后)当前批次的每一张图片(batch_size张);
    # 最后调用collate_fn函数将当前整个批次的batch_size张图片(增强过的)打包成一个batch, 方便送入网络进行前向传播
    # img_index:对于的是哪一张图像的索引
    # paths:图像的路径
    # imgs:一个batch的图像本身
    # targets:一个列表,存储着每张图像的边界框信息
    for imgs, targets, paths, shapes, img_index in metric_logger.log_every(data_loader, 1, header):
        imgs = imgs.to(device).float() / 255.0  # uint8 to float32, 0 - 255 to 0.0 - 1.0
        # targets = targets.to(device)
        # 当使用CPU时,跳过GPU相关指令
        if device != torch.device("cpu"):
            torch.cuda.synchronize(device)
        model_time = time.time()
        pred = model(imgs)[0]  # only get inference result   [4, 5040, 25]
        # [4, 5040, 25] => len=4  [57,6], [5,6], [14,6], [1,6]  6: batch中第几张图(0,1,2,3),类别,x,y,w,h
        pred = non_max_suppression(pred, conf_thres=0.01, nms_thres=0.6, multi_cls=False)
        outputs = []
        for index, p in enumerate(pred):
            if p is None:
                p = torch.empty((0, 6), device=cpu_device)
                boxes = torch.empty((0, 4), device=cpu_device)
            else:
                # xmin, ymin, xmax, ymax
                boxes = p[:, :4]
                # shapes: (h0, w0), ((h / h0, w / w0), pad)
                # 将boxes信息还原回原图尺度,这样计算的mAP才是准确的
                boxes = scale_coords(imgs[index].shape[1:], boxes, shapes[index][0]).round()
            # 注意这里传入的boxes格式必须是xmin, ymin, xmax, ymax,且为绝对坐标
            info = {"boxes": boxes.to(cpu_device),
                    "labels": p[:, 5].to(device=cpu_device, dtype=torch.int64),
                    "scores": p[:, 4].to(cpu_device)}
            outputs.append(info)
        model_time = time.time() - model_time
        # 对每一张图片的信息进行打包出来,信息包括:边界框坐标、类别。置信度
        res = {img_id: output for img_id, output in zip(img_index, outputs)}
        evaluator_time = time.time()
        coco_evaluator.update(res)
        evaluator_time = time.time() - evaluator_time
        metric_logger.update(model_time=model_time, evaluator_time=evaluator_time)
    # gather the stats from all processes
    metric_logger.synchronize_between_processes()
    print("Averaged stats:", metric_logger)
    coco_evaluator.synchronize_between_processes()
    # accumulate predictions from all images
    coco_evaluator.accumulate()
    coco_evaluator.summarize()
    torch.set_num_threads(n_threads)
    result_info = coco_evaluator.coco_eval[iou_types[0]].stats.tolist()  # numpy to list
    return result_info
def _get_iou_types(model):
    model_without_ddp = model
    if isinstance(model, torch.nn.parallel.DistributedDataParallel):
        model_without_ddp = model.module
    iou_types = ["bbox"]
    return iou_types


2.2 简要分析

在验证过程中,最主要的不同是需要看看训练完后对当前这个验证集的效果,这个是验证过程的核心环节,可以用来检验训练效果以及挑选最后的一个训练权重。


这一部分其实和测试部分有点相像,对于训练好的模型输出的一个预测结果,需要经过非极大值抑制处理,来进行一步步的筛选出比较符合的预测框。其中的重点就是需要非极大值抑制处理,也就是所谓的nms后处理方法。根据预测结果获取后处理后的boxes、labels、scores(置信度),然后对于每一张图像就可以得到他的预测边界框信息,预测类别信息,预测的置信度分数三个类别,够成一个字典,形式为:{img_id: img_info}。


然后使用CocoEvaluator来对刚刚所构建的图像-预测结果字典来作一个评价:coco_evaluator.update(res),随机可以调用coco_evaluator类中的相关函数获取对于验证集的预测结果。


调用coco_evaluator的简要流程为:

coco = get_coco_api_from_dataset(data_loader.dataset)
coco_evaluator = CocoEvaluator(coco, iou_types)
...
res = {img_id: output for img_id, output in zip(img_index, outputs)}   # 构建图像-预测结果字典
coco_evaluator.update(res)
coco_evaluator.synchronize_between_processes()
coco_evaluator.accumulate()
coco_evaluator.summarize()
result_info = coco_evaluator.coco_eval[iou_types[0]].stats.tolist()   # 返回最后的评价结果


对于这一部分,之后会开设一个目标检测的技巧(trick)来详细介绍其使用方法。


3. 测试过程


3.1 参考代码

代码如下:

import os
import json
import time
import torch
import cv2
import argparse
import numpy as np
from matplotlib import pyplot as plt
from build_utils import datasets
from modules.model import DarkNet
from train_val_utils.draw_box_utils import draw_box
from train_val_utils.other_utils import time_synchronized, check_file
from train_val_utils.post_processing_utils import non_max_suppression, scale_coords
def main(opt):
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("Using {} device training.".format(device.type))
    # 1、载入opt参数
    cfg = opt.cfg               # yolo网络配置文件path
    weights = opt.weights       # 训练权重path
    json_path = opt.json_path   # voc classes json path
    img_path = opt.img_path     # 预测图片地址
    img_size = opt.img_size     # 预测图像大小(letterbox后)
    # 2、载入json文件 得到所有class
    json_file = open(json_path, 'r')
    class_dict = json.load(json_file)
    category_index = {v: k for k, v in class_dict.items()}
    # 3、初始化模型 模型载入权重
    model = DarkNet(cfg)
    model.load_state_dict(torch.load(weights, map_location=device)["model"], strict=False)
    model.to(device)
    # eval测试模式
    model.eval()
    with torch.no_grad():
        # 载入原图 img_o (375, 500, 3)  H W C
        img_o = cv2.imread(img_path)  # BGR numpy格式
        assert img_o is not None, "Image Not Found " + img_path
        # letterbox  numpy格式(array)   img:(384, 512, 3) H W C
        # 将原图最长边缩放到指定大小,再将原图较短边按原图比例缩放,最后将较短边两边pad操作缩放到最长边大小(不会失真)
        img = datasets.letterbox(img_o, new_shape=img_size, auto=True, color=(0, 0, 0))[0]
        # Convert (384, 512, 3) => (384, 512, 3) => (3, 384, 512)
        # img[:, :, ::-1]  BGR to RGB => transpose(2, 0, 1) HWC(384, 512, 3)  to  CHW(3, 384, 512)
        img = img[:, :, ::-1].transpose(2, 0, 1)
        img = np.ascontiguousarray(img)  # 使内存是连续的
        # numpy(3, 384, 512) CHW => torch.tensor [3, 384, 512] CHW
        img = torch.from_numpy(img).to(device).float()
        img /= 255.0  # 归一化scale (0, 255) to (0, 1)
        # [3, 384, 512] CHW => [1, 3, 384, 512] BCHW
        img = img.unsqueeze(0)  # add batch dimension
        # start inference
        t1 = time_synchronized()  # 获取当前时间 其实可以用time.time()
        # 推理阶段实际上会有两个返回值 x(相对原图的), p
        # x: predictor数据处理后的输出(数值是相对原图的,这里是img)
        #    [batch_size, anchor_num * grid * grid, xywh + obj + classes]
        #    这里pred[1,12096,25] (实际上是等于x)表示这张图片总共生成了12096个anchor(一个grid中三个anchor)
        # p: predictor原始输出即数据是相对feature map的
        #    [batch_size, anchor_num, grid, grid, xywh + obj + classes]
        pred = model(img)[0]  # only get inference result
        t2 = time_synchronized()
        print("model inference time:", t2 - t1)
        # nms pred=[7,6]=[obj_num, xyxy+score+cls] 这里的xyxy是相对img的
        # pred: 按score从大到小排列; output[0]=第一张图片的预测结果 不一定一次只传入一张图片的
        pred = non_max_suppression(pred)[0]
        t3 = time.time()
        print("nms time:", t3 - t2)
        if pred is None:
            print("No target detected.")
            exit(0)
        # 将nms后的预测结果pred tensor格式(是相对img上的)img.shape=[B,C,H,W]
        # 映射到原图img_o上 img_o.shape=[H, W, C]  pred=(anchor_nums, xyxy+score+class)
        pred[:, :4] = scale_coords(img.shape[2:], pred[:, :4], img_o.shape).round()
        print("pred shape:", pred.shape)
        # tensor.detach()截断tensor变量反向传播的梯度流,因为是预测所以不需要计算梯度信息
        # bboxes、scores、classes: 按score从大到小排列  tensor=>numpy
        bboxes = pred[:, :4].detach().cpu().numpy()  # xyxys
        scores = pred[:, 4].detach().cpu().numpy()   # scores
        classes = pred[:, 5].detach().cpu().numpy().astype(int) + 1  # classes
        # 到这一步,我们就得到了最终的相对原图的所有预测信息bboxes(位置信息)(7,4); scores(7); classes(类别)(7)
        # 画出每个预测结果
        img_o = draw_box(img_o[:, :, ::-1], bboxes, classes, scores, category_index)
        # 显示预测图片
        plt.imshow(img_o)
        plt.show()
        # 保存预测后的图片
        img_o.save("outputs/predict_result.jpg")
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--cfg', type=str, default='cfg/yolov3-spp.cfg', help="cfg/*.cfg path")
    parser.add_argument('--weights', type=str, default='weights/yolov3spp-voc-512.pt',
                        help='pretrain weights path')
    parser.add_argument('--json-path', type=str, default='data/pascal_voc_classes.json',
                        help="voc_classes_json_path")
    parser.add_argument('--img-path', type=str, default='imgs/2008_000011.jpg',
                        help="predict img path")
    parser.add_argument('--img-size', type=int, default=512,
                        help="predict img path [416, 512, 608] 32的倍数")
    opt = parser.parse_args()
    # 检查文件是否存在
    opt.cfg = check_file(opt.cfg)
    opt.data = check_file(opt.weights)
    opt.hyp = check_file(opt.json_path)
    opt.hyp = check_file(opt.img_path)
    print(opt)
    main(opt)


3.2 简要分析

对于测试来说,最重要的就是根据训练好的权重来对图像进行预测,与验证类似,关键是对预测结果作一个非极大值抑制处理,也就是一个后处理方法,然后甚至不需要对其作一个评价指标,因为没有label。


所以,在测试来说,最后就是根据后处理完的预测结果来直接的进行一个可视化展示,就是对特征层结果来进行一个缩放处理,缩放到原图的原大小,以及对预测的边界框与类别信息在原图上画出来。


总结:


大致看完了整个yolov3spp项目,收获良多,知道了主要的处理细节。对于训练过程,重点就是正负样本的匹配问题了,也就是标签的分配,其是不需要进行一个后处理方法的;而对于验证过程,需要对预测结果进行nms然后使用coco评价指标来进行测试,这是个工具知道如何掉包就好;而对于测试结果,由于测试是没有标签信息的,所以直接将nms后处理后的预测结果直接在原图上进行一个可视化展示即可。


并且,为了加快验证与测试,验证过程与测试过程都会使用Rectangular inference来加快推理。Rectangular inference属于是目标检测中常见的一个数据处理的技巧了,作用就是加快推理速度与验证速度。


除了Rectangular inference之外,yolov3spp中还涉及大量的技巧,这里我并没有对其过多的介绍,只是记录了yolov3spp最本质的算法核心,就是如何进行正负样本匹配以及其损失是如何计算,如何进行训练与验证的。


对于其他设计的训练与测试技巧,之后会放在其他专栏来学习与记录。


参考资料:


https://blog.csdn.net/qq_38253797/article/details/118046587

https://blog.csdn.net/qq_38253797/article/details/117920079


相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
目录
相关文章
|
23天前
|
机器学习/深度学习
神经网络与深度学习---验证集(测试集)准确率高于训练集准确率的原因
本文分析了神经网络中验证集(测试集)准确率高于训练集准确率的四个可能原因,包括数据集大小和分布不均、模型正则化过度、批处理后准确率计算时机不同,以及训练集预处理过度导致分布变化。
|
9天前
|
机器学习/深度学习 Python
训练集、测试集与验证集:机器学习模型评估的基石
在机器学习中,数据集通常被划分为训练集、验证集和测试集,以评估模型性能并调整参数。训练集用于拟合模型,验证集用于调整超参数和防止过拟合,测试集则用于评估最终模型性能。本文详细介绍了这三个集合的作用,并通过代码示例展示了如何进行数据集的划分。合理的划分有助于提升模型的泛化能力。
|
12天前
|
人工智能 测试技术 PyTorch
AI计算机视觉笔记二十四:YOLOP 训练+测试+模型评估
本文介绍了通过正点原子的ATK-3568了解并实现YOLOP(You Only Look Once for Panoptic Driving Perception)的过程,包括训练、测试、转换为ONNX格式及在ONNX Runtime上的部署。YOLOP由华中科技大学团队于2021年发布,可在Jetson TX2上达到23FPS,实现了目标检测、可行驶区域分割和车道线检测的多任务学习。文章详细记录了环境搭建、训练数据准备、模型转换和测试等步骤,并解决了ONNX转换过程中的问题。
|
22天前
|
Web App开发 敏捷开发 测试技术
自动化测试之美:使用Selenium WebDriver进行网页功能验证
【8月更文挑战第29天】在数字时代,软件质量是企业竞争力的关键。本文将深入探讨如何通过Selenium WebDriver实现自动化测试,确保网页应用的可靠性和性能。我们将从基础设置到编写测试用例,逐步引导读者掌握这一强大的测试工具,同时分享实战经验,让测试不再是开发的负担,而是质量保证的利器。
|
22天前
|
SQL 安全 测试技术
软件测试的哲学:探索、验证与持续改进
【8月更文挑战第29天】本文将深入探讨软件测试的核心理念,从测试的本质和目的出发,阐述测试不仅是技术活动,更是一种思考方式。我们将一起探索如何通过测试来验证软件的正确性,确保其满足用户需求,并在此基础上不断改进。文章将分享实用的测试策略和方法,包括代码示例,旨在帮助读者更好地理解和实践软件测试的艺术。
|
1月前
|
测试技术
单元测试问题之在单元测试中,方法的返回值或异常,如何验证
单元测试问题之在单元测试中,方法的返回值或异常,如何验证
|
2月前
|
前端开发 JavaScript 容器
快照测试中添加更多的断言和验证
快照测试中添加更多的断言和验证
|
2月前
|
测试技术 Apache
单元测试策略问题之设计有效的单测用例问题如何解决
单元测试策略问题之设计有效的单测用例问题如何解决
|
3月前
|
算法 计算机视觉 异构计算
基于FPGA的图像一维FFT变换IFFT逆变换verilog实现,包含tb测试文件和MATLAB辅助验证
```markdown ## FPGA 仿真与 MATLAB 显示 - 图像处理的 FFT/IFFT FPGA 实现在 Vivado 2019.2 中仿真,结果通过 MATLAB 2022a 展示 - 核心代码片段:`Ddddddddddddddd` - 理论:FPGA 实现的一维 FFT/IFFT,加速数字信号处理,适用于高计算需求的图像应用,如压缩、滤波和识别 ```
|
3月前
|
算法 计算机视觉 异构计算
基于FPGA的图像直方图均衡化处理verilog实现,包含tb测试文件和MATLAB辅助验证
摘要: 在FPGA上实现了图像直方图均衡化算法,通过MATLAB2022a与Vivado2019.2进行仿真和验证。核心程序涉及灰度直方图计算、累积分布及映射变换。算法旨在提升图像全局对比度,尤其适合低对比度图像。FPGA利用可编程增益器和查表技术加速硬件处理,实现像素灰度的均匀重分布,提升视觉效果。![image preview](https://ucc.alicdn.com/pic/developer-ecology/3tnl7rfrqv6tw_a075525027db4afbb9c0529921fd0152.png)