融合分布移位卷积(DSConv)YOLO的掌纹ROI区域分割系统

本文介绍了一个融合分布移位卷积(DSConv)与YOLO的掌纹ROI区域分割系统,旨在提高图像分割的效率和准确性。DSConv在保持计算效率的同时增强了对细节的感知,而YOLO则用于目标检测。系统通过数据集的收集、标注和整理,以及模型训练,实现了掌纹ROI的精确分割。实验结果表明,该系统在准确性、实时性和鲁棒性方面都有显著改进,适用于掌纹识别和生物特征识别等领域。
摘要由CSDN通过智能技术生成

1.研究背景与意义

项目参考AAAI Association for the Advancement of Artificial Intelligence

研究背景与意义:

随着计算机视觉技术的不断发展,人们对于图像分割的需求也越来越迫切。图像分割是计算机视觉领域的一个重要研究方向,它的目标是将图像中的不同区域进行分割,以便更好地理解和处理图像。其中,掌纹ROI区域分割是图像分割中的一个重要任务,它可以用于掌纹识别、生物特征识别等领域。

然而,传统的图像分割方法在掌纹ROI区域分割任务上存在一些问题。首先,传统的方法往往需要大量的手工设计特征,这使得算法的泛化能力较差,对于不同的数据集和场景很难取得良好的效果。其次,传统的方法在处理复杂的背景和噪声时表现不佳,容易受到干扰而产生误分割。此外,传统的方法通常需要较长的计算时间,无法满足实时性的要求。

为了解决上述问题,近年来,深度学习技术在图像分割领域取得了显著的进展。特别是卷积神经网络(CNN)的出现,极大地推动了图像分割的发展。然而,传统的CNN模型在处理掌纹ROI区域分割任务时仍然存在一些挑战。首先,传统的CNN模型往往需要大量的计算资源和训练数据,对于一些资源有限的场景来说,这是一个不可忽视的问题。其次,传统的CNN模型在处理图像中的细节信息时表现不佳,容易产生边界模糊的问题。

因此,本研究提出了一种新的掌纹ROI区域分割系统,该系统融合了分布移位卷积(DSConv)和YOLO(You Only Look Once)算法。DSConv是一种新型的卷积操作,它可以在保持计算效率的同时,提高模型对细节信息的感知能力。而YOLO算法是一种实时目标检测算法,它可以快速准确地定位和识别图像中的目标。

本研究的主要目标是设计一个高效准确的掌纹ROI区域分割系统,以满足实时性和准确性的要求。具体来说,本研究的主要工作包括以下几个方面:首先,我们将设计一个基于DSConv的卷积神经网络模型,用于提取图像中的特征信息。其次,我们将引入YOLO算法,用于定位和识别掌纹ROI区域。最后,我们将通过大量的实验验证和比较,评估所提出的系统在掌纹ROI区域分割任务上的性能。

本研究的意义主要体现在以下几个方面:首先,所提出的掌纹ROI区域分割系统可以在保持较高准确性的同时,提高计算效率,满足实时性的要求。其次,所提出的系统可以更好地处理复杂的背景和噪声,提高分割的鲁棒性。最后,本研究的成果可以为掌纹识别、生物特征识别等领域的研究和应用提供有力的支持。

综上所述,本研究将融合分布移位卷积(DSConv)和YOLO算法,设计一个高效准确的掌纹ROI区域分割系统。该系统将在实时性、准确性和鲁棒性等方面取得显著的改进,为掌纹识别、生物特征识别等领域的研究和应用提供有力的支持。

2.图片演示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.视频演示

融合分布移位卷积(DSConv)YOLO的掌纹ROI区域分割系统_哔哩哔哩_bilibili

4.数据集的采集&标注和整理

图片的收集

首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集FrictionDatasets。
在这里插入图片描述

eiseg是一个图形化的图像注释工具,支持COCO和YOLO格式。以下是使用eiseg将图片标注为COCO格式的步骤:

(1)下载并安装eiseg。
(2)打开eiseg并选择“Open Dir”来选择你的图片目录。
(3)为你的目标对象设置标签名称。
(4)在图片上绘制矩形框,选择对应的标签。
(5)保存标注信息,这将在图片目录下生成一个与图片同名的JSON文件。
(6)重复此过程,直到所有的图片都标注完毕。

由于YOLO使用的是txt格式的标注,我们需要将VOC格式转换为YOLO格式。可以使用各种转换工具或脚本来实现。

下面是一个简单的方法是使用Python脚本,该脚本读取XML文件,然后将其转换为YOLO所需的txt格式。

import contextlib
import json

import cv2
import pandas as pd
from PIL import Image
from collections import defaultdict

from utils import *


# Convert INFOLKS JSON file into YOLO-format labels ----------------------------
def convert_infolks_json(name, files, img_path):
    # Create folders
    path = make_dirs()

    # Import json
    data = []
    for file in glob.glob(files):
        with open(file) as f:
            jdata = json.load(f)
            jdata['json_file'] = file
            data.append(jdata)

    # Write images and shapes
    name = path + os.sep + name
    file_id, file_name, wh, cat = [], [], [], []
    for x in tqdm(data, desc='Files and Shapes'):
        f = glob.glob(img_path + Path(x['json_file']).stem + '.*')[0]
        file_name.append(f)
        wh.append(exif_size(Image.open(f)))  # (width, height)
        cat.extend(a['classTitle'].lower() for a in x['output']['objects'])  # categories

        # filename
        with open(name + '.txt', 'a') as file:
            file.write('%s\n' % f)

    # Write *.names file
    names = sorted(np.unique(cat))
    # names.pop(names.index('Missing product'))  # remove
    with open(name + '.names', 'a') as file:
        [file.write('%s\n' % a) for a in names]

    # Write labels file
    for i, x in enumerate(tqdm(data, desc='Annotations')):
        label_name = Path(file_name[i]).stem + '.txt'

        with open(path + '/labels/' + label_name, 'a') as file:
            for a in x['output']['objects']:
                # if a['classTitle'] == 'Missing product':
                #    continue  # skip

                category_id = names.index(a['classTitle'].lower())

                # The INFOLKS bounding box format is [x-min, y-min, x-max, y-max]
                box = np.array(a['points']['exterior'], dtype=np.float32).ravel()
                box[[0, 2]] /= wh[i][0]  # normalize x by width
                box[[1, 3]] /= wh[i][1]  # normalize y by height
                box = [box[[0, 2]].mean(), box[[1, 3]].mean(), box[2] - box[0], box[3] - box[1]]  # xywh
                if (box[2] > 0.) and (box[3] > 0.):  # if w > 0 and h > 0
                    file.write('%g %.6f %.6f %.6f %.6f\n' % (category_id, *box))

    # Split data into train, test, and validate files
    split_files(name, file_name)
    write_data_data(name + '.data', nc=len(names))
    print(f'Done. Output saved to {os.getcwd() + os.sep + path}')


