Swift Packageを作成してみた

# 本日のテーマ 
OepnCVを呼び出すC++ライブラリとObjcとの連携処理をXcodeで書こうとすると地味に面倒。

## これが何かと面倒である (読み飛ばすところ)

事前にC++をビルドするのにOpenCVのヘッダーパスを通す必要がある。`$ brew install opencv@2`は地味に長い。
C++をビルドするとframeworkとしてXcodeで呼び出せるが、frameworkと連携するObjc側もOpenCVを呼ぶ必要があるので、Xcodeに「Header Search Path」やら「Bridging-Header」を設定する必要があって煩雑。
開発初期に誰かが設定すれば解決するのだが、たまに作ろうとするとメモがないとハマる。
ついでに複数人でXcode Projectを触ると時々コンフリクトで壊れるので設定しなおすわけですね orz
C++側のコードでおかしな部分が見つかれば修正して再パッケージとメンテナンスも手間。
修正のループが起きる箇所はXcode上でまとめて全部やってしまいたい。。
そんな痒いところにSwift Pacageが届くみたい。

## Swift Packageを試す

### OpenCVのXCFramework対応

最初に地味に手間なのが、OpenCVのXCFramework対応。
作っておくとXcodeだけでC++のビルドが回せるようになる。
※ [opencv_option.txt](https://github.com/otmb/SwiftOpenPose-body25/blob/main/opencv_option.txt)の部分は外したいライブラリを予め用意

```bash
$ git clone https://github.com/opencv/opencv.git
$ cd opencv
$ git checkout -b 4.5.5 refs/tags/4.5.5  

$ python3 platforms/ios/build_framework.py ios \
`cat ../opencv_option.txt` \
--build_only_specified_archs True \
--iphoneos_archs arm64

$ python3 platforms/ios/build_framework.py ios_simulator \
`cat ../opencv_option.txt` \
--build_only_specified_archs True \
--iphonesimulator_archs x86_64 

$ xcodebuild -create-xcframework  \
-framework ios/opencv2.framework \
-framework ios_simulator/opencv2.framework \
-output opencv2.xcframework
```


### Swift Packageの作成

Xcodeの[File]->[New]->[Package]からSwift Packageを作成。
※ Xcodeから操作はしにくいので、コマンドラインから作業する方が楽。
Sourcesに必要なファイルをCPP/Objcのコードを分けて放り込む。
分け方はproductsのlibrary単位に分けることになる。
今回は「PoseDecoderCPP」にC++、「PoseDecoder」にCPPの連携処理としてObjcを分けて用意した。
なお、言語が混ざるとエラーになるのでコードを言語毎に分けるのは必須。

### 全体のツリー構造

```
PoseDecoder
├── Frameworks
│   └── opencv2.xcframework
├── Package.swift
├── README.md
└── Sources
    ├── PoseDecoder
    │   ├── PoseDecoder.mm
    │   └── include
    │       └── PoseDecoder.h
    ├── PoseDecoderCPP
    │   ├── include
    │   │   ├── hpe_model_openpose.h
    │   │   ├── openpose_decoder.h
    │   │   ├── results.h
    │   │   └── utils
    │   │       └── common.hpp
    │   └── src
    │       ├── hpe_model_openpose.cpp
    │       └── openpose_decoder.cpp
    └── module.modulemap
```

### Package.swift

[Package.swift](https://github.com/otmb/SwiftOpenPose-body25/blob/main/PoseDecoder/Package.swift)に全体の構造をまとめる。

```swift
import PackageDescription

let package = Package(
    name: "PoseDecoder",
    products: [
        .library(
            name: "PoseDecoder",
            targets: ["PoseDecoder"]),
        .library(
            name: "PoseDecoderCPP",
            targets: ["PoseDecoderCPP"]),
    ],
    dependencies: [],
    targets: [
        .binaryTarget(
            name: "opencv2",
            path: "Frameworks/opencv2.xcframework"),
        .target(
            name: "PoseDecoderCPP",
            dependencies: ["opencv2"],
            path: "Sources/PoseDecoderCPP",
            publicHeadersPath: "include"
        ),
        .target(
            name: "PoseDecoder",
            dependencies: ["PoseDecoderCPP", "opencv2"],
            path: "Sources/PoseDecoder",
            publicHeadersPath: "include"
        ),
    ],
    cxxLanguageStandard: .cxx14
)
```

### module.modulemap

Sources配下に[module.modulemap](https://github.com/otmb/SwiftOpenPose-body25/blob/main/PoseDecoder/Sources/module.modulemap)を配置(上のツリー構造参照)し、products.library毎にpublicにするheaderを設定。

```bash
module PoseDecoder {
    header "include/PoseDecoder.h"
    export *
}

module PoseDecoderCPP {
    header "include/hpe_model_openpose.h"
    header "include/openpose_decoder.h"
    export *
}
```

今回はこれだけ設定すると後はよしなにやってくれたのでありがたい。
これまでと比べ面倒な設定が一気に減った感がある。

## Swift Packageが出来ればProjectに追加

1. [File]->[Add Packages...]->[Add Local...]からSwift Package選択
2. 「Frameworks Libraries and Embedded Content」の「＋」からLibraryを選択して追加


### 今回作成したリポジトリ

- [SwiftOpenPose-body25](https://github.com/otmb/SwiftOpenPose-body25)

OpenPoseのbody25の姿勢推定をCoreMLで試せるようにしてみました。
Intelの方が書いた高速なPCM/PAFのC++コードを組み込んだので推論後の処理がとても速いです。

![body25.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35973/f195f787-9da3-057a-0191-59be1c10d74f.jpeg)


## 追記 2025/01/10

`opencv2.xcframework` M1 Mac対応版のメモです。

最近はmiseでPythonバージョン設定して実行してます。
virtualenvを使うと安定してビルドできるようです。
手順を残しておきます。

```bash
$ git clone https://github.com/opencv/opencv.git -b 4.11.0 --depth 1
$ cd opencv
$ curl -LO https://raw.githubusercontent.com/mbotsu/KeypointDecoder/main/opencv_option.txt
```

opencv_option.txtは最小構成で設定している。適宜必要なライブラリを調整ください。

```.mise.toml
[tools]
python = "3.10"

[env]
_.python.venv = { path = ".venv", create = true }
```


```build.sh
#/bin/bash

.venv/bin/python platforms/ios/build_framework.py ios \
`cat ./opencv_option.txt` \
--build_only_specified_archs True \
--iphoneos_archs arm64 \
--iphoneos_deployment_target 16.0

.venv/bin/python platforms/ios/build_framework.py ios_simulator \
`cat ./opencv_option.txt` \
--build_only_specified_archs True \
--iphonesimulator_archs 'arm64,x86_64' \
--iphoneos_deployment_target 16.0

xcodebuild -create-xcframework  \
-framework ios/opencv2.framework \
-framework ios_simulator/opencv2.framework \
-output opencv2.xcframework
```

```bash
$ mise trust
$ bash build.sh
```

`opencv2.xcframework`ができ上がるのでXcodeに取り込んで利用します。