ubuntu システム上の pyinstaller を使用して yolov5 プロジェクト コードを暗号化してパッケージ化する詳細な手順

0.背景

最近、自分で書いた yolov5 ベースのプロジェクト コードを ubuntu 18.04 のバイナリ ファイルにパッケージ化する必要があります。これは展開に便利で、ソース コードの公開を最小限に抑えることができます。

インターネット上の多くのチュートリアルを参照してください。そのほとんどは Win 上で行われており、ubuntu 18.04 では詳細なパッケージ化手順がないようです。

1. 仮想環境を作成する

ここでは、クリーンな Python 環境を作成するために anaconda を選択します。ここでの Python バージョンは 3.8 ですが、他の Python バージョンはほとんど影響を与えません。後で pyinstaller でパッケージ化されるのは、この環境の依存関係です。

以下の操作では一部のライブラリを使用しますので、コマンド実行時にエラーが発生する場合は、お客様ご自身でライブラリをインストールしてください。

まず、yolov5-v4.0 をダウンロードします。ここで v4.0 を選択することに特別な意味はありません。単に v4.0 を使用しているだけです。他のバージョンでも動作するはずです。

git clone https://gitee.com/monkeycc/yolov5.git -b v4.0

次に、仮想環境を作成します。ここで確認が必要です你的显卡型号、cuda版本是否和pytorch版本适配(可以去Pytorch官网查看)。適切でない場合は、後でエラーが報告される可能性があります。ここでの私の cuda バージョンは 11.1 です。ここで選択しますpytorch 1.10.0

直接的なことはしないでくださいpip install -r requirements.txt。他の仮想環境のトーチにダメージを与えることになります。

conda create -n pyinstaller python=3.8
conda activate pyinstaller 

pip install torch==1.10.0+cu111 torchvision==0.11.0+cu111 torchaudio==0.10.0 -f https://download.pytorch.org/whl/torch_stable.html
pip install pyyaml numpy opencv-python matplotlib scipy tqdm pandas seaborn -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install pyinstaller -i https://pypi.tuna.tsinghua.edu.cn/simple

cd yolov5

最後に、重みをダウンロードします (リンク: https://pan.baidu.com/s/1uVa5eylGETYN0tUB2adjcQ
抽出コード: ruwf)。ここでは、yolov5s.pt を ./yolov5/weights にダウンロードします。自分で画像test.pngをプロジェクトアドレス./yolov5に置き、以下のコマンドを実行してテストすると、概ね問題ありません

python detect.py --source test.png --weights weights/yolov5s.pt

2.pyinstallerのパッケージ化

pyinstaller コマンドは、ターゲットの Python コードが配置されているdetect.pyディレクトリで実行する必要があります./yolov5ここでは 2 つのステップに分かれています。最初のステップはスペック ファイルの生成であり、2 番目のステップはスペック ファイルのパラメータを変更してバイナリ ファイルを再生成することです。

2.1. スペックファイルの生成と変更

次のコマンドを実行します

cd yolov5 

pyinstaller -D detect.py

生成された仕様ファイルはdetect.specディレクトリ にありますyolov5。次のステップではdetect.spec、主に外部ライブラリと外部リソースを指定するためにそれを変更します。