# Convert vott JSON file into YOLO-format labels -------------------------------
def convert_vott_json(name, files, img_path):
    # Create folders
    path = make_dirs()
    name = path + os.sep + name

    # Import json
    data = []
    for file in glob.glob(files):
        with open(file) as f:
            jdata = json.load(f)
            jdata['json_file'] = file
            data.append(jdata)

    # Get all categories
    file_name, wh, cat = [], [], []
    for i, x in enumerate(tqdm(data, desc='Files and Shapes')):
        with contextlib.suppress(Exception):
            cat.extend(a['tags'][0] for a in x['regions'])  # categories

    # Write *.names file
    names = sorted(pd.unique(cat))
    with open(name + '.names', 'a') as file:
        [file.write('%s\n' % a) for a in names]

    # Write labels file
    n1, n2 = 0, 0
    missing_images = []
    for i, x in enumerate(tqdm(data, desc='Annotations')):

        f = glob.glob(img_path + x['asset']['name'] + '.jpg')
        if len(f):
            f = f[0]
            file_name.append(f)
            wh = exif_size(Image.open(f))  # (width, height)

            n1 += 1
            if (len(f) > 0) and (wh[0] > 0) and (wh[1] > 0):
                n2 += 1

                # append filename to list
                with open(name + '.txt', 'a') as file:
                    file.write('%s\n' % f)

                # write labelsfile
                label_name = Path(f).stem + '.txt'
                with open(path + '/labels/' + label_name, 'a') as file:
                    for a in x['regions']:
                        category_id = names.index(a['tags'][0])

                        # The INFOLKS bounding box format is [x-min, y-min, x-max, y-max]
                        box = a['boundingBox']
                        box = np.array([box['left'], box['top'], box['width'], box['height']]).ravel()
                        box[[0, 2]] /= wh[0]  # normalize x by width
                        box[[1, 3]] /= wh[1]  # normalize y by height
                        box = [box[0] + box[2] / 2, box[1] + box[3] / 2, box[2], box[3]]  # xywh

                        if (box[2] > 0.) and (box[3] > 0.):  # if w > 0 and h > 0
                            file.write('%g %.6f %.6f %.6f %.6f\n' % (category_id, *box))
        else:
            missing_images.append(x['asset']['name'])

    print('Attempted %g json imports, found %g images, imported %g annotations successfully' % (i, n1, n2))
    if len(missing_images):
        print('WARNING, missing images:', missing_images)

    # Split data into train, test, and validate files
    split_files(name, file_name)
    print(f'Done. Output saved to {os.getcwd() + os.sep + path}')


# Convert ath JSON file into YOLO-format labels --------------------------------
def convert_ath_json(json_dir):  # dir contains json annotations and images
    # Create folders
    dir = make_dirs()  # output directory

    jsons = []
    for dirpath, dirnames, filenames in os.walk(json_dir):
        jsons.extend(
            os.path.join(dirpath, filename)
            for filename in [
                f for f in filenames if f.lower().endswith('.json')
            ]
        )

    # Import json
    n1, n2, n3 = 0, 0, 0
    missing_images, file_name = [], []
    for json_file in sorted(jsons):
        with open(json_file) as f:
            data = json.load(f)

        # # Get classes
        # try:
        #     classes = list(data['_via_attributes']['region']['class']['options'].values())  # classes
        # except:
        #     classes = list(data['_via_attributes']['region']['Class']['options'].values())  # classes

        # # Write *.names file
        # names = pd.unique(classes)  # preserves sort order
        # with open(dir + 'data.names', 'w') as f:
        #     [f.write('%s\n' % a) for a in names]

        # Write labels file
        for x in tqdm(data['_via_img_metadata'].values(), desc=f'Processing {json_file}'):
            image_file = str(Path(json_file).parent / x['filename'])
            f = glob.glob(image_file)  # image file
            if len(f):
                f = f[0]
                file_name.append(f)
                wh = exif_size(Image.open(f))  # (width, height)

                n1 += 1  # all images
                if len(f) > 0 and wh[0] > 0 and wh[1] > 0:
                    label_file = dir + 'labels/' + Path(f).stem + '.txt'

                    nlabels = 0
                    try:
                        with open(label_file, 'a') as file:  # write labelsfile
                            # try:
                            #     category_id = int(a['region_attributes']['class'])
                            # except:
                            #     category_id = int(a['region_attributes']['Class'])
                            category_id = 0  # single-class

                            for a in x['regions']:
                                # bounding box format is [x-min, y-min, x-max, y-max]
                                box = a['shape_attributes']
                                box = np.array([box['x'], box['y'], box['width'], box['height']],
                                               dtype=np.float32).ravel()
                                box[[0, 2]] /= wh[0]  # normalize x by width
                                box[[1, 3]] /= wh[1]  # normalize y by height
                                box = [box[0] + box[2] / 2, box[1] + box[3] / 2, box[2],
                                       box[3]]  # xywh (left-top to center x-y)

                                if box[2] > 0. and box[3] > 0.:  # if w > 0 and h > 0
                                    file.write('%g %.6f %.6f %.6f %.6f\n' % (category_id, *box))
                                    n3 += 1
                                    nlabels += 1

                        if nlabels == 0:  # remove non-labelled images from dataset
                            os.system(f'rm {label_file}')
                            # print('no labels for %s' % f)
                            continue  # next file

                        # write image
                        img_size = 4096  # resize to maximum
                        img = cv2.imread(f)  # BGR
                        assert img is not None, 'Image Not Found ' + f
                        r = img_size / max(img.shape)  # size ratio
                        if r < 1:  # downsize if necessary
                            h, w, _ = img.shape
                            img = cv2.resize(img, (int(w * r), int(h * r)), interpolation=cv2.INTER_AREA)

                        ifile = dir + 'images/' + Path(f).name
                        if cv2.imwrite(ifile, img):  # if success append image to list
                            with open(dir + 'data.txt', 'a') as file:
                                file.write('%s\n' % ifile)
                            n2 += 1  # correct images

                    except Exception:
                        os.system(f'rm {label_file}')
                        print(f'problem with {f}')

            else:
                missing_images.append(image_file)

    nm = len(missing_images)  # number missing
    print('\nFound %g JSONs with %g labels over %g images. Found %g images, labelled %g images successfully' %
          (len(jsons), n3, n1, n1 - nm, n2))
    if len(missing_images):
        print('WARNING, missing images:', missing_images)

    # Write *.names file
    names = ['knife']  # preserves sort order
    with open(dir + 'data.names', 'w') as f:
        [f.write('%s\n' % a) for a in names]

    # Split data into train, test, and validate files
    split_rows_simple(dir + 'data.txt')
    write_data_data(dir + 'data.data', nc=1)
    print(f'Done. Output saved to {Path(dir).absolute()}')


def convert_coco_json(json_dir='../coco/annotations/', use_segments=False, cls91to80=False):
    save_dir = make_dirs()  # output directory
    coco80 = coco91_to_coco80_class()

    # Import json
    for json_file in sorted(Path(json_dir).resolve().glob('*.json')):
        fn = Path(save_dir) / 'labels' / json_file.stem.replace('instances_', '')  # folder name
        fn.mkdir()
        with open(json_file) as f:
            data = json.load(f)

        # Create image dict
        images = {'%g' % x['id']: x for x in data['images']}
        # Create image-annotations dict
        imgToAnns = defaultdict(list)
        for ann in data['annotations']:
            imgToAnns[ann['image_id']].append(ann)

        # Write labels file
        for img_id, anns in tqdm(imgToAnns.items(), desc=f'Annotations {json_file}'):
            img = images['%g' % img_id]
            h, w, f = img['height'], img['width'], img['file_name']

            bboxes = []
            segments = []
            for ann in anns:
                if ann['iscrowd']:
                    continue
                # The COCO box format is [top left x, top left y, width, height]
                box = np.array(ann['bbox'], dtype=np.float64)
                box[:2] += box[2:] / 2  # xy top-left corner to center
                box[[0, 2]] /= w  # normalize x
                box[[1, 3]] /= h  # normalize y
                if box[2] <= 0 or box[3] <= 0:  # if w <= 0 and h <= 0
                    continue

                cls = coco80[ann['category_id'] - 1] if cls91to80 else ann['category_id'] - 1  # class
                box = [cls] + box.tolist()
                if box not in bboxes:
                    bboxes.append(box)
                # Segments
                if use_segments:
                    if len(ann['segmentation']) > 1:
                        s = merge_multi_segment(ann['segmentation'])
                        s = (np.concatenate(s, axis=0) / np.array([w, h])).reshape(-1).tolist()
                    else:
                        s = [j for i in ann['segmentation'] for j in i]  # all segments concatenated
                        s = (np.array(s).reshape(-1, 2) / np.array([w, h])).reshape(-1).tolist()
                    s = [cls] + s
                    if s not in segments:
                        segments.append(s)

            # Write
            with open((fn / f).with_suffix('.txt'), 'a') as file:
                for i in range(len(bboxes)):
                    line = *(segments[i] if use_segments else bboxes[i]),  # cls, box or segments
                    file.write(('%g ' * len(line)).rstrip() % line + '\n')


