この記事はZOZO Advent Calendar 2023Vol.5の7日目の記事です。
はじめに
こんにちは。株式会社ZOZOでiOSエンジニアをしている@rei_nakaokaです。1年ぶりの投稿です。使わなくなったiPhoneで何かしてみるシリーズ第二弾として今回はiPhoneを簡易的なMLサーバーにしてみました。
使用したライブラリ・フレームワーク
Vaporとは
VaporとはSwiftでWebアプリを作るためのフレームワークです。実はこのVaporですが、iOSに対応しており、アプリ上でWebサーバーを構築することができます。リポジトリのPackage.swiftの中身を見るとわかります。
let package = Package(
name: "vapor",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6)
],
iOSアプリ上でWebサーバーを構築する
まずはiOSアプリ上でWebサーバーを起動してみます。他のWebフレームワークと同じような感じでサーバーの設定なりエンドポイントを記述します。
import Vapor
final class Server {
var app: Application
let port: Int
init(port: Int) {
self.port = port
app = Application(.development)
}
func start() throws {
Task(priority: .background) {
do {
try configure(app)
try app.run()
} catch {
fatalError(error.localizedDescription)
}
}
}
func configure(_ app: Application) throws {
app.http.server.configuration.hostname = "0.0.0.0"
app.http.server.configuration.port = port
try defineRoutes(app)
}
func defineRoutes(_ app: Application) throws {
app.get { req in
"Hello, world!"
}
}
}
そして上記のstartメソッドをアプリ起動時に呼ぶようにして、ビルドします。
@main
struct Vapor_with_iOSApp: App {
let server = Server(port: 8080)
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
try? server.start()
}
}
}
}
そして、iPhoneのプライベートIPを調べてcurlコマンドでリクエストを送ってみると「Hello, world!」が無事返ってきます。
$ curl http://(プライベートIP):8080
Hello, world!
画像認識エンドポイントの追加
無事にサーバーが起動できていることが確認できたので、次は画像認識エンドポイントを追加します。今回は送られてきた画像に何の文字が書かれているのかを判定してレスポンスとして返します。文字の判定にはVisionフレームワークを使用します。
まずはテキスト認識クラスを定義します。
import Vapor
import Vision
class TextRecognizer {
func recognizeText(from imageData: Data, on eventLoop: EventLoop) -> EventLoopFuture<String> {
let promise = eventLoop.makePromise(of: String.self)
let handler = VNImageRequestHandler(data: imageData, options: [:])
let request = VNRecognizeTextRequest { (request, error) in
guard let observations = request.results as? [VNRecognizedTextObservation],
error == nil else {
promise.fail(error ?? Abort(.internalServerError))
return
}
let recognizedText = observations.compactMap { $0.topCandidates(1).first?.string }.joined(separator: "\n")
promise.succeed(recognizedText)
}
do {
try handler.perform([request])
} catch {
promise.fail(error)
}
return promise.futureResult
}
}
次にテキスト認識のエンドポイントを追加します。
let let textRecognizer = TextRecognizer()
func defineRoutes(_ app: Application) throws {
app.get { req in
"Hello, world!"
}
app.post("recognize-text") { [self] req -> EventLoopFuture<String> in
let imageBase64Data = try req.content.decode(ImageData.self)
guard let imageData = Data(base64Encoded: imageBase64eData.image) else {
throw Abort(.badRequest)
}
return textRecognizer.recognizeText(from: imageData, on: req.eventLoop)
}
}
再ビルドしてcurlコマンドでBase64にエンコードした以下の画像を送ってみます。
$ base64 -i sample.png -o image.txt
$ curl -X POST -H "Content-Type: application/json" \
-d '{"image": "'$(cat image.txt)'"}' \
http://(プライベートIP):8080/recognize-text
ZOZOTOWN
無事に認識結果が返ってきました。
まとめ
今回はVapor+VisionフレームワークでiPhoneをMLサーバー化してみました。iOS上で動かすことができるのでVisionなどのAppleの公式のフレームワークと組み合わせて使用することができるのが魅力的ですね。
全体の実装は以下のリポジトリにあるので気になる方はこちらから見てみてください。
参考