From d28b2442586df85e91a748d1b724afc7b467e760 Mon Sep 17 00:00:00 2001 From: Mashiro50070 <1251294066@qq.com> Date: Tue, 29 Jul 2025 14:25:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=B2=E6=9C=89=E8=AE=A4=E8=AF=81=E8=AF=86?= =?UTF-8?q?=E5=88=AB=E5=9F=BA=E6=9C=AC=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=9A=82?= =?UTF-8?q?=E6=9C=AA=E6=94=AF=E6=8C=81=E9=AB=98=E5=B9=B6=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/__init__.py | 0 app/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 149 bytes app/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 153 bytes app/__pycache__/api.cpython-310.pyc | Bin 0 -> 4284 bytes app/__pycache__/database.cpython-310.pyc | Bin 0 -> 1457 bytes app/__pycache__/main.cpython-310.pyc | Bin 0 -> 930 bytes app/__pycache__/main.cpython-312.pyc | Bin 0 -> 1144 bytes app/__pycache__/schemas.cpython-310.pyc | Bin 0 -> 4054 bytes app/__pycache__/services.cpython-310.pyc | Bin 0 -> 6762 bytes app/api.py | 107 +++++++++++ app/database.py | 47 +++++ app/main.py | 22 +++ app/schemas.py | 74 ++++++++ app/services.py | 215 +++++++++++++++++++++++ face_recognition.db | Bin 0 -> 20480 bytes requirements.txt | 8 + 16 files changed, 473 insertions(+) create mode 100644 app/__init__.py create mode 100644 app/__pycache__/__init__.cpython-310.pyc create mode 100644 app/__pycache__/__init__.cpython-312.pyc create mode 100644 app/__pycache__/api.cpython-310.pyc create mode 100644 app/__pycache__/database.cpython-310.pyc create mode 100644 app/__pycache__/main.cpython-310.pyc create mode 100644 app/__pycache__/main.cpython-312.pyc create mode 100644 app/__pycache__/schemas.cpython-310.pyc create mode 100644 app/__pycache__/services.cpython-310.pyc create mode 100644 app/api.py create mode 100644 app/database.py create mode 100644 app/main.py create mode 100644 app/schemas.py create mode 100644 app/services.py create mode 100644 face_recognition.db create mode 100644 requirements.txt diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__pycache__/__init__.cpython-310.pyc b/app/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d84036ca057c7371b6b582fcbb83178effe63bd5 GIT binary patch literal 149 zcmd1j<>g`kf_B4>3=sVoL?8o3AjbiSi&=m~3PUi1CZpdI5oEGUSHj|VC&iI3MSsJz8tlbfGX Qnv-e=GO?HmNU$&f021dSqyPW_ literal 0 HcmV?d00001 diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1bc185cdf4360bd8760db5263b8cfefe185b5921 GIT binary patch literal 153 zcmX@j%ge<81nq_$86f&Gh(HIQS%4zb87dhx8U0o=6fpsLpFwJVS-4uoq^IU)=4B@5 zWX7Z=Ca1<1r6%X6=Vg{;=I6y1rxulE0>#RVViF4qV&db03QOYS^$IF)aoFVMr=0Imv*(;~)B8+>b$fl77lB%}l4iwD0Ze4%yhr&D?6W z`}Xbbd!NsH@6z@4ehHqHqq+0{*(piS@lWmJhCjc6SMw7|q7o%ZDMg|rB}e6yBkCX$ zm8m1?Ou3>i-ghS5DNocxB!%=zQLo}{_fePPYWGt&_2eDY3;nt_nfj=|-`Pi^0WMJo zqxCd^5{iS?_mMWjC4(Rtq&xauAi0A}hCnhzciOskf8gv6~ zvAEKzDe8qpTr8iON^2@j zl_V(3q*5`p%kZempE8Q3$8i_yaX<&1`Y(9395lV8yQxQNm$U%1L<{ILC4J;F(H+xH z7;(Yt?vZ93mx%6Z_x2Df=gDuS(~?ut<*pVYU6P5^?bUtMq5FH}K0=*&`7)udOY+C^ zMFLWFUB9eK`9KOB-6Ky(Q}RZ+i&Dg!xdsRIhv6wME|*4U&D?^xaB(rqR;O#P{XBcw z&m|9so159SPs@F4&CSQcZ2pRnWfRXzgI9}-k8ML07q7!qaq*#0eBwC!+YtNb4K|Wz z17DVVN6SwZ%&B{*7Y~4H&FPuS!y#15hs3b?kF5FWJxhl{@cw1< z&Lr%M*5D@kdUdF{dYk1Y*hC0ym|>CHAbB zjeJtMI?NuAv4MN$&3Q9F$Chtc8-MeCKnGhNsLZUu`r@+*aRWGDMopTzTO6n~rpI8x z17bL_TNwctK9G$%O^@jrtu+paG~86tV~OO+%D-Xqo4ugboXoRpGi)5tJW%PIt&EPB z3x#9hNWgG_Zo_A*HvAvOk{RVaRZXj^2UbRRT7+orP%gHjpdlxy;p~W|6cyu<5js_+ z%&96bRK&dD&~(-C*#wL@RHT97%K+C}6CG`7gD4Lq^?NY*-ygqs?94eOmFP&slZi9@ zG^!F$pX*28(+EV{DE*-f-VnmqX{TY4()Vh(1_$wA;r3Akg0F2tElwi*r>mdwJa z%@$i62y8=C5L;*!HUk!v*J0HiHndb3$d^`b08K0;BZVqsIhI}J6ylgGuB`)qN)wC4 z)m659Ura*;V7a^It;bbjb6sX`1Ws40OYDTYL{9`3miv2oW)==2*ey;6fkBvnU0oJe zVV{f@*JsS$!t-ozaV=k3n}bLMEeygrtZmdf@Ua@eCWy@rXiln_1Jqs6MEvS*>^EYw z2OCa~fWQ-{g(lb#JkBAufeR{vQ#}Apl?BnDSg6qI(pOOcsUVuypvP$pU=2gxLbngm z?SC2F2SEnVeL+zZZCzD#AGOduceh;V-5%Wiuol3*Z+mcKPMVPc;O=(MCV(Hd0qzBW z`?dtQzv~?v;NEWq_^&O1Lmq%LZ^F2Z%qmzuzf)YkZVp{#bGKp2N)g+jy!6F0NDBz` z!V*G;qwcHhWO;oYQXM3AP+DBfLklC2wE*Q`Wv483@T@nvY>pQYgs>ItEO<~FUxC;U z&|gtvN7rXpqGr)r{1N;yIJk)}@Lu>hqNFnr62KmZz z)0OeN!u^J)I<-CAKZYd$_eNcsCID-U+j6^u5(M^sX+olo9&DXcF6y!iFK1V?4RjZE z>uwHo4==pDcH!me`k4)M7uM9(!fS&Z6kc_iKfob)C^n~;pa`(A%bmL+>Wa&i3)KpU z`~9hibfWUHNnyn}QBP$sXo)~tYoD;X55~LQ;kQm4KNik7;ZFpRf)#N}(X`k(r7{W} zd@1^QdAKv`pxlM%)`~{1=Kj-qtb@i>YGZ;2M=Ss~1id$5APUhEms7ER3q+`If!O=- zYN!RWxyw(&BuF-7a+Jy7HOki)u*k~aw2Mq(gAKlyLC^&+FTZ~XonG?7^F2gs!19~Q z+~3wP#SShlsdpfoKPKEt%jbD{7fxTIXTUs}1c(IqODcNwgh zP8^4NJDkBghLOO$tc2i$b>ZYJ_=DS@C!>f(aI Z#4U&91`^uaL_&@5+ZZCD0KC2?@_*PH5ZM3# literal 0 HcmV?d00001 diff --git a/app/__pycache__/database.cpython-310.pyc b/app/__pycache__/database.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c703a2357b1190abc6ee7aaf143bab991ae1b129 GIT binary patch literal 1457 zcmaJ>-*4MQ9KW-j#PwRlK$?UA%`b>qLYu~TK-+{!((8!G9F(N2jVgIg?iLKOU2JFV zqE;$PVTF}#5-`M%q2>Wm9;lFzU~EkNKm58nX`guK1-_RhrYUG=-Phmy-uL^x&wcKD z=T1y02->xiYwx^~K~A6rUWe{yKu|<+16BB6Xkc5Y2pCbZA~JzWG_i`>xFXRc zO|7Czf+eZ^p5Ps2i8cZ+TY z>&}Di-shV;cR$&=d%b)6T5qe1=wX=Qak!SSbFMD79q-)7A<-gys0(L4) z!M+_<3&QcqbUB?#mxwW4%!gvpT?h|(?t%eZ4_xm`n5Z>ep8-=6kN6RJ{G|SRm7Jc7 zX_|WsO>^e_{2Vm{vugV6`23adaGeDPtr~;%fz%WyXdHyFqKGmUu#Arg9yg5eDS(0V znT76O1QDP%x_NY&Glx59886`tf#MFvsILLf15NB}XhY=uUO-fmkc8SIMviKTK|`RPW;+8(fxbfk5~4-UFm(jy4%@oT~*XU z%N9wxOjMvXPt@F$su#*CnaP#PCAA3!5UnS3D=?`PC$s!$}kb2=wNJvPXzsYNnHibT(nm`ICi zYC%^gNuB`X*>owJo+L2?`>1JU26W`~NiuWbR4lq^48cRT1MCc!KchFFg?fl@VO$u| z)_QmC*29&nTB2XJVY1freAf$8Y?0NP0Rw|;+g1=J<~_>^G{GB#MBW4!{5m}nhd>LT zhL%!bus#_-x4h6(>k5V9a+MO0)(QH1Aq$Di@|OeheZ9+RI}u=MvoXVaXB literal 0 HcmV?d00001 diff --git a/app/__pycache__/main.cpython-310.pyc b/app/__pycache__/main.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ad02d830cc27c603b43a91d4344873c532fa171 GIT binary patch literal 930 zcmZWn&1(}u6yMq1?56pswI3Hx6};rq*5XAGQN)6mA_^XsU|2KLxOF$X%*+OxQ)-Kr z+El@+AT74mVyn`4(h!vXAA4;!O;6qnf^Rn1Mx0^Zyf^bcfA6{JbV7pl^!&nud$J_$ z3!!_(K)4Erc|fp)ku5pMKuE$eR&3c(3^n3aJLYJHh9rV;3~N}&@v36zL|>2No(+T( zI5{uPA)|-r(+Q&D6iycq?yV|dp-(6ADcpxoS7qFf2O_-`9=s!Eh7KXBltF<$VKRR0 z*3CoIku~c2oX{N<3|K{rTc&N5Ne-Lbyl*n%4}n&*(P+=t+qEa*!spg%HC)^nzB}$; z6hHM!v;HZZ-3m8%_bapE^V;Fs`oY`zZi{ApxxM?LU7LlV9qawd<8o|#WNc(CqXaQ- zahn85Ojw>;9=F_LpqB_`5yQAix$os6);~)me;p1Jm~&}P0_YHwDf{Q(A#AK1tZaub zYT@E`>&>@cKbFFUweaJ!aOrjH)z?;aH*CC|q@p1^5>3Jtk)sPYE@cbE0T}YOm7Oy4 zBu7c!EeKfLVvZ47vf!;Wo;5u$>zGz?#G45c;;WNp3S2Shy_8UB8m_WBYI;_=ZvtEm z-#&4bkps=6WXgJEsN5{DBQ)ywgMJ4d0cYcTYqQdNx0OML&V0u)>CAr^C}44n0Y+ks zR%O6IoeX66p?_95{PMoNx7_@>2XJ)v6G)!>EYKaom{}kvfOK)9vY>|&6X&Sw z@<9+gh01gQ_{mJ#(5GOqpj;{dFciV5f=d7gMq=OzvEg0^~X|jFZu&G CbR*jU literal 0 HcmV?d00001 diff --git a/app/__pycache__/main.cpython-312.pyc b/app/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93d456219c6c83f52023a2ba064e5936a338f3b2 GIT binary patch literal 1144 zcmZ8gO=uHA6rS1LWZN`pYpVTG4=Rd8rE2wJ5y6A?r&2`0LnxLtGmWd+B+Tv>TQ7+e z8~g`<3ZnRrKPjyzEkW@rc!^Z83>1`h)AVFZJ$dp?wrQ&a^X9#M?|t*$H*Y@&0zL$5 zU}xscDG8xTVU(9=L7D#q${-?$;21J+7-K{vBF7{{4l5;HiFpif*o%>bC?+1_CF)Q` zR(A8lDpk*}D1$DYq%waEQDu!Pq>A{XcqmZD#RwuB%KoL3RFj&a z+AMS<7Fn?j+us`u)y;^{ARua9DY_DNyDXX_GIc}pQojHNAr%5%srNF#VD^L)Qm7aY3YeLS9>TkONwb}%(9aa zxNC8V76r-kjUWb*wLm9YBom`v9Fpvf;C6Dir*FS>ZjLyYUls=6{(67a$vkjgTyd`6 zE!=rixHjzMZgns(M7~1vAkm-Q2Ur)>-0x8SiXz^7=9ngnmn_M-hX=+i*0+8qm(E+Og!7CZnNEb^a zEY<|}O4z3GrqGZ2)%ndwzErj6mG)B09iaeMyJ6^{aV9F{ve3kD#(kgxiWop%QJy)b zE(9fc1<*=+Xxu81%B%HwBxVzM5jRDSQuqG<%d6OHB#0fO``8A+5(8ph05L0Lj2*OQ z0&SW=p`YHsxNrS-EH|@2lFWS6&?D4m4SwIL#8fYza`Z$hPTd17$3zVcblu0sQ3$TPgWHG literal 0 HcmV?d00001 diff --git a/app/__pycache__/schemas.cpython-310.pyc b/app/__pycache__/schemas.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ace73d50d7f0bc03aebf568e568cb1797ef9324d GIT binary patch literal 4054 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 literal 0 HcmV?d00001 diff --git a/app/api.py b/app/api.py new file mode 100644 index 0000000..0f6f8d6 --- /dev/null +++ b/app/api.py @@ -0,0 +1,107 @@ + +from fastapi import APIRouter, HTTPException, Body +from . import schemas +from .services import face_service + +router = APIRouter() + +@router.post("/register", response_model=schemas.RegisterResponse, summary="注册新的人脸") +async def register(request: schemas.RegisterRequest): + """ + 为用户注册一张新的人脸。 + + - **功能**: 将一张图片中的人脸与一个用户ID和姓名关联起来。 + - **校验**: + - 检查用户ID是否已被他人注册。 + - 确保图片中只包含一张清晰可识别的人脸。 + - **操作**: 如果是新用户,会创建用户记录;然后将提取到的人脸特征向量存入数据库。 + """ + try: + if not request.url and not request.face_data: + raise ValueError("必须提供图片的url或face_data。") + + user_info = face_service.register_new_face(request.id, request.name, request) + return schemas.RegisterResponse(data=user_info) + + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}") + + +@router.post("/detect", response_model=schemas.DetectResponse, summary="1:N 人脸识别") +async def detect(request: schemas.ImageSource = Body(...)): + """ + 在一张图片中检测并识别所有已知的人脸。 + + - **功能**: 对比图片中的人脸与数据库中的所有人脸,返回匹配结果。 + - **阈值**: 内部使用相似度阈值来判断是否匹配成功。 + - **返回**: 返回一个列表,包含所有被识别出的人员信息、位置和置信度。 + """ + try: + if not request.url and not request.face_data: + raise ValueError("必须提供图片的url或face_data。") + + detected_faces = face_service.detect_faces(request) + return schemas.DetectResponse(data=detected_faces) + + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}") + + +@router.post("/verify", response_model=schemas.VerifyResponse, summary="1:1 人脸认证") +async def verify(request: schemas.VerifyRequest): + """ + 验证一张图片中的人脸是否属于指定的用户ID。 + + - **功能**: 精确比对,判断“这张脸是不是这个人”。 + - **场景**: 用于人脸登录、刷脸支付等高安全要求的场景。 + - **返回**: 返回布尔值 `match` 表示是否匹配,以及具体的相似度 `confidence`。 + """ + try: + if not request.url and not request.face_data: + raise ValueError("必须提供图片的url或face_data。") + + verification_result = face_service.verify_face(request.id, request) + return schemas.VerifyResponse(data=verification_result) + + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}") + + +@router.delete("/users/{user_id}", response_model=schemas.StandardResponse, summary="删除用户") +async def delete_user(user_id: int): + """ + 根据用户ID,从数据库中删除该用户及其所有注册的人脸信息。 + """ + try: + deleted_user = face_service.delete_user(user_id) + return schemas.StandardResponse(message=f"成功删除用户 {deleted_user['name']} (ID: {user_id})。") + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}") + +@router.get("/users/{user_id}", response_model=schemas.RegisterResponse, summary="获取单个用户信息") +async def get_user(user_id: int): + """ + 根据用户ID,查询并返回用户的详细信息。 + """ + user_info = face_service.get_user(user_id) + if not user_info: + raise HTTPException(status_code=404, detail=f"ID为 {user_id} 的用户不存在。") + return schemas.RegisterResponse(data=user_info) + +@router.get("/users", response_model=schemas.UserListResponse, summary="获取所有用户列表") +async def list_users(skip: int = 0, limit: int = 100): + """ + 获取数据库中所有已注册用户的列表,支持分页。 + """ + users_list = face_service.list_all_users(skip=skip, limit=limit) + return schemas.UserListResponse(data=users_list) + +# 可以在这里继续添加其他API端点... diff --git a/app/database.py b/app/database.py new file mode 100644 index 0000000..393102f --- /dev/null +++ b/app/database.py @@ -0,0 +1,47 @@ + +import sqlite3 +from contextlib import contextmanager + +DATABASE_URL = "E:/geminicli/face_recognition_service_v2/face_recognition.db" + +@contextmanager +def get_db_connection(): + """获取数据库连接,并使用上下文管理器确保连接关闭""" + conn = sqlite3.connect(DATABASE_URL) + conn.row_factory = sqlite3.Row + try: + yield conn + finally: + conn.close() + +def initialize_database(): + """初始化数据库,创建所需的表""" + with get_db_connection() as conn: + cursor = conn.cursor() + + # 创建用户表 + cursor.execute(""" + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL + ); + """) + + # 创建人脸特征表 + cursor.execute(""" + CREATE TABLE IF NOT EXISTS face_features ( + feature_id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + embedding BLOB NOT NULL, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE + ); + """) + + # 创建索引以加速查询 + cursor.execute("CREATE INDEX IF NOT EXISTS idx_user_id ON face_features (user_id);") + + conn.commit() + print("数据库初始化完成。") + +if __name__ == '__main__': + initialize_database() diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..5f9365a --- /dev/null +++ b/app/main.py @@ -0,0 +1,22 @@ + +from fastapi import FastAPI +from .api import router as api_router +from .database import initialize_database + +app = FastAPI( + title="人脸识别服务 V2", + description="一个健壮、可靠的人脸识别与认证API服务。", + version="2.0.0" +) + +@app.on_event("startup") +def on_startup(): + """应用启动时,初始化数据库""" + initialize_database() + +# 挂载API路由 +app.include_router(api_router, prefix="/api", tags=["Face Recognition"]) + +@app.get("/", summary="服务健康检查") +def read_root(): + return {"status": "ok", "message": "欢迎使用人脸识别服务 V2"} diff --git a/app/schemas.py b/app/schemas.py new file mode 100644 index 0000000..7d5f699 --- /dev/null +++ b/app/schemas.py @@ -0,0 +1,74 @@ + +from pydantic import BaseModel, Field +from typing import Optional, List + +# =================================================================== +# 基础模型 (Base Models) +# =================================================================== + +class UserInfo(BaseModel): + """用户的基本信息""" + id: int = Field(..., description="用户的唯一ID", example=1001) + name: str = Field(..., description="用户的姓名", example="张三") + registered_faces_count: int = Field(..., description="该用户已注册的人脸数量", example=2) + +class FaceLocation(BaseModel): + """人脸在图片中的位置和尺寸""" + x: int = Field(..., description="人脸框左上角的X坐标") + y: int = Field(..., description="人脸框左上角的Y坐标") + width: int = Field(..., description="人脸框的宽度") + height: int = Field(..., description="人脸框的高度") + +# =================================================================== +# API 请求模型 (Request Models) +# =================================================================== + +class ImageSource(BaseModel): + """图片来源,可以是URL或Base64编码的数据""" + url: Optional[str] = Field(None, description="图片的URL地址", example="http://example.com/image.jpg") + face_data: Optional[str] = Field(None, description="图片的Base64编码字符串") + +class RegisterRequest(ImageSource): + """注册新用户的请求体""" + id: int = Field(..., description="要注册用户的唯一ID", example=1001) + name: str = Field(..., description="要注册用户的姓名", example="张三") + +class VerifyRequest(ImageSource): + """1:1人脸认证的请求体""" + id: int = Field(..., description="要验证的用户ID", example=1001) + +# =================================================================== +# API 响应模型 (Response Models) +# =================================================================== + +class StandardResponse(BaseModel): + """标准API响应模型""" + code: int = Field(0, description="响应码,0为成功,非0为失败", example=0) + message: str = Field("success", description="响应消息", example="操作成功") + data: Optional[dict] = None + +class UserListResponse(StandardResponse): + """获取用户列表的响应""" + data: List[UserInfo] + +class RegisterResponse(StandardResponse): + """注册成功后的响应""" + data: UserInfo + +class VerificationResult(BaseModel): + """1:1认证结果""" + match: bool = Field(..., description="是否匹配") + confidence: float = Field(..., description="置信度 (0.0 to 1.0)") + +class VerifyResponse(StandardResponse): + """1:1人脸认证的响应""" + data: VerificationResult + +class DetectedFace(UserInfo): + """1:N识别结果中的单个人脸信息""" + location: FaceLocation + confidence: float = Field(..., description="识别的置信度") + +class DetectResponse(StandardResponse): + """1:N人脸识别的响应""" + data: List[DetectedFace] diff --git a/app/services.py b/app/services.py new file mode 100644 index 0000000..07a15e9 --- /dev/null +++ b/app/services.py @@ -0,0 +1,215 @@ + +import cv2 +import numpy as np +from insightface.app import FaceAnalysis +from . import database +import base64 +import requests + +class FaceRecognitionService: + def __init__(self): + # 初始化InsightFace分析器 + self.app = FaceAnalysis(name="buffalo_l", providers=['CPUExecutionProvider']) + self.app.prepare(ctx_id=0, det_size=(640, 640)) + + def _decode_image(self, image_source): + """从URL或Base64解码图片""" + img = None + if image_source.face_data: + try: + img_data = base64.b64decode(image_source.face_data) + nparr = np.frombuffer(img_data, np.uint8) + img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) + except Exception as e: + raise ValueError(f"Base64解码失败: {e}") + elif image_source.url: + try: + response = requests.get(image_source.url, timeout=10) + response.raise_for_status() + img_array = np.asarray(bytearray(response.content), dtype=np.uint8) + img = cv2.imdecode(img_array, cv2.IMREAD_COLOR) + except Exception as e: + raise ValueError(f"从URL获取图片失败: {e}") + + if img is None: + raise ValueError("无法加载图片") + return img + + def register_new_face(self, user_id: int, name: str, image_source): + """注册新的人脸""" + with database.get_db_connection() as conn: + cursor = conn.cursor() + + # 1. 检查ID是否已存在 + cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,)) + user = cursor.fetchone() + + if user and user['name'] != name: + raise ValueError(f"ID {user_id} 已被 '{user['name']}' 注册,无法更改为 '{name}'。") + + # 2. 解码和处理图片 + image = self._decode_image(image_source) + faces = self.app.get(image) + + if not faces: + raise ValueError("图片中未检测到人脸。") + if len(faces) > 1: + raise ValueError("注册图片中只能包含一张人脸。") + + embedding = faces[0].embedding + + # 3. 存储到数据库 + if not user: + # 如果是新用户,先在users表创建记录 + cursor.execute("INSERT INTO users (id, name) VALUES (?, ?)", (user_id, name)) + + # 插入新的人脸特征 + cursor.execute("INSERT INTO face_features (user_id, embedding) VALUES (?, ?)", (user_id, embedding.tobytes())) + + conn.commit() + + # 4. 返回用户信息 + cursor.execute("SELECT COUNT(*) FROM face_features WHERE user_id = ?", (user_id,)) + count = cursor.fetchone()[0] + + return {"id": user_id, "name": name, "registered_faces_count": count} + + + def detect_faces(self, image_source): + """1:N 人脸识别""" + image = self._decode_image(image_source) + detected_faces = self.app.get(image) + + if not detected_faces: + return [] + + with database.get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT user_id, embedding FROM face_features") + db_features = cursor.fetchall() + + if not db_features: + # 如果数据库为空,所有检测到的人脸都是未知的 + # (此处简化处理,实际可返回带位置的未知人脸列表) + return [] + + # 将数据库特征加载到Numpy数组中以便快速计算 + db_user_ids = np.array([f['user_id'] for f in db_features]) + db_embeddings = np.array([np.frombuffer(f['embedding'], dtype=np.float32) for f in db_features]) + + results = [] + for face in detected_faces: + embedding = face.embedding + + # 计算与数据库中所有特征的余弦相似度 + # (insightface的特征是归一化的,点积等价于余弦相似度) + similarities = np.dot(db_embeddings, embedding) + + best_match_index = np.argmax(similarities) + best_similarity = similarities[best_match_index] + + # 设置一个阈值来判断是否为已知人脸 + # ArcFace 官方建议的阈值通常在 0.4 到 0.6 之间,这里我们用0.5 + RECOGNITION_THRESHOLD = 0.5 + + if best_similarity > RECOGNITION_THRESHOLD: + matched_user_id = db_user_ids[best_match_index] + + # 查询用户信息 + cursor.execute("SELECT name, (SELECT COUNT(*) FROM face_features WHERE user_id=?) FROM users WHERE id=?", (matched_user_id, matched_user_id)) + user_info = cursor.fetchone() + + x1, y1, x2, y2 = face.bbox.astype(int) + results.append({ + "id": matched_user_id, + "name": user_info['name'], + "registered_faces_count": user_info[2], + "confidence": float(best_similarity), + "location": {"x": x1, "y": y1, "width": x2 - x1, "height": y2 - y1} + }) + + return results + + + def verify_face(self, user_id: int, image_source): + """1:1 人脸认证""" + # 1. 解码图片并检测人脸 + image = self._decode_image(image_source) + detected_faces = self.app.get(image) + + if not detected_faces: + raise ValueError("图片中未检测到人脸。") + if len(detected_faces) > 1: + raise ValueError("用于认证的图片中只能包含一张人脸。") + + embedding = detected_faces[0].embedding + + # 2. 从数据库获取该ID对应的所有人脸特征 + with database.get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT embedding FROM face_features WHERE user_id = ?", (user_id,)) + db_features = cursor.fetchall() + + if not db_features: + raise ValueError(f"数据库中不存在ID为 {user_id} 的用户,或该用户未注册任何人脸。") + + db_embeddings = np.array([np.frombuffer(f['embedding'], dtype=np.float32) for f in db_features]) + + # 3. 计算与该ID所有特征的相似度,取最高值 + similarities = np.dot(db_embeddings, embedding) + best_similarity = np.max(similarities) + + # 1:1 认证通常使用比 1:N 更严格的阈值 + VERIFICATION_THRESHOLD = 0.6 + + match = bool(best_similarity > VERIFICATION_THRESHOLD) + + return {"match": match, "confidence": float(best_similarity)} + + + def delete_user(self, user_id: int): + """根据ID删除用户及其所有的人脸数据""" + with database.get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT name FROM users WHERE id = ?", (user_id,)) + user = cursor.fetchone() + if not user: + raise ValueError(f"ID为 {user_id} 的用户不存在。") + + # 使用了外键的 ON DELETE CASCADE,删除users表中的记录会自动删除face_features中的相关记录 + cursor.execute("DELETE FROM users WHERE id = ?", (user_id,)) + conn.commit() + + # cursor.rowcount 在 SQLite 中可能不总是可靠,我们确认用户存在即认为删除成功 + return {"id": user_id, "name": user['name']} + + def get_user(self, user_id: int): + """根据ID获取用户信息""" + with database.get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT id, name, (SELECT COUNT(*) FROM face_features WHERE user_id=?) FROM users WHERE id=?", (user_id, user_id)) + user = cursor.fetchone() + if not user: + return None + return {"id": user['id'], "name": user['name'], "registered_faces_count": user[2]} + + def list_all_users(self, skip: int = 0, limit: int = 100): + """列出所有已注册的用户,支持分页""" + with database.get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute(""" + SELECT u.id, u.name, COUNT(f.feature_id) as face_count + FROM users u + LEFT JOIN face_features f ON u.id = f.user_id + GROUP BY u.id, u.name + ORDER BY u.id + LIMIT ? OFFSET ? + """, (limit, skip)) + users = cursor.fetchall() + return [{"id": u['id'], "name": u['name'], "registered_faces_count": u['face_count']} for u in users] + +# 在文件末尾实例化服务,方便API层调用 +face_service = FaceRecognitionService() + + + diff --git a/face_recognition.db b/face_recognition.db new file mode 100644 index 0000000000000000000000000000000000000000..6d4061efa114e302bd1b27098f4f32b2729b34d5 GIT binary patch literal 20480 zcmeI&F;Ck-6u@zt1dx!RT`Dgfp41_+KwbI;>Ldq>FgA6JN~Xx*3$jE~5ho&RTZvD_ zhwIrgR*dM-HT|Dt=T5%&&hK|wPRZz-pDndae=KInQodO4E!(!fiddG_GS{xTURp73 zzYNT$z0v<$w`F~Pau3bZ_TjVj?&;|6=!5Cd5I_I{1Q0*~0R#|0009L4N#J{X&-v7` z^K72#b(XFtt6VQ8SvpN_^<=7(C0;)TxS8&{rcV*2<-urEcLpP`)yI(f3AuZ=mXz za@Y30<2d%uu+%AI%G*vuqsmo>6&P<<->jtA1&P(Qsj{lHPEnrC&7q%$00IagfB*sr zAb