def min_index(arr1, arr2):
    """Find a pair of indexes with the shortest distance. 
    Args:
        arr1: (N, 2).
        arr2: (M, 2).
    Return:
        a pair of indexes(tuple).
    """
    dis = ((arr1[:, None, :] - arr2[None, :, :]) ** 2).sum(-1)
    return np.unravel_index(np.argmin(dis, axis=None), dis.shape)


def merge_multi_segment(segments):
    """Merge multi segments to one list.
    Find the coordinates with min distance between each segment,
    then connect these coordinates with one thin line to merge all 
    segments into one.

    Args:
        segments(List(List)): original segmentations in coco's json file.
            like [segmentation1, segmentation2,...], 
            each segmentation is a list of coordinates.
    """
    s = []
    segments = [np.array(i).reshape(-1, 2) for i in segments]
    idx_list = [[] for _ in range(len(segments))]

    # record the indexes with min distance between each segment
    for i in range(1, len(segments)):
        idx1, idx2 = min_index(segments[i - 1], segments[i])
        idx_list[i - 1].append(idx1)
        idx_list[i].append(idx2)

    # use two round to connect all the segments
    for k in range(2):
        # forward connection
        if k == 0:
            for i, idx in enumerate(idx_list):
                # middle segments have two indexes
                # reverse the index of middle segments
                if len(idx) == 2 and idx[0] > idx[1]:
                    idx = idx[::-1]
                    segments[i] = segments[i][::-1, :]

                segments[i] = np.roll(segments[i], -idx[0], axis=0)
                segments[i] = np.concatenate([segments[i], segments[i][:1]])
                # deal with the first segment and the last one
                if i in [0, len(idx_list) - 1]:
                    s.append(segments[i])
                else:
                    idx = [0, idx[1] - idx[0]]
                    s.append(segments[i][idx[0]:idx[1] + 1])

        else:
            for i in range(len(idx_list) - 1, -1, -1):
                if i not in [0, len(idx_list) - 1]:
                    idx = idx_list[i]
                    nidx = abs(idx[1] - idx[0])
                    s.append(segments[i][nidx:])
    return s


def delete_dsstore(path='../datasets'):
    # Delete apple .DS_store files
    from pathlib import Path
    files = list(Path(path).rglob('.DS_store'))
    print(files)
    for f in files:
        f.unlink()


if __name__ == '__main__':
    source = 'COCO'

    if source == 'COCO':
        convert_coco_json('./annotations',  # directory with *.json
                          use_segments=True,
                          cls91to80=True)

    elif source == 'infolks':  # Infolks https://infolks.info/
        convert_infolks_json(name='out',
                             files='../data/sm4/json/*.json',
                             img_path='../data/sm4/images/')

    elif source == 'vott':  # VoTT https://github.com/microsoft/VoTT
        convert_vott_json(name='data',
                          files='../../Downloads/athena_day/20190715/*.json',
                          img_path='../../Downloads/athena_day/20190715/')  # images folder

    elif source == 'ath':  # ath format
        convert_ath_json(json_dir='../../Downloads/athena/')  # images folder

    # zip results
    # os.system('zip -r ../coco.zip ../coco')


整理数据文件夹结构

我们需要将数据集整理为以下结构:

-----datasets
	-----coco128-seg
	   |-----images
	   |   |-----train
	   |   |-----valid
	   |   |-----test
	   |
	   |-----labels
	   |   |-----train
	   |   |-----valid
	   |   |-----test
	   |

模型训练
 Epoch   gpu_mem       box       obj       cls    labels  img_size
 1/200     20.8G   0.01576   0.01955  0.007536        22      1280: 100%|██████████| 849/849 [14:42<00:00,  1.04s/it]
           Class     Images     Labels          P          R     mAP@.5 mAP@.5:.95: 100%|██████████| 213/213 [01:14<00:00,  2.87it/s]
             all       3395      17314      0.994      0.957      0.0957      0.0843

 Epoch   gpu_mem       box       obj       cls    labels  img_size
 2/200     20.8G   0.01578   0.01923  0.007006        22      1280: 100%|██████████| 849/849 [14:44<00:00,  1.04s/it]
           Class     Images     Labels          P          R     mAP@.5 mAP@.5:.95: 100%|██████████| 213/213 [01:12<00:00,  2.95it/s]
             all       3395      17314      0.996      0.956      0.0957      0.0845

 Epoch   gpu_mem       box       obj       cls    labels  img_size
 3/200     20.8G   0.01561    0.0191  0.006895        27      1280: 100%|██████████| 849/849 [10:56<00:00,  1.29it/s]
           Class     Images     Labels          P          R     mAP@.5 mAP@.5:.95: 100%|███████   | 187/213 [00:52<00:00,  4.04it/s]
             all       3395      17314      0.996      0.957      0.0957      0.0845

5.核心代码讲解

5.1 export.py



def export_formats():
    # YOLOv5 export formats
    x = [
        ['PyTorch', '-', '.pt', True, True],
        ['TorchScript', 'torchscript', '.torchscript', True, True],
        ['ONNX', 'onnx', '.onnx', True, True],
        ['OpenVINO', 'openvino', '_openvino_model', True, False],
        ['TensorRT', 'engine', '.engine', False, True],
        ['CoreML', 'coreml', '.mlmodel', True, False],
        ['TensorFlow SavedModel', 'saved_model', '_saved_model', True, True],
        ['TensorFlow GraphDef', 'pb', '.pb', True, True],
        ['TensorFlow Lite', 'tflite', '.tflite', True, False],
        ['TensorFlow Edge TPU', 'edgetpu', '_edgetpu.tflite', False, False],
        ['TensorFlow.js', 'tfjs', '_web_model', False, False],
        ['PaddlePaddle', 'paddle', '_paddle_model', True, True],]
    return pd.DataFrame(x, columns=['Format', 'Argument', 'Suffix', 'CPU', 'GPU'])


def try_export(inner_func):
    # YOLOv5 export decorator, i..e @try_export
    inner_args = get_default_args(inner_func)

    def outer_func(*args, **kwargs):
        prefix = inner_args['prefix']
        try:
            with Profile() as dt:
                f, model = inner_func(*args, **kwargs)
            LOGGER.info(f'{prefix} export success ✅ {dt.t:.1f}s, saved as {f} ({file_size(f):.1f} MB)')
            return f, model
        except Exception as e:
            LOGGER.info(f'{prefix} export failure ❌ {dt.t:.1f}s: {e}')
            return None, None

    return outer_func