ここでのspecファイルのパラメータの意味については、ブログ「【Pythonサードパーティライブラリ】pyinstallerチュートリアルとspecリソースファイルの紹介」とブログ「pyinstallerのスペックファイル詳細解説」を参照していただき、ここでは変更が必要なパラメータを紹介します。

  • Analysis のscriptsパラメータは下図の通りで、
    ここに画像の説明を挿入
    ここでの scripts パラメータは .py ファイルのリストで、デフォルトは対象となる Python コードですdetect.pyここでは detect.py を実行するだけなので、冗長な Python コードを記述する必要はありません。detect.py に加えて他の Python コードを実行したい場合は、Python コードをリストに追加できます。

  • Analysis のパラメータはpathex下図のとおりです.
    ここに画像の説明を挿入
    ここでの pathex パラメータはフォルダのリストです. 空の場合は, デフォルトで対象の Python コードが置かれているdetect.pyディレクトリの絶対アドレスになります./yolov5; ここでは,通常、カスタム ライブラリが追加されるディレクトリ。yolov5 プロジェクトに必要なカスタム ライブラリはutilsにあるため./yolov5、ここのパラメータはpathexデフォルトにすることができます。

  • Analysis のパラメータはdatas、以下の図に示すように
    ここに画像の説明を挿入
    、ここでの datas パラメータは、ピクチャ/ピクチャ ディレクトリ、データベース/データベース ディレクトリ、構成/構成など、Python コード以外のリソースがある場合のリソース ディレクトリ/リソースのリストです。ディレクトリ、ウェイトファイル/ウェイトファイルディレクトリなどをここに記述する必要があります。たとえば、ウェイト ファイルは、ルート ディレクトリ であるディレクトリyolov5s.ptに保存され、実際のバイナリ ファイルが実行されるルート ディレクトリは であるため、このルート ディレクトリにコピーする必要があります./yolov5/weights./yolov5./yolov5/dist/detectweights

  • Analysis のhiddenimportsパラメータは次の図に示すとおりです
    ここに画像の説明を挿入
    。ここでの hiddenimports パラメータは、サードパーティ ライブラリ名のリストです。エラーが報告された場合ModuleNotFoundError: No module named 'xxx'、つまりサードパーティのライブラリが使用できない場合はimport、モジュール名をリストに追加します。

最終的な完全なdetect.specファイルは次のとおりです。

# -*- mode: python ; coding: utf-8 -*-


block_cipher = None


a = Analysis(
    ['detect.py'],
    pathex=[],
    binaries=[],
    datas=[('models','./models'), ('weights', './weights'), ('data', './data')],
    hiddenimports=['utils', 'utils.autoanchor'],
    hookspath=[],
    hooksconfig={
    
    },
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='detect',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)
coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='detect',
)
2.2. バイナリの再構築

変更後detect.spec、次のようにバイナリファイルを再生成します。

pyinstaller detect.spec

生成プロセス中に、「はい」というプロンプトが表示され、元のディレクトリ内のすべてのファイルが削除されます。下の図に示すように、./yolov5/dist/detect直接入力するだけです。事故がなければ、すぐに正常に完了したことがわかります。y
ここに画像の説明を挿入

生成されたバイナリが配置されるディレクトリは ですyolov5/dist/detect

3. テスト

ここで、Python 環境を終了し、次のようにバイナリ ファイルを実行します。

conda deactivate pyinstaller

cd dist/detect

./detect --source ../../test.png --weights weights/yolov5s.pt

ここで、テスト結果はyolov5/dist/detect/runsにあります。

4. 暗号化されたパッケージング

逆コンパイルを防ぐために暗号化されたコンパイル。

pycrypto サードパーティ ライブラリをインストールします。

pip install pycrypto -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install tinyaes -i https://pypi.tuna.tsinghua.edu.cn/simple

暗号化とパッケージ化は次の手順に従います。

  • 他の Python コード (detect.py) をサードパーティ ライブラリとして呼び出すエントリ関数 main.py を作成します。
  • detect.py を変更して、エントリ関数 main.py の呼び出しを容易にします。
  • コマンドラインを使用してファイルpyinstaller -D main.pyを生成します。main.spec
  • main.specファイルを変更し、バイナリを再生成します
4.1. エントリ関数 main.py を作成する

エントリ関数が作成される理由は、主にビジネス コードを公開せずにサードパーティ ライブラリとしてビジネス コードを導入するためであり、同時に、暗号化プロセスは主にサードパーティ ライブラリのビジネス コードに対して行われます。ビジネスコードが逆コンパイルできないようにしてください。

# coding=utf8

