<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Goodix on mimoexのブログ</title>
    <link>https://www.mimoex.net/tags/goodix/</link>
    <description>Recent content in Goodix on mimoexのブログ</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>ja</language>
    <lastBuildDate>Fri, 07 Aug 2026 23:39:00 +0900</lastBuildDate><atom:link href="https://www.mimoex.net/tags/goodix/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>UbuntuでZephyrus G15のGoodix指紋センサーを動かすためにlibfprintを改造している【途中経過】</title>
      <link>https://www.mimoex.net/260807-zephyrus-g15-goodix-fingerprint-ubuntu/</link>
      <pubDate>Fri, 07 Aug 2026 23:39:00 +0900</pubDate>
      
      <guid>https://www.mimoex.net/260807-zephyrus-g15-goodix-fingerprint-ubuntu/</guid>
      <description>Ubuntuで未対応だったASUS ROG Zephyrus G15のGoodix指紋センサーを動かすため、PSK修復、画像取得、指検出、libfprintの状態管理まで調査・修正した記録。</description>
      <content:encoded><![CDATA[<p>ASUS ROG Zephyrus G15（GA503QR）には指紋センサーが搭載されている。</p>
<p>自分の環境ではUSBデバイスとしては認識されていた。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> lsusb
</span></span><span class="line"><span class="cl"><span class="go">Bus ... Device ...: ID 27c6:521d Shenzhen Goodix Technology Co.,Ltd. FingerPrint
</span></span></span></code></pre></div><p>Goodix製の<code>27c6:521d</code>である。</p>
<p>ところがUbuntuでは、USBデバイスとして見えていることと、指紋センサーとして利用できることはまったく別だった。</p>
<p>標準のlibfprintではこのデバイスを利用できず、<code>fprintd</code>から見ると指紋センサーとして正常に扱えない。</p>
<p>そこで、Goodix 52XD系をサポートするコミュニティ版libfprintをベースに、自分のZephyrus G15で動作するところまで修正してみることにした。</p>
<p>この記事は、その過程の記録である。</p>
<p>ソースコードの書き換えを行ったのは、現状、
driverディレクトリのgoodix.cのみである。</p>
<p>
  <a href="https://github.com/barsikus007/libfprint/blob/9fc5207500752d12c063e1dc69089ced4a25b140/libfprint/drivers/goodixtls/goodix.c" target="_blank" rel="nofollow noopener noreferrer">https://github.com/barsikus007/libfprint/blob/9fc5207500752d12c063e1dc69089ced4a25b140/libfprint/drivers/goodixtls/goodix.c</a>
</p>
<h2 id="環境">環境</h2>
<p>今回使用した環境は以下。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">PC:
</span></span><span class="line"><span class="cl">ASUS ROG Zephyrus G15 GA503QR
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">OS:
</span></span><span class="line"><span class="cl">Ubuntu 24.04.4 LTS
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Fingerprint sensor:
</span></span><span class="line"><span class="cl">Goodix 27c6:521d
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">fprintd:
</span></span><span class="line"><span class="cl">1.94.3
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Ubuntu libfprint:
</span></span><span class="line"><span class="cl">1.94.7+tod1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">検証したlibfprint:
</span></span><span class="line"><span class="cl">barsikus007/libfprint
</span></span><span class="line"><span class="cl">branch: merge/upstream-1.94.9
</span></span></code></pre></div><p>センサー自体は、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">27c6:521d
</span></span></code></pre></div><p>としてUSB上に存在している。</p>
<p>しかしUbuntu標準のlibfprintから利用すると、サポート対象デバイスとして扱われなかった。</p>
<h2 id="コミュニティ版libfprintを試す">コミュニティ版libfprintを試す</h2>
<p>Goodix 52XD向けドライバを含むlibfprintのforkを見つけたので、まずこれをビルドした。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/barsikus007/libfprint.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> libfprint
</span></span><span class="line"><span class="cl">git checkout merge/upstream-1.94.9
</span></span></code></pre></div><p>Ninjaでビルドし、Ubuntu標準のlibfprintを直接置き換えず、検証用ディレクトリへ配置した。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/usr/local/lib/libfprint-goodix-test/
</span></span></code></pre></div><p><code>fprintd.service</code> には <code>LD_LIBRARY_PATH</code> を設定し、検証版をロードさせた。</p>
<p>これでGoodix 521d自体は認識されるようになった。</p>
<p>しかし、次の問題が出た。</p>
<h2 id="invalid-device-psk">Invalid device PSK</h2>
<p>ドライバがセンサーを認識したものの、初期化途中で、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Invalid device PSK
</span></span></code></pre></div><p>となった。</p>
<p>ここで初めて、単にUSB経由で画像を読み出せばよいデバイスではないことが分かった。</p>
<p>Goodix 521dはホストとセンサーの間で暗号化された通信を行っており、その通信にPSK（Pre-Shared Key）が使われるらしい。</p>
<p>センサー内部のPSKと、ドライバ側が期待するPSKが一致しないと、その先へ進めない。</p>
<p>調査にはGoodix 521d向けの解析ツールを利用した。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">goodix-521d-explanation
</span></span><span class="line"><span class="cl">goodix-fp-dump
</span></span></code></pre></div><p>最初の状態は、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Firmware: GFUSB_GM168SEC_APP_10019
</span></span><span class="line"><span class="cl">Valid PSK: False
</span></span></code></pre></div><p>だった。</p>
<p>そこで一度IAP用ファームウェアへ移行してPSKを書き込み、通常ファームウェアへ戻した。</p>
<p>概略は、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">通常Firmware
</span></span><span class="line"><span class="cl">    ↓
</span></span><span class="line"><span class="cl">IAP Firmware
</span></span><span class="line"><span class="cl">    ↓
</span></span><span class="line"><span class="cl">PSK書き込み
</span></span><span class="line"><span class="cl">    ↓
</span></span><span class="line"><span class="cl">通常Firmware復元
</span></span></code></pre></div><p>という流れになる。</p>
<p>処理後は、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Firmware: GFUSB_GM168SEC_APP_10019
</span></span><span class="line"><span class="cl">Valid PSK: True
</span></span></code></pre></div><p>となった。</p>
<p>ようやくセンサーと正常に通信できるようになった。</p>
<p>「通信できた。これで終わりだろう」</p>
<p>と思ったが、実際にはここからが長かった。</p>
<h2 id="指紋を読み取ったはずなのに画像が真っ白">指紋を読み取ったはずなのに画像が真っ白</h2>
<p>通信が成立すると、今度はセンサーから画像を取得できるようになった。</p>
<p>しかしデバッグ用に画像を書き出してみると、スキャン直後の画像が真っ白だった。</p>
<p></p>
<p>※pgmファイルを取得し、pngファイルに変換し、webp形式にしています。</p>
<p>実際のログでは、画像を正規化した後の白画素率を計算すると、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">指なし:
</span></span><span class="line"><span class="cl">white_ratio ≈ 0.93
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">指紋が正常に写った画像:
</span></span><span class="line"><span class="cl">white_ratio ≈ 0.000〜0.002
</span></span></code></pre></div><p>という極端な差があった。</p>
<p>そこで暫定的に、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">return</span> <span class="n">white_ratio</span> <span class="o">&lt;</span> <span class="mf">0.10</span><span class="p">;</span>
</span></span></code></pre></div><p>として、白画像を指紋画像として採用しないようにした。</p>
<p>後の検証では、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">white_ratio = 0.095
</span></span></code></pre></div><p>程度の中途半端な画像まで通ってしまうケースも見つかったため、現在は、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">return</span> <span class="n">white_ratio</span> <span class="o">&lt;</span> <span class="mf">0.05</span><span class="p">;</span>
</span></span></code></pre></div><p>程度まで厳しくしてもよさそうだと考えている。</p>
<p>これはGoodix公式の判定値ではなく、あくまで自分の521dで観測した値を使った暫定的な判定である。</p>
<h2 id="センサーレジスタへの書き込みがおかしかった">センサーレジスタへの書き込みがおかしかった</h2>
<p>画像取得処理を追っている途中で、センサーレジスタへの書き込みにも問題を見つけた。</p>
<p>元コードには、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">guint16</span> <span class="n">payload</span> <span class="o">=</span> <span class="p">{</span><span class="mh">0x05</span><span class="p">,</span> <span class="mh">0x03</span><span class="p">};</span>
</span></span></code></pre></div><p>という記述があった。</p>
<p>当然ながら <code>guint16</code> はスカラーなので、この初期化は意図した2バイト配列にはならない。</p>
<p>コンパイラも、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">excess elements in scalar initializer
</span></span></code></pre></div><p>相当の警告を出していた。</p>
<p>521dではワイヤ上で、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">05 03
</span></span></code></pre></div><p>を送る必要があったため、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="nf">goodix_send_write_sensor_register</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">dev</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="mh">0x022c</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nf">GUINT16_TO_LE</span><span class="p">(</span><span class="mh">0x0305</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">write_sensor_complete</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">ssm</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></div><p>と修正した。</p>
<p>x86のlittle endian環境では、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0x0305
</span></span><span class="line"><span class="cl">→ wire: 05 03
</span></span></code></pre></div><p>となる。</p>
<p>この修正後、画像取得の挙動が大きく変わった。</p>
<p>最初の数枚は白画像になるものの、その後は実際の指紋画像が得られるようになった。</p>
<h2 id="10フレームをつなぐという前提がおかしい">「10フレームをつなぐ」という前提がおかしい</h2>
<p>元のGoodix 52XDドライバは、1回のスキャンで複数フレームを取得し、それらを移動推定してつなぎ合わせる設計になっていた。</p>
<p>例えば、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define GOODIX52XD_CAP_FRAMES 10
</span></span></span></code></pre></div><p>として10枚取得し、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="nf">fpi_do_movement_estimation</span><span class="p">(...)</span>
</span></span><span class="line"><span class="cl"><span class="nf">fpi_assemble_frames</span><span class="p">(...)</span>
</span></span></code></pre></div><p>で画像を構成している。</p>
<p>最初は自分も、このセンサーが小型のスワイプ式センサーなのだと思っていた。</p>
<p>しかし取得した各フレームを比較すると、ほぼ同じ画像だった。</p>
<p>さらにlibfprint側のmovement estimationが、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">delta_x = -8
</span></span><span class="line"><span class="cl">delta_y = -79
</span></span></code></pre></div><p>や、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">delta_x = +8
</span></span><span class="line"><span class="cl">delta_y = +79
</span></span></code></pre></div><p>のような明らかに異常な値を返し、最終画像が、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">192x791
</span></span></code></pre></div><p>のようなサイズになることもあった。</p>
<p>独自にNumPyで画像間位置合わせを試すと、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">dx = 0
</span></span><span class="line"><span class="cl">dy = 0
</span></span></code></pre></div><p>で、そもそも取得画像がほぼ同一であることが分かった。</p>
<p>Goodix解析用Pythonコードも確認したところ、521dの取得方法は基本的に、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">指を検出
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">1画像取得
</span></span></code></pre></div><p>だった。</p>
<p>libfprint側でもこのデバイスは、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">FP_SCAN_TYPE_PRESS</span>
</span></span></code></pre></div><p>として定義されている。</p>
<p>つまりスワイプ式ではなくpress sensorである。</p>
<p>そこで、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define GOODIX52XD_CAP_FRAMES 1
</span></span></span></code></pre></div><p>へ変更し、</p>
<p><strong>1回指を押すごとに1画像</strong></p>
<p>として扱うよう変更した。</p>
<p>これでかなり構造が単純になった。</p>
<h2 id="64x80では小さすぎる">64x80では小さすぎる</h2>
<p>521dから得られる画像は、</p>
<p>64px x 80px</p>
<p>しかない。</p>
<p>わずか、
5120px
である。</p>
<p>この画像をそのままlibfprintへ渡すと、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Failed to detect minutiae: No minutiae found
</span></span></code></pre></div><p>となることが多かった。</p>
<p>そこで、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="nf">fpi_image_resize</span><span class="p">(</span><span class="n">img</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
</span></span></code></pre></div><p>を使い、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">64x80
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">128x160
</span></span></code></pre></div><p>へ2倍拡大してから指紋特徴点抽出へ渡すことにした。</p>
<p>もちろん、拡大したから新しい指紋情報が増えるわけではない。</p>
<p>ただしNBIS側の特徴点抽出処理に対しては、画像解像度をある程度確保することで処理しやすくなるらしく、<code>No minutiae found</code> は大幅に減った。</p>
<p>これで、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> fprintd-enroll -f right-middle-finger <span class="s2">&#34;</span><span class="nv">$USER</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="go">Enroll result: enroll-stage-passed
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">Enroll result: enroll-completed
</span></span></span></code></pre></div><p>まで到達した。</p>
<p>ついに指紋登録できた。</p>
<p>しかし、また問題があった。</p>
<h2 id="指を一度置いただけで登録が全部終わる">指を一度置いただけで登録が全部終わる</h2>
<p>最初の実装では、一度指を置くだけで、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">enroll-stage-passed
</span></span><span class="line"><span class="cl">enroll-stage-passed
</span></span><span class="line"><span class="cl">enroll-stage-passed
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">enroll-completed
</span></span></code></pre></div><p>と一気に登録が完了してしまった。</p>
<p>明らかにおかしい。</p>
<p>原因を追うと、画像取得直後に、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="nf">fpi_image_device_report_finger_status</span><span class="p">(</span><span class="n">img_dev</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">);</span>
</span></span></code></pre></div><p>としていた。</p>
<p>つまり、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">画像を取得した
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">実際にはまだ指が載っている
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">ドライバ「指が離れました」
</span></span></code></pre></div><p>と報告していた。</p>
<p>その結果、libfprint側は、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">指を置いた
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">画像取得
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">指を離した
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">次の登録ステージへ
</span></span></code></pre></div><p>が完了したと誤認し、同じ指を載せたまま次々に登録していた。</p>
<p>この時点で、以前取得した画像同士の相関が異常に高かった理由も説明できた。</p>
<p>同じ指紋イメージを何度も登録していた可能性が高い。</p>
<h2 id="libfprintのfinger-state-machineを読む">libfprintのfinger state machineを読む</h2>
<p>ここでlibfprint側の画像デバイス状態管理を詳しく読むことにした。</p>
<p>大まかな状態遷移は、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">AWAIT_FINGER_ON
</span></span><span class="line"><span class="cl">       ↓
</span></span><span class="line"><span class="cl">   finger ON
</span></span><span class="line"><span class="cl">       ↓
</span></span><span class="line"><span class="cl">    CAPTURE
</span></span><span class="line"><span class="cl">       ↓
</span></span><span class="line"><span class="cl"> image_captured()
</span></span><span class="line"><span class="cl">       ↓
</span></span><span class="line"><span class="cl">AWAIT_FINGER_OFF
</span></span><span class="line"><span class="cl">       ↓
</span></span><span class="line"><span class="cl">   finger OFF
</span></span><span class="line"><span class="cl">       ↓
</span></span><span class="line"><span class="cl">      IDLE
</span></span></code></pre></div><p>となっている。</p>
<p>さらに登録時には、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">minutiae抽出完了
</span></span><span class="line"><span class="cl">かつ
</span></span><span class="line"><span class="cl">finger OFF
</span></span></code></pre></div><p>の両方が成立して初めて次の <code>AWAIT_FINGER_ON</code>へ進む。</p>
<p>つまりドライバ側がやるべきなのは、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">指を検出した
</span></span><span class="line"><span class="cl">→ report TRUE
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">画像を取得
</span></span><span class="line"><span class="cl">→ image_captured()
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">実際に指が離れた
</span></span><span class="line"><span class="cl">→ report FALSE
</span></span></code></pre></div><p>である。</p>
<p><code>image_captured()</code> の直後に勝手に <code>FALSE</code> を送ってはいけない。</p>
<h2 id="本当に指を離すまで待つ">本当に指を離すまで待つ</h2>
<p>そこでfinger-off検出を追加した。</p>
<p>今回のセンサーでは、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">指あり:
</span></span><span class="line"><span class="cl">white_ratio ≈ 0.000〜0.002
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">指なし:
</span></span><span class="line"><span class="cl">white_ratio ≈ 0.93
</span></span></code></pre></div><p>とかなり明確に分離できている。</p>
<p>そのため画像取得後も同じscan state machineを継続し、100msごとに画像を取得して、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">white_ratio &lt; threshold
</span></span><span class="line"><span class="cl">→ まだ指がある
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">white_ratio &gt;= threshold
</span></span><span class="line"><span class="cl">→ 指が離れた
</span></span></code></pre></div><p>と判定するようにした。</p>
<p>実際のログでは、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">frame white ratio: 0.001
</span></span><span class="line"><span class="cl">finger-off check: finger still present
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">frame white ratio: 0.001
</span></span><span class="line"><span class="cl">finger-off check: finger still present
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">frame white ratio: 0.924
</span></span><span class="line"><span class="cl">finger-off check: finger removed
</span></span></code></pre></div><p>となった。</p>
<p>その後、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">reporting real finger-off
</span></span><span class="line"><span class="cl">Image device reported finger status: off
</span></span></code></pre></div><p>とlibfprintへ通知する。</p>
<p>これでようやく、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">1. 指を置く
</span></span><span class="line"><span class="cl">2. 画像取得
</span></span><span class="line"><span class="cl">3. 指を離す
</span></span><span class="line"><span class="cl">4. 次の登録ステージ
</span></span></code></pre></div><p>という、本来の指紋登録らしい動作になった。</p>
<h2 id="finger-offを直したらfprintdが終了しなくなった">finger-offを直したらfprintdが終了しなくなった</h2>
<p>これで解決したと思ったら、今度は連続verify後に、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">GDBus.Error:net.reactivated.Fprint.Error.AlreadyInUse:
</span></span><span class="line"><span class="cl">Device was already claimed
</span></span></code></pre></div><p>が出るようになった。</p>
<p>さらに、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> systemctl status fprintd.service
</span></span></code></pre></div><p>を見ると、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Active: deactivating (stop-sigterm)
</span></span></code></pre></div><p>のまま止まっていた。</p>
<p><code>systemctl restart fprintd</code> すら完了しない。</p>
<p>原因は、finger-off監視のためにscan state machineを継続するようにした一方で、libfprintからdeactivate要求が来るとGoodix側のTLS shutdownを即座に開始していたことだった。</p>
<p>つまり、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">scan SSM:
</span></span><span class="line"><span class="cl">USB通信中
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">同時に
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">deactivate:
</span></span><span class="line"><span class="cl">TLS shutdown
</span></span></code></pre></div><p>という競合が起きる可能性があった。</p>
<p>そこで現在動いているscan SSMをデバイス構造体で追跡するようにした。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">FpiSsm</span> <span class="o">*</span><span class="n">scan_ssm</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">gboolean</span> <span class="n">deactivating</span><span class="p">;</span>
</span></span></code></pre></div><p>deactivate要求が来た場合は、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">deactivating = TRUE
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">scan SSMが残っている
</span></span><span class="line"><span class="cl">→ TLS shutdownを延期
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">scan SSM完了
</span></span><span class="line"><span class="cl">→ goodix_shutdown_tls()
</span></span><span class="line"><span class="cl">→ deactivate_complete()
</span></span></code></pre></div><p>という順序へ変更した。</p>
<p>また、元コードにはエラー時に、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">fp_err</span><span class="p">(...);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>としている箇所があった。</p>
<p>これだとSSMが完了も失敗もしないまま取り残される可能性がある。</p>
<p>そこで、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">fp_err</span><span class="p">(...);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">fpi_ssm_mark_failed</span><span class="p">(</span><span class="n">user_data</span><span class="p">,</span> <span class="n">error</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>へ変更した。</p>
<p>修正後のログは、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">finger-off check: finger removed
</span></span><span class="line"><span class="cl">finished scan
</span></span><span class="line"><span class="cl">reporting real finger-off
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">AWAIT_FINGER_OFF → IDLE
</span></span><span class="line"><span class="cl">IDLE → DEACTIVATING
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">finishing deferred deactivation
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">DEACTIVATING → INACTIVE
</span></span><span class="line"><span class="cl">released device 0
</span></span></code></pre></div><p>となった。</p>
<p>その後、連続して <code>fprintd-verify</code>を実行しても <code>AlreadyInUse</code>は発生しなくなった。</p>
<p>ここまでで、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Goodix通信            OK
</span></span><span class="line"><span class="cl">PSK                   OK
</span></span><span class="line"><span class="cl">画像取得              OK
</span></span><span class="line"><span class="cl">1押下1画像            OK
</span></span><span class="line"><span class="cl">finger-on             OK
</span></span><span class="line"><span class="cl">finger-off            OK
</span></span><span class="line"><span class="cl">連続verify            OK
</span></span><span class="line"><span class="cl">deactivate/release    OK
</span></span></code></pre></div><p>まで来た。</p>
<h2 id="残る問題は認証精度">残る問題は認証精度</h2>
<p>動作自体はかなり正常になったが、認証精度はまだ低い。</p>
<p>右中指を登録し、右中指で10回verifyしたところ、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">verify-no-match
</span></span><span class="line"><span class="cl">verify-match
</span></span><span class="line"><span class="cl">verify-no-match
</span></span><span class="line"><span class="cl">verify-no-match
</span></span><span class="line"><span class="cl">verify-no-match
</span></span><span class="line"><span class="cl">verify-no-match
</span></span><span class="line"><span class="cl">verify-no-match
</span></span><span class="line"><span class="cl">verify-match
</span></span><span class="line"><span class="cl">verify-no-match
</span></span><span class="line"><span class="cl">verify-match
</span></span></code></pre></div><p>となった。</p>
<p>10回中3回成功。</p>
<p>成功率は30%しかない。</p>
<p>ログを見ると、libfprint内部ではBozorth3のscoreが出ていた。</p>
<p>例えば、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">score 28/24
</span></span><span class="line"><span class="cl">→ verify-match
</span></span></code></pre></div><p>や、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">score 21/24
</span></span><span class="line"><span class="cl">→ verify-no-match
</span></span></code></pre></div><p>となる。</p>
<p>現在のドライバでは、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">img_dev_class</span><span class="o">-&gt;</span><span class="n">bz3_threshold</span> <span class="o">=</span> <span class="mi">24</span><span class="p">;</span>
</span></span></code></pre></div><p>である。</p>
<p>つまりBozorth3のscoreが24以上なら一致と判定する。</p>
<h2 id="nbisとbozorth3">NBISとBozorth3</h2>
<p>ここで初めて、生体認証の内部についてちゃんと調べることになった。</p>
<p>libfprintの指紋照合には、NISTが公開しているNBIS（NIST Biometric Image Software）の技術が使われている。</p>
<p>その中のBozorth3は、画像の画素を直接比較するアルゴリズムではない。</p>
<p>大まかには、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">指紋画像
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">minutiae（特徴点）抽出
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">特徴点の位置・方向
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">Bozorth3
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">特徴点同士の幾何学的関係を比較
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">score
</span></span></code></pre></div><p>という流れになる。</p>
<p>minutiaeとは、例えば指紋の隆線が途切れる端点や、分岐する点などである。</p>
<p>指をセンサーに置く位置は毎回少しずれるので、単純な座標一致ではなく、特徴点同士の距離や方向といった幾何学的関係を比較している。</p>
<p>ログに出ている、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">score 29/24
</span></span></code></pre></div><p>は、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Bozorth3 score = 29
</span></span><span class="line"><span class="cl">threshold = 24
</span></span><span class="line"><span class="cl">29 &gt;= 24
</span></span><span class="line"><span class="cl">→ match
</span></span></code></pre></div><p>という意味になる。</p>
<p>29%一致という意味ではない。</p>
<p>生体認証は今まであまり勉強したことがなかったので、NISTがこういう特徴点抽出や指紋照合アルゴリズムまで実装として公開していることを知って少し感動した。</p>
<p>さすがNIST。</p>
<h2 id="しきい値を下げればいいわけではない">しきい値を下げればいいわけではない</h2>
<p>本人でのverifyでは、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0
</span></span><span class="line"><span class="cl">28
</span></span><span class="line"><span class="cl">16
</span></span><span class="line"><span class="cl">17
</span></span><span class="line"><span class="cl">21
</span></span><span class="line"><span class="cl">19
</span></span><span class="line"><span class="cl">20
</span></span><span class="line"><span class="cl">26
</span></span><span class="line"><span class="cl">16
</span></span><span class="line"><span class="cl">29
</span></span></code></pre></div><p>程度の最大scoreが出ていた。</p>
<p>それなら、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">threshold 24
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">threshold 20
</span></span></code></pre></div><p>などに下げれば認証率が上がるのではないか、と考えたくなる。</p>
<p>しかし別の指で試す必要がある。</p>
<p>登録は右中指のまま、右人差し指で10回verifyした。</p>
<p>各verifyの最大scoreは、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">21
</span></span><span class="line"><span class="cl">7
</span></span><span class="line"><span class="cl">13
</span></span><span class="line"><span class="cl">13
</span></span><span class="line"><span class="cl">13
</span></span><span class="line"><span class="cl">14
</span></span><span class="line"><span class="cl">11
</span></span><span class="line"><span class="cl">7
</span></span><span class="line"><span class="cl">16
</span></span><span class="line"><span class="cl">8
</span></span></code></pre></div><p>だった。</p>
<p>最大21。</p>
<p>したがってthresholdを21まで下げると、今回の実測だけでも別の指がmatchする可能性が出る。</p>
<p>本人と別指のscore分布はかなり重なっている。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">正しい指:
</span></span><span class="line"><span class="cl">0 ── 16 17 19 20 21 ───── 26 28 29
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">別指:
</span></span><span class="line"><span class="cl">7 8 11 13 14 16 ─── 21
</span></span></code></pre></div><p>つまり、単純にBozorth3のthresholdを下げるだけでは解決できない。</p>
<p>問題はもっと前段にありそうだ。</p>
<h2 id="64x80という小ささ">64x80という小ささ</h2>
<p>今回のGoodix 521dの生画像は、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">64x80
</span></span></code></pre></div><p>である。</p>
<p>非常に小さい。</p>
<p>2倍resizeして、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">128x160
</span></span></code></pre></div><p>にしてはいるものの、元の情報量は増えない。</p>
<p>指を1〜2mmずらすだけでも、登録時とverify時に写る領域がかなり変わる可能性がある。</p>
<p>広い指紋画像なら多数のminutiaeを使えるが、64x80程度の小さな画像では特徴点数が少なくなりやすい。</p>
<p>その結果、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">同じ指なのにscore 16
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">別の指なのにscore 21
</span></span></code></pre></div><p>のようなことが起きている可能性がある。</p>
<p>また、現在は画像ごとに、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="nf">squash_frame_linear</span><span class="p">()</span>
</span></span></code></pre></div><p>でmin/maxを0〜255へ正規化している。</p>
<p>この処理によってノイズや固定パターンまで強調され、偽のminutiaeが生成されている可能性も考えられる。</p>
<h2 id="次はnccで画像そのものを比較する">次はNCCで画像そのものを比較する</h2>
<p>そこで次に試そうとしているのがNCC（Normalized Cross-Correlation）である。</p>
<p>Bozorth3が、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">特徴点の幾何学的配置が似ているか
</span></span></code></pre></div><p>を見るのに対し、NCCでは、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">画像パターンそのものが似ているか
</span></span></code></pre></div><p>を見る。</p>
<p>概念的には、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Bozorth3:
</span></span><span class="line"><span class="cl">画像
</span></span><span class="line"><span class="cl">→ minutiae
</span></span><span class="line"><span class="cl">→ 特徴点比較
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">NCC:
</span></span><span class="line"><span class="cl">画像
</span></span><span class="line"><span class="cl">→ 位置を少しずらしながら相関計算
</span></span><span class="line"><span class="cl">→ 画像そのものを比較
</span></span></code></pre></div><p>となる。</p>
<p>以前、一度NCCを試したときには同じ指で、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0.9382
</span></span><span class="line"><span class="cl">0.9531
</span></span><span class="line"><span class="cl">0.3035
</span></span><span class="line"><span class="cl">0.9945
</span></span><span class="line"><span class="cl">0.9998
</span></span></code></pre></div><p>とかなり高い値が出た。</p>
<p>ただし、このときはfinger-off処理にバグがあり、同じラッチ画像を繰り返し比較していた可能性がある。</p>
<p>そのため、この値は信用できない。</p>
<p>finger on/offが正常になった現在の状態で、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">右中指 vs 右中指
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">右中指 vs 右人差し指
</span></span></code></pre></div><p>の画像を独立して大量に取得し、NCC分布を比較する予定である。</p>
<p>もし、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">same finger:
</span></span><span class="line"><span class="cl">0.7〜0.95
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">different finger:
</span></span><span class="line"><span class="cl">0.1〜0.4
</span></span></code></pre></div><p>のように綺麗に分離できれば、521dのような小型press sensorではBozorth3より画像ベース照合の方が適している可能性がある。</p>
<p>逆にNCCでも本人と別指が大きく重なるなら、matcher以前に、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">背景補正
</span></span><span class="line"><span class="cl">固定パターン除去
</span></span><span class="line"><span class="cl">画像正規化
</span></span></code></pre></div><p>を改善する必要がある。</p>
<h2 id="現在の状態">現在の状態</h2>
<p>最初は、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">lsusbでは見える
</span></span><span class="line"><span class="cl">でも指紋センサーとして使えない
</span></span></code></pre></div><p>というところから始まった。</p>
<p>そこから、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">USBデバイス認識
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">PSK不整合修復
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">Goodixとの暗号化通信確立
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">画像取得
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">白画像除外
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">センサーレジスタ書き込み修正
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">10フレーム方式を廃止
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">1押下1画像化
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">64x80 → 128x160
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">minutiae抽出成功
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">finger-on/off状態管理修正
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">deactivateハング修正
</span></span><span class="line"><span class="cl">↓
</span></span><span class="line"><span class="cl">連続verify可能
</span></span></code></pre></div><p>まで進んだ。</p>
<p>最初は「Linuxで指紋センサーを使えるようにしたい」程度の話だったが、気付けば、</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">USBプロトコル
</span></span><span class="line"><span class="cl">TLS/PSK
</span></span><span class="line"><span class="cl">センサー制御
</span></span><span class="line"><span class="cl">画像処理
</span></span><span class="line"><span class="cl">状態機械
</span></span><span class="line"><span class="cl">NBIS
</span></span><span class="line"><span class="cl">minutiae
</span></span><span class="line"><span class="cl">Bozorth3
</span></span></code></pre></div><p>まで追うことになった。</p>
<p>こういう、ハードウェアは認識しているのにソフトウェアスタックのどこか一段が欠けていて動かないものを追いかけるのは面白い。</p>
<p>まだ認証精度は実用レベルではないので、次は画像処理とmatcher側を調べていく予定である。</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