@try_export
def export_torchscript(model, im, file, optimize, prefix=colorstr('TorchScript:')):
    # YOLOv5 TorchScript model export
    LOGGER.info(f'\n{prefix} starting export with torch {torch.__version__}...')
    f = file.with_suffix('.torchscript')

    ts = torch.jit.trace(model, im, strict=False)
    d = {"shape": im.shape, "stride": int(max(model.stride)), "names": model.names}
    extra_files = {'config.txt': json.dumps(d)}  # torch._C.ExtraFilesMap()
    if optimize:  # https://pytorch.org/tutorials/recipes/mobile_interpreter.html
        optimize_for_mobile(ts)._save_for_lite_interpreter(str(f), _extra_files=extra_files)
    else:
        ts.save(str(f), _extra_files=extra_files)
    return f, None


@try_export
def export_onnx(model, im, file, opset, dynamic, simplify, prefix=colorstr('ONNX:')):
    # YOLOv5 ONNX export
    check_requirements('onnx>=1.12.0')
    import onnx

    LOGGER.info(f'\n{prefix} starting export with onnx {onnx.__version__}...')
    f = file.with_suffix('.onnx')

    output_names = ['output0', 'output1'] if isinstance(model, SegmentationModel) else ['output0']
    if dynamic:
        dynamic = {'images': {0: 'batch', 2: 'height', 3: 'width'}}  # shape(1,3,640,640)
        if isinstance(model, SegmentationModel):
            dynamic['output0'] = {0: 'batch', 1: 'anchors'}  # shape(1,25200,85)
            dynamic['output1'] = {0: 'batch', 2: 'mask_height', 3: 'mask_width'}  # shape(1,32,160,160)
        elif isinstance(model, DetectionModel):
            dynamic['output0'] = {0: 'batch', 1: 'anchors'}  # shape(1,25200,85)

    torch.onnx.export(
        model.cpu() if dynamic else model,  # --dynamic only compatible with cpu
        im.cpu() if dynamic else im,
        f,
        verbose=False,
        opset_version=opset,
        do_constant_folding=True,  # WARNING: DNN inference with torch>=1.12 may require do_constant_folding=False
        input_names=['images'],
        output_names=output_names,
        dynamic_axes=dynamic or None)

    # Checks
    model_onnx = onnx.load(f)  # load onnx model
    onnx.checker.check_model(model_onnx)  # check onnx model

    # Metadata
    d = {'stride': int(max(model.stride)), 'names': model.names}
    for k, v in d.items():
        meta = model_onnx.metadata_props.add()
        meta.key, meta.value = k, str(v)
    onnx.save(model_onnx, f)

    # Simplify
    if simplify:
        try:
            cuda = torch.cuda.is_available()
            check_requirements(('onnxruntime-gpu' if cuda else 'onnxruntime', 'onnx-simplifier>=0.4.1'))
            import onnxsim

            LOGGER.info(f'{prefix} simplifying with onnx-simplifier {onnxsim.__version__}...')
            model_simp, check = onnxsim.simplify(f, check=True)
            assert check, 'assert check failed'
            onnx.save(model_simp, f)
        except Exception as e:
            LOGGER.info(f'{prefix} simplifier failure ❌ {e}')
    return f, None

export.py是一个用于将YOLOv5 PyTorch模型导出为其他格式的程序文件。该文件提供了多种导出格式选项,包括TorchScript、ONNX、OpenVINO、TensorRT、CoreML、TensorFlow SavedModel、TensorFlow GraphDef、TensorFlow Lite、TensorFlow Edge TPU、TensorFlow.js和PaddlePaddle。用户可以根据需要选择要导出的格式,并使用相应的命令行参数运行export.py文件来进行导出。

export.py文件还包含了一些辅助函数和装饰器,用于处理模型导出过程中的异常情况和记录导出结果。具体的导出过程会根据选择的导出格式和模型类型进行相应的处理。例如,对于TorchScript格式的导出,会使用torch.jit.trace函数将模型转换为TorchScript格式,并保存为.torchscript文件;对于ONNX格式的导出,会使用torch.onnx.export函数将模型转换为ONNX格式,并保存为.onnx文件。

在导出过程中,export.py文件会检查所需的依赖库是否已安装,并根据需要进行安装。导出成功后,会输出导出结果的信息,包括导出所花费的时间和导出文件的大小。

此外,export.py文件还提供了一个export_formats函数,用于返回支持的导出格式的信息。

5.2 ui.py


class YOLOv5Detector:
    def __init__(self, weights='./best.pt', data=ROOT / 'data/coco128.yaml', device='', half=False, dnn=False):
        self.model, self.stride, self.names, self.pt = self.load_model(weights, data, device, half, dnn)

    def load_model(self, weights, data, device, half, dnn):
        device = select_device(device)
        model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
        stride, names, pt = model.stride, model.names, model.pt
        return model, stride, names, pt

    def run(self, img, imgsz=(640, 640), conf_thres=0.25, iou_thres=0.45, max_det=1000, device='', classes=None,
            agnostic_nms=False, augment=False, half=False, retina_masks=True):
        imgsz = check_img_size(imgsz, s=self.stride)  # check image size
        self.model.warmup(imgsz=(1 if self.pt else 1, 3, *imgsz))  # warmup

        cal_detect = []
        device = select_device(device)
        names = self.model.module.names if hasattr(self.model, 'module') else self.model.names  # get class names

        # Set Dataloader
        im = letterbox(img, imgsz, self.stride, self.pt)[0]

        # Convert
        im = im.transpose((2, 0, 1))[::-1]  # HWC to CHW, BGR to RGB
        im = np.ascontiguousarray(im)

        im = torch.from_numpy(im).to(device)
        im = im.half() if half else im.float()  # uint8 to fp
        .....

这个程序文件是一个使用PyQt5库创建的图形用户界面(GUI)应用程序。它包含了一个主窗口,其中有一个标签用于显示原始图像,另一个标签用于显示输出图像。还有一个文本浏览器用于显示程序的输出信息。主窗口还包含三个按钮,分别用于选择文件、开始识别和退出系统。

程序文件中还导入了其他的Python库,包括PyQt5的各个模块、os、sys、pathlib、numpy、time、argparse、torch、cv2等。这些库用于实现图形界面的创建、文件操作、模型加载、图像处理等功能。

程序的主要功能是使用已经训练好的模型对图像进行目标检测。通过选择文件按钮,用户可以选择要检测的图像文件。然后,点击开始识别按钮,程序会调用模型进行目标检测,并将结果显示在输出标签中。如果没有选择文件,程序会自动打开摄像头进行实时目标检测。

在目标检测过程中,程序会将检测到的目标在原始图像上用矩形框标出,并在输出图像中显示检测结果。同时,程序会将检测到的目标的类别、置信度和轮廓信息保存在一个列表中,并在文本浏览器中显示出来。

用户可以通过退出系统按钮退出程序。

总之,这个程序文件是一个基于PyQt5的图像目标检测应用程序,可以选择图像文件或使用摄像头进行实时目标检测,并将检测结果显示在图形界面中。

