別智慧醫(yī)療預(yù)約掛號(hào)系統(tǒng)全棧開(kāi)發(fā)與部署實(shí)踐)
一個(gè)多月前我?guī)湍掣咝5男值茼?xiàng)目組把一個(gè)“基于人臉識(shí)別的智慧醫(yī)療預(yù)約掛號(hào)平臺(tái)系統(tǒng)”從零到一完整跑通交付的時(shí)候包含源碼、配套論文、部署文檔和分步講解視頻。這套東西看起來(lái)就是典型的畢設(shè)級(jí)全棧項(xiàng)目但真正把它當(dāng)成一個(gè)能跑、能演示、能寫(xiě)進(jìn)論文的完整系統(tǒng)來(lái)做涉及的點(diǎn)遠(yuǎn)不止“接一個(gè)人臉識(shí)別API”那么簡(jiǎn)單。這篇就把我從需求拆解、數(shù)據(jù)庫(kù)設(shè)計(jì)、人臉核驗(yàn)流程、并發(fā)占號(hào)到最后服務(wù)器部署踩過(guò)的坑全部梳理出來(lái)給正準(zhǔn)備做同類(lèi)項(xiàng)目的同學(xué)一個(gè)可以直接參考的路線(xiàn)。這篇內(nèi)容適合三類(lèi)人一是在做人臉識(shí)別/智慧醫(yī)療方向課程設(shè)計(jì)或畢設(shè)的學(xué)生二是想給中小診所、社區(qū)醫(yī)院做輕量化預(yù)約系統(tǒng)的開(kāi)發(fā)人員三是對(duì)全棧項(xiàng)目從編碼到部署完整鏈路感興趣的同行。我會(huì)盡量把每一步為什么這么做、參數(shù)怎么定、坑在哪兒都說(shuō)清楚不會(huì)只丟一個(gè)“能用就行”的糊弄方案。1. 項(xiàng)目核心思路拆解為什么是“人臉識(shí)別預(yù)約掛號(hào)”1.1 傳統(tǒng)預(yù)約掛號(hào)的痛點(diǎn)與我看到的改進(jìn)空間醫(yī)院掛號(hào)這個(gè)場(chǎng)景表面上是個(gè)業(yè)務(wù)流程問(wèn)題本質(zhì)上是個(gè)身份確認(rèn)問(wèn)題。傳統(tǒng)線(xiàn)上預(yù)約無(wú)非是“注冊(cè)賬號(hào)填手機(jī)號(hào)驗(yàn)證碼選號(hào)源”這本沒(méi)什么問(wèn)題但真正落到線(xiàn)下就露餡了患者手機(jī)上掛了號(hào)到院之后還要出示證件、報(bào)手機(jī)號(hào)、核對(duì)姓名窗口和導(dǎo)診臺(tái)的工作量一點(diǎn)沒(méi)降老人不會(huì)用手機(jī)子女幫忙掛號(hào)后老人到院被卡在身份核驗(yàn)環(huán)節(jié)還有號(hào)販子批量注冊(cè)賬號(hào)搶號(hào)再去倒賣(mài)后臺(tái)很難識(shí)別。人臉識(shí)別的價(jià)值恰恰就在這里。把人臉唯一性和預(yù)約單綁定之后患者可以在家完成建檔到院之后在自助機(jī)或者診區(qū)簽到終端前掃一下臉身份核驗(yàn)、預(yù)約確認(rèn)、就診簽到一步到位。對(duì)于不會(huì)操作智能手機(jī)的老人監(jiān)護(hù)人代掛號(hào)后可憑患者本人面部信息直接到院核驗(yàn)體驗(yàn)比翻證件找取號(hào)二維碼順滑得多。1.2 技術(shù)選型為什么優(yōu)先選人臉識(shí)別方案選人臉識(shí)別而不是繼續(xù)用驗(yàn)證碼、短信二次驗(yàn)證核心是解決兩個(gè)矛盾一是“線(xiàn)上確認(rèn)了身份線(xiàn)下沒(méi)人能證明是他本人”二是“號(hào)源有限但搶號(hào)的人不一定是看病的人”。從技術(shù)角度講人臉識(shí)別用在預(yù)約流程里不是要做得像安防系統(tǒng)那么極端我們要求的是“1:1核驗(yàn)”和“1:N檢索”兩種能力。1:1核驗(yàn)用于患者到院時(shí)用現(xiàn)場(chǎng)抓拍的照片跟預(yù)約建檔時(shí)留存的人臉底圖做比對(duì)判斷“來(lái)的人是不是預(yù)約單上這個(gè)人”。1:N檢索用于防止重復(fù)建檔、黑名單攔截比如某身份證號(hào)或某張臉在系統(tǒng)里已經(jīng)存在爽約記錄。方案選型上我建議優(yōu)先選用本地化部署的人臉識(shí)別引擎而不是每次調(diào)用云端接口。原因很現(xiàn)實(shí)醫(yī)院內(nèi)網(wǎng)和公網(wǎng)之間往往有復(fù)雜的網(wǎng)絡(luò)安全邊界患者人臉照片屬于敏感個(gè)人生物信息數(shù)據(jù)出境和數(shù)據(jù)存儲(chǔ)都有合規(guī)壓力本地化部署讓人臉特征向量只在內(nèi)網(wǎng)流轉(zhuǎn)后續(xù)寫(xiě)論文、應(yīng)付答辯也更好解釋安全設(shè)計(jì)。2. 系統(tǒng)整體設(shè)計(jì)與數(shù)據(jù)建模2.1 功能模塊規(guī)劃從預(yù)約到就診的閉環(huán)我設(shè)計(jì)的系統(tǒng)按角色拆成三類(lèi)端患者端小程序/公眾號(hào)H5、醫(yī)生端工作臺(tái)、醫(yī)院管理后臺(tái)?;颊叨说暮诵逆溌肥亲?cè) → 實(shí)名建檔 → 人臉信息采集 → 科室/醫(yī)生選擇 → 號(hào)源鎖定 → 支付/確認(rèn) → 預(yù)約單生成 → 到院刷臉簽到 → 查看候診隊(duì)列。這里“人臉信息采集”不要求用戶(hù)上傳照片而是通過(guò)小程序調(diào)用手機(jī)攝像頭拍攝一段實(shí)時(shí)視頻流后端抽幀做人臉質(zhì)量檢測(cè)質(zhì)量合格后提取特征向量入庫(kù)。這樣能避免“端上照片被翻拍、替身照”的問(wèn)題。醫(yī)生端和管理后臺(tái)相對(duì)常規(guī)醫(yī)生端查看到院簽到順序、叫號(hào)、寫(xiě)診后記錄后臺(tái)管理負(fù)責(zé)科室排班、號(hào)源池配置、黑名單管理、異常預(yù)約處理。2.2 數(shù)據(jù)庫(kù)表設(shè)計(jì)要點(diǎn)數(shù)據(jù)庫(kù)設(shè)計(jì)是整個(gè)系統(tǒng)的地基。我采用MySQL 8.0核心表有六張。每張表都要留出擴(kuò)展字段比如“特征版本號(hào)”因?yàn)槿四樐P蜕?jí)后特征維度可能變化需要區(qū)分新舊特征。用戶(hù)主表uid、手機(jī)號(hào)、密碼摘要、角色標(biāo)識(shí)、狀態(tài)。密碼摘要用BCrypt不用明文。患者檔案表檔案id、uid、真實(shí)姓名、證件類(lèi)型、證件號(hào)、出生日期、緊急聯(lián)系人。證件號(hào)要做加密存儲(chǔ)后端服務(wù)里統(tǒng)一加解密不能直接select出明文。人臉特征表特征id、檔案id、特征向量用JSON或BLOB存、特征版本、采集時(shí)間、活體檢測(cè)得分。特征向量是核心數(shù)據(jù)建議單獨(dú)建表因?yàn)槿四樀讕?kù)的讀取策略和用戶(hù)基本資料不一樣分離存儲(chǔ)便于后續(xù)把特征庫(kù)遷到向量數(shù)據(jù)庫(kù)或獨(dú)立人臉?lè)?wù)節(jié)點(diǎn)。醫(yī)生排班表排班id、醫(yī)生id、科室id、出診日期、午別上午/下午、號(hào)源總量、已占號(hào)量、版本號(hào)。版本號(hào)是樂(lè)觀鎖用的下面講并發(fā)控制時(shí)會(huì)具體說(shuō)。預(yù)約單表預(yù)約單號(hào)、檔案id、排班id、就診狀態(tài)、預(yù)約時(shí)間、簽到時(shí)間、候診序號(hào)、支付狀態(tài)。狀態(tài)機(jī)建議設(shè)計(jì)為待支付 → 已支付/待就診 → 已簽到 → 就診中 → 已完成以及獨(dú)立的已退號(hào)、爽約態(tài)。黑名單表檔案id、觸發(fā)規(guī)則、鎖定截止時(shí)間。爽約和惡意占號(hào)都會(huì)寫(xiě)進(jìn)這里。2.3 人臉識(shí)別模塊的接入思路人臉識(shí)別引擎獨(dú)立部署成一個(gè)Python服務(wù)對(duì)外提供三個(gè)接口人臉質(zhì)量檢測(cè)、人臉特征提取、人臉特征比對(duì)。后端主程序是Java技術(shù)棧的話(huà)通過(guò)HTTP調(diào)用這個(gè)Python服務(wù)即可。技術(shù)棧隔離的好處是人臉模型推理依賴(lài)的OpenCV、PyTorch等庫(kù)不污染Java服務(wù)的環(huán)境后續(xù)單獨(dú)升級(jí)模型也不影響業(yè)務(wù)主程序。質(zhì)量檢測(cè)接口接收?qǐng)D片后返回人臉檢測(cè)框坐標(biāo)、模糊度、遮擋分?jǐn)?shù)、角度。只有角度偏轉(zhuǎn)小于15度、模糊度低于閾值、遮擋面積小于設(shè)定值時(shí)才允許進(jìn)入下一步。特征提取接口輸出512維浮點(diǎn)向量特征比對(duì)接口計(jì)算兩個(gè)向量之間的余弦相似度超過(guò)0.62判定為同一人。這個(gè)閾值是我在測(cè)試集上調(diào)出來(lái)的太嚴(yán)會(huì)導(dǎo)致老人、兒童識(shí)別率低太松則可能誤放行。3. 核心流程實(shí)現(xiàn)預(yù)約、核驗(yàn)、簽到3.1 首次建檔與人臉信息登記流程建檔這個(gè)動(dòng)作最容易做得虎頭蛇尾。很多同學(xué)習(xí)慣讓用戶(hù)填完基本資料就算建檔完成人臉信息另找時(shí)間補(bǔ)錄結(jié)果就是到院刷臉時(shí)發(fā)現(xiàn)底庫(kù)是空的很尷尬。我的設(shè)計(jì)是把人臉采集并進(jìn)建檔步驟里前端先拍證件OCR自動(dòng)識(shí)別姓名和證件號(hào)再通過(guò)活體檢測(cè)拍一段短視頻后端從視頻中抽三幀分別做人臉質(zhì)量檢測(cè)取質(zhì)量分最高的一幀做特征提取。為什么是三幀不是一幀因?yàn)閱螏赡苡龅介]眼、低頭、逆光三幀取質(zhì)量最優(yōu)能顯著降低建檔階段的人臉照片廢片率。活體檢測(cè)這步不要省。如果沒(méi)有動(dòng)作活體檢測(cè)別人拿一張患者照片也能完成建檔。我當(dāng)時(shí)用了一個(gè)極簡(jiǎn)方案屏幕隨機(jī)顯示“點(diǎn)頭/張嘴/左轉(zhuǎn)頭”指令用戶(hù)完成動(dòng)作后采集6到8幀圖像序列送到Python服務(wù)做關(guān)鍵點(diǎn)軌跡分析軌跡符合指令動(dòng)作才視為通過(guò)。這個(gè)方案實(shí)現(xiàn)成本不高但答辯時(shí)被問(wèn)“如何防止照片攻擊”就有得說(shuō)了。3.2 預(yù)約掛號(hào)業(yè)務(wù)流號(hào)源鎖定與支付預(yù)約掛號(hào)最敏感的是號(hào)源并發(fā)問(wèn)題。大家寫(xiě)死“先查號(hào)源再插入預(yù)訂單”并發(fā)一高就會(huì)出超賣(mài)。我們用樂(lè)觀鎖解決醫(yī)生排班表上保留一個(gè)版本號(hào)字段預(yù)約時(shí)執(zhí)行的SQL類(lèi)似這樣UPDATE doctor_schedule SET occupied occupied 1, version version 1 WHERE id ? AND occupied total AND version ?;如果返回影響行數(shù)為0說(shuō)明號(hào)源不足或版本號(hào)已變化直接提示“號(hào)源被搶完或已被占用”。這樣避免引入分布式鎖復(fù)雜度對(duì)畢設(shè)和中小醫(yī)院場(chǎng)景完全夠用。號(hào)源鎖定成功后生成預(yù)約單并開(kāi)啟15分鐘的支付倒計(jì)時(shí)。這里有個(gè)隱藏問(wèn)題用戶(hù)鎖號(hào)后不去支付號(hào)源會(huì)一直被占著。解決辦法是啟動(dòng)一個(gè)定時(shí)任務(wù)每分鐘掃描狀態(tài)為“待支付”且創(chuàng)建時(shí)間超過(guò)15分鐘的預(yù)訂單自動(dòng)釋放號(hào)源。定時(shí)任務(wù)用Spring的Scheduled即可固定延遲2分鐘避免整點(diǎn)批處理打崩數(shù)據(jù)庫(kù)。3.3 到院簽到與就診核驗(yàn)人臉比對(duì)的實(shí)時(shí)交互到院簽到是整個(gè)系統(tǒng)的高光時(shí)刻。我用兩套終端自助簽到大屏和診區(qū)門(mén)口的小型簽到平板?;颊唿c(diǎn)擊屏幕上的“刷臉簽到”攝像頭抓拍一張照片后端執(zhí)行三步第一步人臉檢測(cè)與質(zhì)量過(guò)濾模糊、逆光的照片直接提示重新拍攝避免無(wú)效比對(duì)浪費(fèi)算力。第二步取當(dāng)前科室當(dāng)前時(shí)段的所有預(yù)約單關(guān)聯(lián)人臉特征底庫(kù)做1:N檢索因?yàn)殚T(mén)診單位時(shí)間內(nèi)到場(chǎng)人數(shù)有限可以把檢索范圍控制在“未來(lái)2小時(shí)預(yù)約當(dāng)前科室患者”這個(gè)集合內(nèi)比對(duì)速度會(huì)非???。第三步比對(duì)命中后更新預(yù)約單狀態(tài)為已簽到生成候診序號(hào)同時(shí)推送給醫(yī)生端叫號(hào)大屏。這里有個(gè)細(xì)節(jié)簽到不能只做一次人臉比對(duì)就算完還應(yīng)該校驗(yàn)預(yù)約時(shí)間。我設(shè)定患者只能在預(yù)約就診時(shí)間段前30分鐘和后60分鐘內(nèi)完成簽到提前太多提示“尚未到簽到時(shí)段”遲到超過(guò)1小時(shí)自動(dòng)標(biāo)記為遲到由分診臺(tái)人工決定是否順延。這個(gè)規(guī)則一開(kāi)始沒(méi)做結(jié)果有人凌晨三點(diǎn)就到醫(yī)院刷臉把值班系統(tǒng)搞出了奇怪的提前簽到數(shù)據(jù)。3.4 后臺(tái)管理號(hào)源池、退號(hào)與爽約處理后臺(tái)管理模塊容易被忽略但恰恰是寫(xiě)論文時(shí)最能體現(xiàn)“系統(tǒng)完整性”的部分。號(hào)源池配置支持按排班批量生成一周的號(hào)源每日號(hào)源量 單人次平均就診時(shí)長(zhǎng) / 出診總時(shí)長(zhǎng)比如一個(gè)上午門(mén)診4小時(shí)均次就診8分鐘那號(hào)源量約30個(gè)。這個(gè)計(jì)算邏輯要能配置因?yàn)椴煌剖揖螘r(shí)長(zhǎng)差異很大皮膚科和心內(nèi)科完全不是一個(gè)量級(jí)。退號(hào)流程我設(shè)置了兩個(gè)關(guān)鍵邊界就診前24小時(shí)以上可在線(xiàn)自助退號(hào)金額原路返回24小時(shí)內(nèi)退號(hào)需管理員審核防止號(hào)販子臨診前大量釋放號(hào)源擾亂排班。爽約規(guī)則是“30天內(nèi)爽約2次凍結(jié)線(xiàn)上預(yù)約權(quán)限7天”這個(gè)規(guī)則能有效壓制測(cè)試階段發(fā)現(xiàn)的人力刷號(hào)問(wèn)題。4. 部署與上線(xiàn)注意點(diǎn)4.1 本地開(kāi)發(fā)環(huán)境的快速搭建本地跑通前后端協(xié)同開(kāi)發(fā)我建議用Docker Compose一鍵起MySQL、Redis、Java后端、Python人臉?lè)?wù)、前端Nginx靜態(tài)資源。version: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: smart_clinic ports: - 3306:3306 volumes: - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql redis: image: redis:7-alpine ports: - 6379:6379 face-engine: build: ./face-engine ports: - 8000:8000 backend: build: ./backend ports: - 8080:8080 depends_on: - mysql - redis web: image: nginx:alpine ports: - 80:80人臉?lè)?wù)這個(gè)鏡像會(huì)比較大因?yàn)橐bPyTorch和OpenCV。我第一次build直接卡了十幾分鐘后來(lái)改成在Dockerfile里固定國(guó)內(nèi)鏡像源、分階段安裝依賴(lài)才把構(gòu)建時(shí)間壓到5分鐘以?xún)?nèi)。本地調(diào)試時(shí)建議直接用虛擬環(huán)境跑Python服務(wù)不要每次改代碼都走完整build流程。4.2 服務(wù)器部署前后端分離與模型加載服務(wù)器部署我選擇一臺(tái)4核8G的云主機(jī)操作系統(tǒng)Ubuntu 22.04。分層策略是最外層Nginx托管前端Vue打包產(chǎn)物反向代理/api路徑到Java服務(wù)端口8080反向代理/face路徑到Python人臉?lè)?wù)端口8000。這樣前端只需要認(rèn)識(shí)一個(gè)域名不需要感知后端服務(wù)拆分。人臉模型加載有個(gè)大坑PyTorch加載預(yù)訓(xùn)練模型默認(rèn)需要較多內(nèi)存4G內(nèi)存的服務(wù)器上跑起來(lái)非常吃力。我的處理方式是在Python服務(wù)啟動(dòng)時(shí)延遲加載模型首次請(qǐng)求觸發(fā)加載加載完成后駐留顯存/內(nèi)存。服務(wù)啟動(dòng)后體檢一下人臉接口延遲單次特征提取應(yīng)控制在150毫秒以?xún)?nèi)比對(duì)控制在30毫秒以?xún)?nèi)。如果你實(shí)測(cè)明顯高于這個(gè)值先看CPU是否滿(mǎn)核跑再看有沒(méi)有把圖片base64編碼后傳圖片壓縮到720px寬度能大幅降低網(wǎng)絡(luò)傳輸和推理耗時(shí)。4.3 性能與安全方面的取舍人臉圖片這種生物數(shù)據(jù)我做了兩層保護(hù)接口層必須攜帶一次性臨時(shí)憑證憑證有效期為10分鐘防止截圖后離線(xiàn)重放存儲(chǔ)層對(duì)人臉原始圖單獨(dú)加密保存業(yè)務(wù)庫(kù)只存特征向量。特征向量本身是數(shù)學(xué)量無(wú)法直接還原成照片泄露風(fēng)險(xiǎn)相對(duì)可控。數(shù)據(jù)庫(kù)方面連接池建議設(shè)置最小5、最大20超出等待時(shí)間設(shè)置為3秒。這是由于人臉比對(duì)接口慢時(shí)Java服務(wù)的數(shù)據(jù)庫(kù)查詢(xún)會(huì)排隊(duì)堆積連接池過(guò)大會(huì)把MySQL線(xiàn)程數(shù)打滿(mǎn)我實(shí)際測(cè)試20個(gè)連接足夠支撐門(mén)診高峰的并發(fā)。5. 常見(jiàn)問(wèn)題排查與避坑實(shí)錄5.1 刷臉失敗率高先查這三個(gè)環(huán)節(jié)刷臉失敗一般不是模型不夠好而是現(xiàn)場(chǎng)環(huán)境影響。首先查攝像頭采集分辨率很多人臉?lè)?wù)默認(rèn)丟入模型前會(huì)壓縮圖片如果前端采集幀本身只有480p人臉關(guān)鍵點(diǎn)會(huì)缺失識(shí)別率驟降。我的經(jīng)驗(yàn)是前端采集至少1280x720抓拍后將人臉區(qū)域裁剪放大后再送后端。其次是光線(xiàn)醫(yī)院診區(qū)燈光經(jīng)常偏冷偏暗。我在自助機(jī)屏幕上方加了一個(gè)補(bǔ)光燈板實(shí)測(cè)失敗率從12%降到3%以下。最后是用戶(hù)姿勢(shì)簽到平板上我畫(huà)了“人臉?lè)趴騼?nèi)、平視攝像頭”的引導(dǎo)動(dòng)畫(huà)這一步非常有效。測(cè)試環(huán)境很多失敗是下巴過(guò)度抬起或側(cè)臉過(guò)大導(dǎo)致質(zhì)量檢測(cè)接口應(yīng)該直接攔截并提示用戶(hù)調(diào)整而不是把低質(zhì)量照片硬送比對(duì)。5.2 號(hào)源超賣(mài)問(wèn)題別用查再插寫(xiě)業(yè)務(wù)邏輯剛開(kāi)始最典型的問(wèn)題是先select當(dāng)前已占號(hào)量判斷小于總量然后insert預(yù)訂單。兩個(gè)請(qǐng)求同時(shí)查到已占號(hào)量一樣就會(huì)雙雙插入成功。我改成前面那個(gè)樂(lè)觀鎖更新后測(cè)試腳本用200個(gè)并發(fā)搶30個(gè)號(hào)源最終訂單數(shù)正好30無(wú)一超賣(mài)。補(bǔ)充一個(gè)排查技巧如果發(fā)現(xiàn)更新語(yǔ)句影響行數(shù)總是0檢查一下排班id是否誤用了另一個(gè)字段作為條件。5.3 接口響應(yīng)慢定位瓶頸要分層看一次完整的預(yù)約請(qǐng)求鏈路里包含前端→Java→MySQL/Redis→Python人臉?lè)?wù)鏈路長(zhǎng)哪個(gè)環(huán)節(jié)慢了都會(huì)拖垮體驗(yàn)。我推薦在Java服務(wù)里加一個(gè)簡(jiǎn)單的耗時(shí)切面把每次外部調(diào)用時(shí)間打出來(lái)。快速判斷依據(jù)是若人臉特征提取接口耗時(shí)長(zhǎng)優(yōu)先在Python服務(wù)里開(kāi)多線(xiàn)程/多進(jìn)程來(lái)處理并發(fā)請(qǐng)求多時(shí)單線(xiàn)程服務(wù)會(huì)排隊(duì)若MySQL查詢(xún)慢優(yōu)先檢查預(yù)約單表有沒(méi)有對(duì)“檔案id預(yù)約日期”建聯(lián)合索引。我碰到過(guò)一次詭異的現(xiàn)象并發(fā)一高Java接口整體卡頓MySQL的CPU不高Redis卻撐了一下就崩。排查下來(lái)是登錄態(tài)session直接存Rediskey設(shè)置不規(guī)范一個(gè)用戶(hù)反復(fù)登錄產(chǎn)生大量過(guò)期key加上懶刪除策略的隨機(jī)抽查導(dǎo)致高并發(fā)時(shí)阻塞。把session過(guò)期時(shí)間統(tǒng)一為2小時(shí)并且改成主動(dòng)過(guò)期刪除后解決。5.4 照片翻拍與底庫(kù)質(zhì)量不匹配注意模型版本一致性人臉特征比對(duì)的鐵律是特征提取和特征比對(duì)必須使用同一版本模型。建檔時(shí)用模型A提取的特征向量線(xiàn)下簽到用模型B做比對(duì)即使兩個(gè)模型精度都很高跨模型的余弦相似度也會(huì)整體偏低。我經(jīng)歷過(guò)一次升級(jí)模型后老用戶(hù)全部被拒的嚴(yán)重事故后來(lái)方案是特征向量中都帶feature_version字段比對(duì)時(shí)如果發(fā)現(xiàn)版本不一致先拒絕并轉(zhuǎn)人工核驗(yàn)而不是用新模型去硬比舊向量。另一個(gè)容易被忽視的坑是證件照底圖與生活照的差異。某個(gè)患者身份證照是十年前拍的現(xiàn)場(chǎng)人臉識(shí)別怎么都比不過(guò)閾值。后來(lái)我在建檔時(shí)增加“可另外錄入一張現(xiàn)場(chǎng)真實(shí)照片”的選項(xiàng)以現(xiàn)場(chǎng)照為主特征、證件照為輔助特征兩條向量都存下來(lái)比對(duì)時(shí)取最高分。這算是一個(gè)低成本高收益的改進(jìn)。6. 從項(xiàng)目到產(chǎn)品的延展思考6.1 單院區(qū)版本邁向多院區(qū)版本的數(shù)據(jù)模型變化如果這套系統(tǒng)要從單體醫(yī)院擴(kuò)展到醫(yī)聯(lián)體第一個(gè)要改的是身份主索引。不同醫(yī)院之間患者檔案需要統(tǒng)一但人臉特征向量未必能跨院共享因?yàn)楦髟簠^(qū)的采集設(shè)備型號(hào)、光線(xiàn)條件、模型版本都可能不一致。穩(wěn)妥做法是建立“全局患者ID 院區(qū)特征分庫(kù)”的結(jié)構(gòu)預(yù)約單要帶上所屬院區(qū)字段簽到終端只能檢索當(dāng)前院區(qū)特征庫(kù)。這里還有一個(gè)業(yè)務(wù)規(guī)則問(wèn)題跨院區(qū)預(yù)約需要共享號(hào)源池嗎我的建議是初期各院區(qū)獨(dú)立號(hào)源池后臺(tái)管理端做匯總視圖即可。否則一個(gè)患者在同一時(shí)間段內(nèi)被兩個(gè)院區(qū)誤傷鎖號(hào)容易引發(fā)醫(yī)院內(nèi)部的號(hào)源分配糾紛。6.2 人臉?lè)?wù)的高可用演進(jìn)單節(jié)點(diǎn)的人臉?lè)?wù)在實(shí)際門(mén)診場(chǎng)景下風(fēng)險(xiǎn)不小。有一次我用Gunicorn默認(rèn)worker跑Python服務(wù)一個(gè)模型的推理請(qǐng)求阻塞后續(xù)請(qǐng)求全部排隊(duì)門(mén)診高峰期簽到平板直接轉(zhuǎn)圈。至少應(yīng)該做到人臉?lè)?wù)部署雙副本Nginx做輪詢(xún)負(fù)載均衡比對(duì)接口超時(shí)設(shè)為2秒超時(shí)后立即降級(jí)為“人工核驗(yàn)通道”而不是讓患者在終端前干等。將來(lái)有條件的話(huà)可以把特征向量從MySQL搬到向量數(shù)據(jù)庫(kù)1:N檢索性能能提升一個(gè)量級(jí)。但對(duì)于預(yù)約掛號(hào)場(chǎng)景患者集合是可控的提前按科室和時(shí)段圈定檢索范圍比換數(shù)據(jù)庫(kù)更加立竿見(jiàn)影。6.3 演示與講解視頻的制作心得最后分享一個(gè)交付層面的經(jīng)驗(yàn)。源碼之外的那份講解視頻一定不要照著部署文檔念而是講三個(gè)故事第一個(gè)故事是“患者從家到簽到臺(tái)全程無(wú)感通行”把業(yè)務(wù)流串起來(lái)第二個(gè)故事是“200個(gè)并發(fā)搶30個(gè)號(hào)源不超賣(mài)”把技術(shù)難點(diǎn)亮出來(lái)第三個(gè)故事是“模型升級(jí)導(dǎo)致老用戶(hù)全被拒”把工程教訓(xùn)說(shuō)出來(lái)。面試官和答辯老師看到這三個(gè)場(chǎng)景就知道你不是在堆功能而是在認(rèn)真解決過(guò)實(shí)際問(wèn)題。另外部署文檔最好按“全新虛擬機(jī)從零開(kāi)始”的視角寫(xiě)裝什么依賴(lài)、改哪些配置、起什么服務(wù)、看什么日志確認(rèn)成功每一行命令都給出預(yù)期輸出。我當(dāng)年踩過(guò)的最冤枉的坑就是文檔里漏寫(xiě)了人臉?lè)?wù)需要先安裝特定版本的libgl1導(dǎo)致評(píng)審老師換了個(gè)機(jī)器部署時(shí)一連串環(huán)境報(bào)錯(cuò)。這套系統(tǒng)真要做扎實(shí)工程量不小但它覆蓋了一個(gè)全棧項(xiàng)目幾乎所有的核心訓(xùn)練點(diǎn)業(yè)務(wù)建模、并發(fā)控制、生物識(shí)別接入、前后端分離部署、異常降級(jí)設(shè)計(jì)。做完這一套你手里就不只是“一個(gè)畢設(shè)”而是可以大大方方寫(xiě)進(jìn)簡(jiǎn)歷的“一整個(gè)項(xiàng)目”。如果時(shí)間允許建議你把黑名單規(guī)則、號(hào)源池配置、日志審計(jì)再補(bǔ)細(xì)一點(diǎn)這套東西的延展空間還很大。