from mydetect import detect
import argparse
import torch


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)')
    parser.add_argument('--source', type=str, default='data/images', help='source')  # file/folder, 0 for webcam
    parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
    parser.add_argument('--conf-thres', type=float, default=0.25, help='object confidence threshold')
    parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS')
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    parser.add_argument('--view-img', action='store_true', help='display results')
    parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
    parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
    parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')
    parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
    parser.add_argument('--augment', action='store_true', help='augmented inference')
    parser.add_argument('--update', action='store_true', help='update all models')
    parser.add_argument('--project', default='runs/detect', help='save results to project/name')
    parser.add_argument('--name', default='exp', help='save results to project/name')
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    opt = parser.parse_args()
    print(opt)

    with torch.no_grad():
        weights = opt.weights
        source = opt.source 
        img_size = opt.img_size
        conf_thres = opt.conf_thres
        iou_thres = opt.iou_thres
        device = opt.device
        view_img = opt.view_img
        save_txt = opt.save_txt
        save_conf = opt.save_conf
        classes = opt.classes
        agnostic_nms = opt.agnostic_nms
        augment = opt.augment
        update = opt.update
        project = opt.project
        name = opt.name
        exist_ok = opt.exist_ok
        
        detect(weights, source, img_size, conf_thres, iou_thres, device, view_img, save_txt, save_conf, classes, agnostic_nms, augment, update, project, name, exist_ok)
4.2. detect.py を変更する

detect.py を mydetect.py としてコピーし、mydetect.py を次のように変更します

# coding=utf8
import argparse
import time
from pathlib import Path

import cv2
import torch
import torch.backends.cudnn as cudnn
from numpy import random

from models.experimental import attempt_load
from utils.datasets import LoadStreams, LoadImages
from utils.general import check_img_size, non_max_suppression, apply_classifier, scale_coords, xyxy2xywh, \
    strip_optimizer, set_logging, increment_path
from utils.plots import plot_one_box
from utils.torch_utils import select_device, load_classifier, time_synchronized