5.3 val.py
class YOLOv5Validator:
    def __init__(self, weights, data, batch_size=32, imgsz=640, conf_thres=0.001, iou_thres=0.6, max_det=300, task='val', device='', workers=8, single_cls=False, augment=False, verbose=False, save_txt=False, save_hybrid=False, save_conf=False, save_json=False, project=ROOT / 'runs/val', name='exp', exist_ok=False, half=True, dnn=False, model=None, dataloader=None, save_dir=Path(''), plots=True, callbacks=Callbacks(), compute_loss=None):
        self.weights = weights
        self.data = data
        self.batch_size = batch_size
        self.imgsz = imgsz
        self.conf_thres = conf_thres
        self.iou_thres = iou_thres
        self.max_det = max_det
        self.task = task
        self.device = device
        self.workers = workers
        self.single_cls = single_cls
        self.augment = augment
        self.verbose = verbose
        self.save_txt = save_txt
        self.save_hybrid = save_hybrid
        self.save_conf = save_conf
        self.save_json = save_json
        self.project = project
        self.name = name
        self.exist_ok = exist_ok
        self.half = half
        self.dnn = dnn
        self.model = model
        self.dataloader = dataloader
        self.save_dir = save_dir
        self.plots = plots
        self.callbacks = callbacks
        self.compute_loss = compute_loss

    def run(self):
        # Initialize/load model and set device
        training = self.model is not None
        if training:  # called by train.py
            device, pt, jit, engine = next(self.model.parameters()).device, True, False, False  # get model device, PyTorch model
            half &= device.type != 'cpu'  # half precision only supported on CUDA
            self.model.half() if half else self.model.float()
        else:  # called directly
            device = select_device(self.device, batch_size=self.batch_size)

            # Directories
            self.save_dir = increment_path(Path(self.project) / self.name, exist_ok=self.exist_ok)  # increment run
            (self.save_dir / 'labels' if self.save_txt else self.save_dir).mkdir(parents=True, exist_ok=True)  # make dir

            # Load model
            self.model = DetectMultiBackend(self.weights, device=device, dnn=self.dnn, data=self.data, fp16=self.half)
            stride, pt, jit, engine = self.model.stride, self.model.pt, self.model.jit, self.model.engine
            self.imgsz = check_img_size(self.imgsz, s=stride)  # check image size
            self.half = self.model.fp16  # FP16 supported on limited backends with CUDA
            if engine:
                self.batch_size = self.model.batch_size
            else:
                device = self.model.device
                if not (pt or jit):
                    self.batch_size = 1  # export.py models default to batch-size 1
                    LOGGER.info(f'Forcing --batch-size 1 square inference (1,3,{self.imgsz},{self.imgsz}) for non-PyTorch models')

            # Data
            self.data = check_dataset(self.data)  # check

        # Configure
        self.model.eval()
        cuda = device.type != 'cpu'
        is_coco = isinstance(self.data.get('val'), str) and self.data['val'].endswith(f'coco{os.sep}val2017.txt')  # COCO dataset
        nc = 1 if self.single_cls else int(self.data['nc'])  # number of classes
        iouv = torch.linspace(0.5, 0.95, 10, device=device)  # iou vector for mAP@0.5:0.95
        niou = iouv.numel()

        # Dataloader
        if not training:
            if pt and not self.single_cls:  # check --weights are trained on --data
                ncm = self.model.model.nc
                assert ncm == nc, f'{self.weights} ({ncm} classes) trained on different --data than what you passed ({nc} ' \
                                  f'classes). Pass correct combination of --weights and --data that are trained together.'
            self.model.warmup(imgsz=(1 if pt else self.batch_size, 3, self.imgsz, self.imgsz))  # warmup
            pad, rect = (0.0, False) if self.task == 'speed' else (0.5, pt)  # square inference for benchmarks
            self.task = self.task if self.task in ('train', 'val', 'test') else 'val'  # path to train/val/test images
            self.dataloader = create_dataloader(self.data[self.task],
                                           self.imgsz,
                                           self.batch_size,
                                           stride,
                                           self.single_cls,
                                           pad=pad,
                                           rect=rect,
                                           workers=self.workers,
                                           prefix=colorstr(f'{self.task}: '))[0]

        seen = 0
        confusion_matrix = ConfusionMatrix(nc=nc)
        names = self.model.names if hasattr(self.model, 'names') else self.model.module.names  # get class names
        if isinstance(names, (list, tuple)):  # old format
            names = dict(enumerate(names))
        class_map = coco80_to_coco91_class() if is_coco else list(range(1000))
        s = ('%22s' + '%11s' * 6) % ('Class', 'Images', 'Instances', 'P', 'R', 'mAP50', 'mAP50-95')
        tp, fp, p, r, f1, mp, mr, map50, ap50, map = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,

val.py是一个用于在检测数据集上验证训练好的YOLOv5检测模型的程序文件。它可以根据指定的参数加载模型并在指定的数据集上进行推理和评估。程序文件中包含了一些函数和类,用于处理数据、进行推理、计算损失、绘制图像等操作。程序文件还包含了一些命令行参数,用于指定模型权重文件、数据集文件、推理图像大小等参数。

5.4 yolov5-DSConv.py


class DSConv(_ConvNd):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1,
                 padding=None, dilation=1, groups=1, padding_mode='zeros', bias=False, block_size=32, KDSBias=False, CDS=False):
        padding = _pair(autopad(kernel_size, padding, dilation))
        kernel_size = _pair(kernel_size)
        stride = _pair(stride)
        dilation = _pair(dilation)

        blck_numb = math.ceil(((in_channels)/(block_size*groups)))
        super(DSConv, self).__init__(
            in_channels, out_channels, kernel_size, stride, padding, dilation,
            False, _pair(0), groups, bias, padding_mode)

        # KDS weight From Paper
        self.intweight = torch.Tensor(out_channels, in_channels, *kernel_size)
        self.alpha = torch.Tensor(out_channels, blck_numb, *kernel_size)

        # KDS bias From Paper
        self.KDSBias = KDSBias
        self.CDS = CDS

        if KDSBias:
            self.KDSb = torch.Tensor(out_channels, blck_numb, *kernel_size)
        if CDS:
            self.CDSw = torch.Tensor(out_channels)
            self.CDSb = torch.Tensor(out_channels)

        self.reset_parameters()

    def get_weight_res(self):
        # Include expansion of alpha and multiplication with weights to include in the convolution layer here
        alpha_res = torch.zeros(self.weight.shape).to(self.alpha.device)

        # Include KDSBias
        if self.KDSBias:
            KDSBias_res = torch.zeros(self.weight.shape).to(self.alpha.device)

        # Handy definitions:
        nmb_blocks = self.alpha.shape[1]
        total_depth = self.weight.shape[1]
        bs = total_depth//nmb_blocks

        llb = total_depth-(nmb_blocks-1)*bs

        # Casting the Alpha values as same tensor shape as weight
        for i in range(nmb_blocks):
            length_blk = llb if i==nmb_blocks-1 else bs

            shp = self.alpha.shape # Notice this is the same shape for the bias as well
            to_repeat=self.alpha[:, i, ...].view(shp[0],1,shp[2],shp[3]).clone()
            repeated = to_repeat.expand(shp[0], length_blk, shp[2], shp[3]).clone()
            alpha_res[:, i*bs:(i*bs+length_blk), ...] = repeated.clone()

            if self.KDSBias:
                to_repeat = self.KDSb[:, i, ...].view(shp[0], 1, shp[2], shp[3]).clone()
                repeated = to_repeat.expand(shp[0], length_blk, shp[2], shp[3]).clone()
                KDSBias_res[:, i*bs:(i*bs+length_blk), ...] = repeated.clone()

        if self.CDS:
            to_repeat = self.CDSw.view(-1, 1, 1, 1)
            repeated = to_repeat.expand_as(self.weight)
            print(repeated.shape)

        # Element-wise multiplication of alpha and weight
        weight_res = torch.mul(alpha_res, self.weight)
        if self.KDSBias:
            weight_res = torch.add(weight_res, KDSBias_res)
        return weight_res

    def forward(self, input):
        # Get resulting weight
        #weight_res = self.get_weight_res()

        # Returning convolution
        return F.conv2d(input, self.weight, self.bias,
                            self.stride, self.padding, self.dilation,
                            self.groups)

