本文へ移動
実績一覧

Shelfree: 顔認証とRFIDを使った無人図書貸出システム

顔認証とRFIDで利用者と本を識別し、Webから貸出状況と返却期限を確認できる無人図書貸出システム。チームでKloud ハッカソン#3のデザイン賞を受賞しました。

Kloud ハッカソン#3
Shelfreeのロゴと、顔認証・RFIDを使った図書貸出画面

2023年8月、最大3人のチームで参加したKloud ハッカソン#3に向けて、顔認証とRFIDを使った無人図書貸出システム「Shelfree」を制作しました。部活動や同好会、研究室のように、専任の管理者を置くことが難しい場所でも、そこにいる人たちがおすすめの本を共有できる仕組みです。

この作品はチームで企画・制作し、デザイン賞を受賞しました。システム全体は共同成果です。その中で私は、Webアプリケーションと、顔認証・RFIDなどの機器側から受け取った情報を貸出処理へつなぐ部分を担当しました。

ShelfreeのKloud ハッカソン#3発表資料

本を借りるために必要なのは、顔と本だけ

一般的な小規模の図書管理では、台帳への記入や管理者への声かけが必要です。しかし、管理を担当する人がいつもその場にいるとは限りません。手続きが面倒になると、個人や研究室が持っている本を共有する機会そのものが減ってしまいます。

そこでShelfreeでは、次の流れで貸出を完了できる体験を考えました。

  1. カメラの前に立ち、事前登録した顔画像から利用者を識別する
  2. 借りたい本をRFIDリーダーへ近づけ、貼付したタグから本を識別する
  3. 画面で利用者と本を確認し、まとめて貸出処理を実行する
  4. Webアプリから貸出状況と返却期限を確認する
  5. 返却ボックスへ本を入れ、管理者へのLINE通知につなげる
Shelfreeの本一覧、詳細、プロフィール、貸出履歴を表示したモバイル画面
Shelfreeのモバイル画面

単に管理画面をつくるのではなく、利用者が本を手に取ってから借り終えるまでの操作を短くすることを目指しました。同時に、貸出中かどうか、誰が借りているか、いつまでに返すかをWebから確認できるようにし、利用者側と管理者側の両方から状態が見えるようにしています。

チーム全体のシステム構成

システムは、ブラウザで使うWebアプリ、Raspberry Pi上のHTTPサーバー、顔認証、RFIDリーダー、返却ボックスで構成しました。

Next.js、Flask、顔認証、RFIDリーダー、返却ボックスを接続したShelfreeのシステム構成図
Shelfreeのシステム構成
  • Next.js 13 / TypeScript: 本の一覧・詳細、プロフィール、貸出履歴、貸出確認画面、Route Handlerを実装
  • Clerk: Web上のアカウントと、顔認証結果に含まれる利用者IDを接続
  • Prisma / Vercel Postgres: 本、所有者、貸出日、返却期限、返却日時を保存
  • Zod: Python側から届く利用者・本・時刻のペイロードと、Web APIのレスポンスを実行時に検証
  • TanStack Query: 機器側の状態取得と、本・利用者情報の再取得を管理
  • Python 3 / Flask: Raspberry Pi上で、顔認証とRFIDの読み取り結果をWebへ渡すHTTPサーバーを構成
  • face_recognition: Webカメラの映像と事前登録画像の特徴を比較し、利用者IDを取得
  • MFRC522 / MIFARE Classic 1K: 本へ貼り付けたRFIDタグの識別子を読み取る

返却ボックス側では、センサー、モーター、カメラ、RFIDリーダーを組み合わせ、本が貸出中かを確認してから受け付ける構成を検討しました。Webだけ、機器だけで閉じず、両者が同じ本と貸出状態を参照することがシステムの中心です。

私が担当したWebアプリと機器連携

私は、Next.jsの画面とAPI、認証、データモデルを実装し、機器側が取得した「誰が」「どの本を」借りようとしているかという情報を、Web上の確認と貸出記録へ接続しました。公開リポジトリでは、Webアプリのコミット履歴を確認できます。