# 这里主要将detect()的参数修改下
def detect(weights, source, img_size, conf_thres, iou_thres, device, view_img, save_txt, save_conf, classes, agnostic_nms, augment, update, project, name, exist_ok, save_img=False):
    #source, weights, view_img, save_txt, imgsz = opt.source, opt.weights, opt.view_img, opt.save_txt, opt.img_size
    imgsz = img_size
    webcam = source.isnumeric() or source.endswith('.txt') or source.lower().startswith(
        ('rtsp://', 'rtmp://', 'http://'))

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

    # Initialize
    set_logging()
    device = select_device(device)
    half = device.type != 'cpu'  # half precision only supported on CUDA

    # Load model
    model = attempt_load(weights, map_location=device)  # load FP32 model
    imgsz = check_img_size(imgsz, s=model.stride.max())  # check img_size
    if half:
        model.half()  # to FP16

    # Second-stage classifier
    classify = False
    if classify:
        modelc = load_classifier(name='resnet101', n=2)  # initialize
        modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=device)['model']).to(device).eval()

    # Set Dataloader
    vid_path, vid_writer = None, None
    if webcam:
        view_img = True
        cudnn.benchmark = True  # set True to speed up constant image size inference
        dataset = LoadStreams(source, img_size=imgsz)
    else:
        save_img = True
        dataset = LoadImages(source, img_size=imgsz)

    # Get names and colors
    names = model.module.names if hasattr(model, 'module') else model.names
    colors = [[random.randint(0, 255) for _ in range(3)] for _ in names]

    # Run inference
    t0 = time.time()
    img = torch.zeros((1, 3, imgsz, imgsz), device=device)  # init img
    _ = model(img.half() if half else img) if device.type != 'cpu' else None  # run once
    for path, img, im0s, vid_cap in dataset:
        img = torch.from_numpy(img).to(device)
        img = img.half() if half else img.float()  # uint8 to fp16/32
        img /= 255.0  # 0 - 255 to 0.0 - 1.0
        if img.ndimension() == 3:
            img = img.unsqueeze(0)

        # Inference
        t1 = time_synchronized()
        pred = model(img, augment=augment)[0]

        # Apply NMS
        pred = non_max_suppression(pred, conf_thres, iou_thres, classes=classes, agnostic=agnostic_nms)
        t2 = time_synchronized()

        # Apply Classifier
        if classify:
            pred = apply_classifier(pred, modelc, img, im0s)

        # Process detections
        for i, det in enumerate(pred):  # detections per image
            if webcam:  # batch_size >= 1
                p, s, im0, frame = path[i], '%g: ' % i, im0s[i].copy(), dataset.count
            else:
                p, s, im0, frame = path, '', im0s, getattr(dataset, 'frame', 0)

            p = Path(p)  # to Path
            save_path = str(save_dir / p.name)  # img.jpg
            txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')  # img.txt
            s += '%gx%g ' % img.shape[2:]  # print string
            gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh
            if len(det):
                # Rescale boxes from img_size to im0 size
                det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()

                # Print results
                for c in det[:, -1].unique():
                    n = (det[:, -1] == c).sum()  # detections per class
                    s += f'{n} {names[int(c)]}s, '  # add to string

                # Write results
                for *xyxy, conf, cls in reversed(det):
                    if save_txt:  # Write to file
                        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh
                        line = (cls, *xywh, conf) if opt.save_conf else (cls, *xywh)  # label format
                        with open(txt_path + '.txt', 'a') as f:
                            f.write(('%g ' * len(line)).rstrip() % line + '\n')

                    if save_img or view_img:  # Add bbox to image
                        label = f'{names[int(cls)]} {conf:.2f}'
                        plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=3)

            # Print time (inference + NMS)
            print(f'{s}Done. ({t2 - t1:.3f}s)')

            # Stream results
            if view_img:
                cv2.imshow(str(p), im0)

            # Save results (image with detections)
            if save_img:
                if dataset.mode == 'image':
                    cv2.imwrite(save_path, im0)
                else:  # 'video'
                    if vid_path != save_path:  # new video
                        vid_path = save_path
                        if isinstance(vid_writer, cv2.VideoWriter):
                            vid_writer.release()  # release previous video writer

                        fourcc = 'mp4v'  # output video codec
                        fps = vid_cap.get(cv2.CAP_PROP_FPS)
                        w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                        h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                        vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*fourcc), fps, (w, h))
                    vid_writer.write(im0)

    if save_txt or save_img:
        s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ''
        print(f"Results saved to {save_dir}{s}")

    print(f'Done. ({time.time() - t0:.3f}s)')


if __name__ == '__main__':
    print("运行mydetect.py!")

4.3. 暗号化して main.spec ファイルを生成する

pyinstaller コマンドは Python 環境で実行する必要があることに注意してください。

pyinstaller -D  main.py

生成された main.spec ファイルは ./yolov5 ディレクトリにあります。

4.4. main.spec ファイルを変更する

暗号化なしの場合と比較すると、block_cipher = pyi_crypto.PyiBlockCipher(key='123456')main.spec ファイル全体は次のようになります。

# -*- mode: python ; coding: utf-8 -*-


block_cipher = pyi_crypto.PyiBlockCipher(key='123456')


a = Analysis(
    ['main.py'],
    pathex=[],
    binaries=[],
    datas=[('models','./models'), ('weights', './weights'), ('data', './data')],
    hiddenimports=['utils', 'utils.autoanchor'],
    hookspath=[],
    hooksconfig={
    
    },
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='main',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)
coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='detect',
)
4.5. バイナリの生成
pyinstaller main.spec
4.6. テスト

yolov5/dist/main と入力すると、確認できます。

./main --source ../../test.png --weights weights/yolov5s.pt

問題がなければ、推論結果は ./yolov5/dist/main/runs にあります。

おすすめ

転載: blog.csdn.net/qq_30841655/article/details/128583336