class DSConv2D(nn.Module):
    def __init__(self, inc, ouc, k=1, s=1, p=None, g=1, d=1, act=True):
        super().__init__()
        self.conv = DSConv(inc, ouc, k, s, p, g, d)

class Bottleneck_DSConv(nn.Module):
    # Standard bottleneck
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = DSConv2D(c1, c_, 1, 1)
        self.cv2 = DSConv2D(c_, c2, 3, 1, g=g)
        self.add = shortcut and c1 == c2

    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

class C3_DSConv(nn.Module):
    # C3 module with dsconv
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
        super().__init__()
        c_ = int(c2 * e)
        self.m = nn.Sequential(*(Bottleneck_DSConv(c_, c_, shortcut, g, e=1.0) for _ in range(n)))

这个程序文件是一个实现了DSConv(Depthwise Separable Convolution)的模块。DSConv是一种卷积操作,可以在减少参数数量的同时保持模型的性能。该文件定义了DSConv类和DSConv2D类,以及使用DSConv的Bottleneck_DSConv和C3_DSConv模块。

DSConv类继承自torch.nn.modules.conv._ConvNd类,它接受一些参数,包括输入通道数、输出通道数、卷积核大小、步长、填充、膨胀率、分组数等。在初始化时,它会根据输入参数计算填充大小、卷积核大小、步长和膨胀率等。它还定义了一些权重和偏置参数,以及一些方法用于计算权重和前向传播。

DSConv2D类继承自torch.nn.modules.conv.Conv类,它是DSConv的一个封装,方便在模型中使用。

Bottleneck_DSConv类是一个标准的瓶颈块,它包含了两个DSConv2D层,用于进行特征提取和特征融合。

C3_DSConv类是一个使用DSConv的C3模块,它包含了多个Bottleneck_DSConv块,用于构建深度网络。

整个程序文件实现了DSConv的定义和使用,可以在深度学习模型中替代传统的卷积操作,以减少参数数量并提高模型性能。

5.5 classify\predict.py