貸出画面は、Raspberry Pi上のFlaskサーバーから2秒ごとに状態を取得します。顔認証が完了すると利用者が、本をRFIDリーダーへ近づけると選択中の本が画面に現れます。確定時には確認済みのペイロードをNext.jsのRoute Handlerへ送り、貸出記録を作成します。

機器側の状態を貸出画面へ反映する処理の抜粋
const { data } = useQuery({
  queryKey: ["payload"],
  queryFn: async () => {
    const response = await fetch("http://localhost:5000/")
    return PayloadSchema.parse(JSON.parse(await response.text()))
  },
  refetchInterval: 2000,
})

機器から届くJSONをそのまま信用せず、Zodで形と日時を検証します。本はRFIDタグの識別子をIDとして扱い、顔認証結果にはClerkの利用者IDと認識時刻を含めました。

顔認証・RFIDの結果を表すペイロード
const PayloadSchema = z.object({
  book: z.record(z.string().datetime({ offset: true })),
  person: z
    .object({
      id: z.string().min(1),
      seenAt: z.string().datetime({ offset: true }),
    })
    .nullable(),
  timestamp: z.string().datetime({ offset: true }),
  signature: z.string().min(1),
  confirm: z.boolean().optional(),
})

古い認証結果や二重貸出を受け付けない

貸出APIでは、ペイロードの検証に加えて、認証結果の鮮度と現在の貸出状態を確認しました。

貸出前の状態確認と返却期限の計算
if (new Date(payload.person.seenAt).getTime() < Date.now() - 5 * 60 * 1000) {
  throw new Error("顔認証から5分以上経過しています")
}

if (book.transactions.length > 0) {
  throw new Error(`${book.title} は貸出中です`)
}

await prismaClient.transaction.create({
  data: {
    book: { connect: { id: book.id } },
    userId: user.id,
    dueAt: new Date(Date.now() + book.availableDays * 24 * 60 * 60 * 1000),
  },
})

APIはClerk上に利用者が存在するか、顔認証から5分以内か、対象の本が登録済みで貸出中ではないかを確かめます。すべての本を確認してから、それぞれのavailableDaysを使って返却期限を保存します。

一方、リポジトリにある実装では、ペイロードのsignatureは存在を確認しているだけで、暗号学的な署名検証はTODOとして残っています。そのため、改ざんを検出できる完成した署名方式としては扱えません。機器とWebをつなぐプロトタイプを動かすだけでなく、信頼境界をどこに置き、どの検証を完成させる必要があるかまで設計することが、その後の課題として残りました。

デザイン賞と、ハードウェアを含む体験設計

Kloud ハッカソン#3のテーマは「コードで繋がる!夏の友情物語」でした。Shelfreeでは、本を管理するための業務システムとしてではなく、おすすめの本をきっかけに人と人がつながる体験として提案しました。

画面上の情報量を増やしすぎず、顔を映す、本をかざす、確認するという物理的な動きと画面の状態を対応させました。本の表紙、貸出可否、返却期限、所有者のメッセージをモバイルでも追いやすくし、発表資料からWeb画面、実機の利用フローまで一つのトーンで揃えました。チームはこの作品でデザイン賞を受賞しました。

学んだこと

この制作で学んだのは、Webとハードウェアの連携では、APIを一つ用意するだけでは体験が完成しないということです。カメラやRFIDの認識は時間とともに状態が変わり、同じ本がすでに借りられている可能性もあります。画面、認証、データベース、機器のそれぞれで、いつの情報を正しいものとして扱うかを揃える必要があります。

また、ハッカソンで動くプロトタイプと、継続運用できる貸出システムの間には、署名検証、エラーからの復旧、複数冊を記録するときのトランザクション、返却処理の信頼性など、埋めるべき差があります。完成した部分だけでなく残った制約を把握できたことも、Webと現実の機器をつないだこの制作から得た経験です。

出典

次の実績

TableCast:料理選びを相談できる、卓上のAI接客・注文システム — GPT-Live-1とAgent Pluginを活用した接客・店舗管理