class YOLOv5Classifier:
    def __init__(self, weights, source, data, imgsz, device, view_img, save_txt, nosave, augment, visualize, update,
                 project, name, exist_ok, half, dnn, vid_stride):
        self.weights = weights
        self.source = source
        self.data = data
        self.imgsz = imgsz
        self.device = device
        self.view_img = view_img
        self.save_txt = save_txt
        self.nosave = nosave
        self.augment = augment
        self.visualize = visualize
        self.update = update
        self.project = project
        self.name = name
        self.exist_ok = exist_ok
        self.half = half
        self.dnn = dnn
        self.vid_stride = vid_stride

    def run(self):
        source = str(self.source)
        save_img = not self.nosave and not source.endswith('.txt')  # save inference images
        is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
        is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
        webcam = source.isnumeric() or source.endswith('.streams') or (is_url and not is_file)
        screenshot = source.lower().startswith('screen')
        if is_url and is_file:
            source = check_file(source)  # download

        # Directories
        save_dir = increment_path(Path(self.project) / self.name, exist_ok=self.exist_ok)  # increment run
        (save_dir / 'labels' if self.save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir

        # Load model
        device = select_device(self.device)
        model = DetectMultiBackend(self.weights, device=device, dnn=self.dnn, data=self.data, fp16=self.half)
        stride, names, pt = model.stride, model.names, model.pt
        imgsz = check_img_size(self.imgsz, s=stride)  # check image size

        # Dataloader
        bs = 1  # batch_size
        if webcam:
            view_img = check_imshow(warn=True)
            dataset = LoadStreams(source, img_size=imgsz, transforms=classify_transforms(imgsz[0]),
                                  vid_stride=self.vid_stride)
            bs = len(dataset)
        elif screenshot:
            dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
        else:
            dataset = LoadImages(source, img_size=imgsz, transforms=classify_transforms(imgsz[0]),
                                 vid_stride=self.vid_stride)
        vid_path, vid_writer = [None] * bs, [None] * bs

        # Run inference
        model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # warmup
        seen, windows, dt = 0, [], (Profile(), Profile(), Profile())
        for path, im, im0s, vid_cap, s in dataset:
            with dt[0]:
                im = torch.Tensor(im).to(model.device)
                im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32
                if len(im.shape) == 3:
                    im = im[None]  # expand for batch dim

            # Inference
            with dt[1]:
                results = model(im)

            # Post-process
            with dt[2]:
                pred = F.softmax(results, dim=1)  # probabilities

            # Process predictions
            for i, prob in enumerate(pred):  # per image
                seen += 1
                if webcam:  # batch_size >= 1
                    p, im0, frame = path[i], im0s[i].copy(), dataset.count
                    s += f'{i}: '
                else:
                    p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)

                p = Path(p)  # to Path
                save_path = str(save_dir / p.name)  # im.jpg
                txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')  # im.txt

                s += '%gx%g ' % im.shape[2:]  # print string
                annotator = Annotator(im0, example=str(names), pil=True)

                # Print results
                top5i = prob.argsort(0, descending=True)[:5].tolist()  # top 5 indices
                s += f"{', '.join(f'{names[j]} {prob[j]:.2f}' for j in top5i)}, "

                # Write results
                text = '\n'.join(f'{prob[j]:.2f} {names[j]

这个程序文件是一个使用YOLOv5模型进行图像分类推断的脚本。它可以在图像、视频、目录、URL、摄像头等多种来源上运行。

该脚本可以接受以下参数:

  • --weights:模型权重文件的路径,默认为yolov5s-cls.pt
  • --source:数据源的路径,可以是文件、目录、URL等,默认为data/images
  • --data:数据集配置文件的路径,默认为data/coco128.yaml
  • --imgsz:推断时的图像尺寸,默认为[224]
  • --device:使用的设备,默认为空,即自动选择。
  • --view-img:是否显示结果。
  • --save-txt:是否保存结果到文本文件。
  • --nosave:是否不保存图像/视频。
  • --augment:是否进行增强推断。
  • --visualize:是否可视化特征。
  • --update:是否更新所有模型。
  • --project:保存结果的项目路径,默认为runs/predict-cls
  • --name:保存结果的名称,默认为exp
  • --exist-ok:是否允许覆盖已存在的项目/名称。
  • --half:是否使用FP16半精度推断。
  • --dnn:是否使用OpenCV DNN进行ONNX推断。
  • --vid-stride:视频帧率的步长,默认为1。

该脚本会加载模型、数据集配置文件,并根据参数进行推断。推断结果可以保存为图像、视频和文本文件,并可以显示在屏幕上。

6.系统整体结构

整体功能和构架概述:

该项目是一个视觉项目,主要目标是实现掌纹ROI区域分割系统。该系统使用了融合分布移位卷积(DSConv)的YOLOv5模型进行目标检测和分类。项目包含了多个程序文件,用于不同的功能,包括模型训练、模型推断、图形界面等。

下面是每个文件的功能概述:

文件路径功能概述
export.py将YOLOv5模型导出为其他格式的文件
ui.py创建图形用户界面(GUI)应用程序
val.py在检测数据集上验证训练好的YOLOv5模型
yolov5-DSConv.py实现了DSConv的模块
classify/predict.py使用YOLOv5模型进行图像分类推断
classify/train.py训练一个基于YOLOv5的分类器模型
classify/val.py在验证数据集上验证训练好的分类器模型
models/common.py包含了一些通用的模型函数和类
models/experimental.py包含了一些实验性的模型函数和类
models/tf.py包含了一些与TensorFlow相关的模型函数和类
models/yolo.py包含了YOLOv5模型的定义和相关函数
models/init.py模型模块的初始化文件
segment/predict.py使用分割模型进行图像分割推断
segment/train.py训练一个图像分割模型
segment/val.py在验证数据集上验证训练好的图像分割模型
utils/activations.py包含了一些激活函数的定义
utils/augmentations.py包含了一些数据增强的函数和类
utils/autoanchor.py包含了自动锚框生成的函数和类
utils/autobatch.py包含了自动批处理的函数和类
utils/callbacks.py包含了一些回调函数的定义
utils/dataloaders.py包含了数据加载器的定义
utils/downloads.py包含了一些下载数据集的函数
utils/general.py包含了一些通用的辅助函数
utils/loss.py包含了一些损失函数的定义
utils/metrics.py包含了一些评估指标的定义
utils/plots.py包含了一些绘图函数的定义
utils/torch_utils.py包含了一些与PyTorch相关的辅助函数
utils/triton.py包含了与Triton Inference Server相关的函数和类
utils/init.py工具模块的初始化文件
utils/aws/resume.py包含了AWS训练恢复的函数和类
utils/aws/init.pyAWS模块的初始化文件
utils/flask_rest_api/example_request.py包含了Flask REST API的示例请求
utils/flask_rest_api/restapi.py包含了Flask REST API的实现
utils/loggers/init.py日志记录器模块的初始化文件
utils/loggers/clearml/clearml_utils.py包含了ClearML日志记录器的辅助函数
utils/loggers/clearml/hpo.py包含了ClearML日志记录器的超参数优化函数
utils/loggers/clearml/init.pyClearML日志记录器模块的初始化文件
utils/loggers/comet/comet_utils.py包含了Comet日志记录器的辅助函数
utils/loggers/comet/hpo.py包含了Comet日志记录器的超参数优化函数
utils/loggers/comet/init.pyComet日志记录器模块的初始化文件
utils/loggers/wandb/log_dataset.py包含了WandB日志记录器的数据集记录函数
utils/loggers/wandb/sweep.py包含了WandB日志记录器的超参数优化函数
utils/loggers/wandb/wandb_utils.py包含了WandB日志记录器的辅助函数
utils/loggers/wandb/init.pyWandB日志记录器模块的初始化文件
utils/segment/augmentations.py包含了图像分割的数据增强函数和类
utils/segment/dataloaders.py包含了图像分割的数据加载器的定义
utils/segment/general.py包含了图像分割的通用辅助函数
utils/segment/loss.py包含了图像分割的损失函数的定义
utils/segment/metrics.py包含了图像分割的评估指标的定义
utils/segment/plots.py包含了图像分割的绘图函数的定义
utils/segment/init.py图像分割模块的初始化文件

以上是每个文件的功能概述,根据文件路径进行了分类。

7.DSConv简介

卷积神经网络已被证明在计算机视觉中传统的艰巨任务中是成功的,例如图像分类和目标检测。随着AlexNet的突破,ILSVRC中创建了许多新的拓扑来实现高精度。此类网络的成功不仅将注意力转移到如何做到这一点上,而且还转移到了它运行的速度和记忆效率上。这些模型以具有数百万个参数而闻名,即使使用GPU,它也需要更多的计算时间和比许多应用程序所需的更多的存储空间。

运行卷积神经网络时所需的大部分内存和计算工作都花在了卷积层中,例ResNet50超过90%的时间/内存。这意味着,为了让网络运行得更快更高效,我们必须提高卷积层的计算负载。

考虑到这一点,研究者提出了一种新型的卷积层,我们称之为分布移位卷积(DSConv)。这种类型的层在设计时考虑了两个主要目标:

它应该大大提高标准卷积层的内存效率和速度;
它应该是标准卷积的即插即用替代品,因此它可以直接用于任何卷积神经网络,包括推理和训练。
研究者通过将传统的卷积内核分解为两个组件来实现这一点。其中之一是只有整数值的张量,不可训练,并根据预训练网络中浮点 (FP) 权重的分布进行计算。另一个组件由两个分布移位器张量组成,它们将量化张量的权重定位在模拟原始预训练网络分布的范围内:其中一个移动每个内核的分布,另一个移动每个通道。这些权重可以重新训练,使网络能够适应新的任务和数据集。

框架(DSConv layer)

在这里插入图片描述

可变量化内核(VQK):此张量仅保留可变位长整数值,并且与原始卷积张量具有相同大小的(ch0,chi,k,k),参数值被设置为从原始浮点模型量化,并且一旦设置不能改变,这是DSConv的量化组件。

分布移位:此组件的目的是移动VQK的分布以尝试模仿原始卷积内核的分布。通过使用两个张量转换两个域来实现。第一个张量是内核分布移位器(KDS),他改变每个(1,BLK,1,1)的分布。

例如,给定(128,128,3,3)的原始单精度张量大小,将位大小的超参数设置为2位且块大小设置为64,将保存2位整数的VQK的大小为(128,128,3,3)(量化后的,由单精度变整型),保持FP32编号的内核移位器(KDS)的大小为2*(128,2,3,3),保存Fp32编号的通道移位器的大小为2*(128),在此示例中,卷积内核减少到其原始大小的7%

使用此设置,VQK充当先验,它捕获特定切片应提取的特征类型的本质。

Quantization Procedure

量化函数将要量化的网络的比特数作为输入,并将带符号的整数表示来存储。

在这里插入图片描述

这是通过首先缩放每个卷积层的权重以使得原始权重w的最大绝对值与上面的量化约束的最大值匹配来实现的。再次步骤之后,将所有权重量化为最接近的整数,然后将新权重wq作为整数值存储到存储器中,以便稍后在训练和推理中使用。

Distribution Shifts

分布转移的目的是移动VQK,使得输出和原始权重张量的值是相匹配的,这是通过内核中的分布偏移(KDS)以及通道中的分布偏移(CDS)来完成的,对其进行良好的初始化是有必要的,因为他会使网络最接近最佳值,只有在达到最大精度之前才进行微调。

KL-Divergence: 内核分布器移位后产生的VQK应该具有与原始权重类似的分布。量化过程仅适用缩放因子来评估VQK的整数值

最小化L2范数:初始化内核移位器张量的值,使得逐元素乘法后的结果尽可能接近原始值。

两种方法效果是一致的。

Optimized Inference

首先将它乘以输入张量,而不是移动VQK,这意味着大部分操作将以整数值而不是浮点数计算,根据所使用的硬件,这可以通过8位操作实现2-10倍的加速。使硬件可以利用整数运算而不必使用浮点运算。

给定BLK的块大小,当chi是BLK的倍数时,该方法将执行比其原始对应物少的FP乘法的BLK倍。对于块大小为128,通过简单的将卷积层更改为DSConv,将显著减少2个量级的fp乘法

在执行给定内核中所有卷积的总和之后,将在稍微应用信道分布移位,进一步改善存储器和计算能力,如果模型在卷积运算符之后包括它,则可以将通道移位合并到BN中。

在这里插入图片描述

使用此过程,也可以很容易地计算方向传播。借助上图可以看出方向传播被简单地分解为三个简单的操作。还应该注意的是,VQK核是不可训练的,因此不需要计算∂wr/∂w的值。相反,只需要计算∂ξr/∂ξ,它的大小明显小于∂wr/∂w。
在这里插入图片描述

8.融合分布移位卷积(DSConv)YOLOv5

在掌纹ROI区域分割系统中,融合分布移位卷积(DSConv)YOLOv5扮演着关键角色。这一节将深入探讨DSConv在YOLOv5架构中的运用,其与传统方法的对比以及它所带来的优势。

DSConv在YOLOv5中的集成

DSConv,即分布偏移卷积,是一种能够有效替代标准神经网络体系结构的卷积变体。其核心在于将传统卷积内核分解为两个要素:可变量化内核(VQK)和分布偏移。这一方法在保持与原始卷积相同输出的同时,极大地降低了存储器使用并提高了计算速度。在YOLOv5中,DSConv的应用为模型带来了显著的性能提升,特别是在掌纹ROI区域分割任务上。

DSConv与深度可分离卷积的对比

DSConv作为深度可分离卷积的变体,与传统的深度可分离卷积有着一些本质上的差异。深度可分离卷积将标准卷积分解为深度卷积和逐点卷积两个步骤,这种方法在轻量级模型设计中非常受欢迎。然而,DSConv与深度可分离卷积相比,在模型性能和表现上有着更大的优势。

DSConv利用可学习的卷积核进一步提高了模型的表现。这个特性使得DSConv在卷积操作中拥有更多灵活性和表达能力,有助于模型更好地适应复杂的掌纹ROI区域分割任务。与此同时,DSConv通过整数值存储可变量化内核,在内存使用和计算速度上均有显著优势。这使得在保持高精度的同时,模型在计算效率上有了大幅提升。

DSConv在掌纹ROI区域分割任务中的优势

掌纹ROI区域分割任务对模型的要求较高,需要高效的特征提取和准确的区域分割。DSConv在此任务中展现出了其独特的优势。其能够提供更精确的特征表达,帮助模型更好地理解掌纹的复杂纹路和结构。同时,DSConv所带来的存储器使用减少和计算速度提升,使得模型在实际应用中更具可行性和效率。

Yolov5-DSConv网络结构配置文件
# YOLOv5 🚀 by Ultralytics, AGPL-3.0 license

# Parameters
nc: 1  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.25  # layer channel multiple
anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32

# YOLOv5 v6.0 backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 6, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, SPPF, [1024, 5]],  # 9
  ]

# YOLOv5 v6.0 head
head:
  [[-1, 1, Conv, [512, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, C3_DSConv, [512, False]],  # 13

   [-1, 1, Conv, [256, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 4], 1, Concat, [1]],  # cat backbone P3
   [-1, 3, C3_DSConv, [256, False]],  # 17 (P3/8-small)

   [-1, 1, Conv, [256, 3, 2]],
   [[-1, 14], 1, Concat, [1]],  # cat head P4
   [-1, 3, C3_DSConv, [512, False]],  # 20 (P4/16-medium)

   [-1, 1, Conv, [512, 3, 2]],
   [[-1, 10], 1, Concat, [1]],  # cat head P5
   [-1, 3, C3_DSConv, [1024, False]],  # 23 (P5/32-large)

   [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]

9.训练结果可视化分析

评价指标

训练损失(train/box_loss, train/seg_loss, train/obj_loss, train/cls_loss):这些损失值表示模型在训练集上的表现,包括边界框损失、分割损失、对象损失和分类损失。

指标评价(metrics/ precision、metrics/recall、metrics/mAP_0.5、metrics/mAP_0.5:0.95):这些指标分别对应准确度、识别率和不同阈值下的平均精度。这些指标分别针对两个类别(B 和 M)进行了测量。

验证损失(val/box_loss, val/seg_loss, val/obj_loss, val/cls_loss):这些损失值表示模型在验证集上的表现。

学习率(x/lr0, x/lr1, x/lr2):这些值表示训练过程中的学习率。

训练结果可视化

在这里插入图片描述
可视化提供了模型在训练周期内各种指标的性能的全面概述。现在,让我们详细分析这些结果:

训练和验证损失

框、对象和类损失(训练):随着时代的进展,所有三个损失指标(框、对象和类)都显示出下降趋势。这表明该模型正在有效地学习,提高了其预测边界框、识别对象和正确分类的能力。
框、对象和类损失(验证):验证损失也会随着时间的推移而减少,反映了训练损失。这是一个积极的信号,表明该模型没有过度拟合,并且可以很好地推广到未见过的数据。

精度、召回率和平均精度 (mAP)

精确度和召回率:精确度一开始相当低,但后来显着增加,表明模型在做出正确的积极预测方面变得越来越好。召回率也增加,表明模型查找所有相关案例的能力不断提高。
mAP@0.5 和 mAP@0.5:0.95:两个 mAP 指标都会随着时间的推移而增加。mAP@0.5 始终高于 mAP@0.5:0.95,这是预期的,因为后者是一个更严格的指标。这些指标的不断改进表明该模型在准确定位和分类对象方面的熟练程度不断提高。

学习率

学习率 ( x/lr0、x/lr1和x/lr2) 似乎随着时间的推移而降低,这在训练深度学习模型中很常见。这种逐渐减少有助于微调模型的权重,特别是当它接近最佳性能时。

10.系统整合

下图完整源码&数据集&环境部署视频教程&自定义UI界面

在这里插入图片描述

参考博客《融合分布移位卷积(DSConv)YOLO的掌纹ROI区域分割系统》

11.参考文献


[1]刘明,李丽华,李哲.基于指导滤波与二值图像组互相关匹配的3D掌纹识别[J].计算机科学.2014,(9).DOI:10.11896/j.issn.1002-137X.2014.09.058 .

[2]赵志刚,吴鑫,洪丹枫,等.基于信息熵的GLBP掌纹识别算法[J].计算机科学.2014,(8).DOI:10.11896/j.issn.1002-137X.2014.08.062 .

[3]曹雏清,李瑞峰,赵立军.基于深度图像技术的手势识别方法[J].计算机工程.2012,(8).DOI:10.3969/j.issn.1000-3428.2012.08.006 .

[4]徐婉莹,黄新生,刘育浩,等.一种基于Gabor小波的局部特征尺度提取方法[J].中国图象图形学报A.2011,(1).

[5]苑玮琦,李燕.手形特征点定位方法[J].计算机应用.2010,(12).

[6]David Lowrence.Biometrics and retail: moving towards the future[J].Biometric Technology Today.2014,2014(2).7-9.

[7]Christian Rathgeb,Andreas Uhl.A survey on biometric cryptosystems and cancelable biometrics[J].EURASIP Journal on Information Security.2011.2011

[8]Zhang, D.,Lu, G.,Li, W.,等.Palmprint Recognition Using 3-D Information[J].IEEE transactions on systems, man and cybernetics, Part C. Applications and reviews: A publication of the IEEE Systems, Man, and Cybernetics Society.2009,39(5).505-519.

[9]Xu, Shuang,Suo, Jidong,Ding, Jifeng.Improved linear discriminant analysis based on two-dimensional Gabor for palmprint recognition[C].2011.

[10]Shuang Xu,Jidong Suo,Jiyin Zhao.Research on the Location and Segmentation Technologies in Palmprint Identification[C].2011.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值