前言 我们要实现一个人脸识别的功能,人脸识别的都是调用本地的图片,所以我们搭建一个接口服务来提供图片的上传。
接口 一般接口 接口使用FastAPI框架
https://fastapi.tiangolo.com/zh/#_4
注意
该框架需要Python 3.6 及更高版本
环境变量中添加
KEY
VALUE
Path
D:\Tools\Python310 D:\Tools\Python310\Scripts
另外要注意
系统变量的优先级要比用户变量的优先级高,如果配置后还是2.x版本,就要看看是否系统变量中也配置了。
配置完成后要重启开发工具,不用重启电脑。
安装
1 2 pipenv install fastapi pipenv install uvicorn[standard]
创建一个 main.py 文件并写入以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from typing import Union from fastapi import FastAPIapp = FastAPI() @app.get("/" ) def read_root (): return {"Hello" : "World" } @app.get("/items/{item_id}" ) def read_item (item_id: int , q: Union [str , None ] = None ): return {"item_id" : item_id, "q" : q}
通过以下命令运行服务器:
1 pipenv run uvicorn main:app --reload
使用浏览器访问
http://127.0.0.1:8000/items/5?q=somequery
这样我们的接口服务就搭建好了。
对接的接口文档地址
http://127.0.0.1:8000/docs
静态文件 1 2 from fastapi.staticfiles import StaticFilesapp.mount("/web" , StaticFiles(directory="web" ), name="web" )
所有以/web/开头的请求都会访问到web目录中。
允许跨域访问 1 2 3 4 5 6 7 8 9 from starlette.middleware.cors import CORSMiddlewareapp.add_middleware( CORSMiddleware, allow_origins=["*" ], allow_credentials=True , allow_methods=["GET" , "POST" , "PUT" , "DELETE" ], allow_headers=["*" ], )
文件上传 一般文件上传 要用 File,需要先安装这个库
1 pipenv install python-multipart
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import uuidimport uvicornimport osfrom fastapi import FastAPI, File, UploadFileapp = FastAPI() @app.get("/" ) def read_root (): return {"code" : 0 , "msg" : "请求成功" } @app.post("/upfile/" ) async def upfile (file: bytes = File(... ) ): return {"file_size" : len (file)} @app.post("/uploadfile/" ) async def uploadfile (image: UploadFile = File(... ) ): try : if not os.path.exists("images" ): os.makedirs("images" ) except Exception as e: print (e) suffix_arr = image.filename.split("." ) suffix = suffix_arr[len (suffix_arr) - 1 ] file_name = os.getcwd() + "/images/" + str (uuid.uuid1()) + "." + suffix with open (file_name, "wb+" ) as f: f.write(image.file.read()) f.close() return {"filename" : file_name} if __name__ == "__main__" : uvicorn.run(app="main:app" , host="0.0.0.0" , port=8000 , reload=True )
注意
uvicorn.run(app="main:app", host="0.0.0.0", port=8000, reload=True)其中host不要设置为127.0.0.1,否则无法远程访问。
Base64图片上传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import osimport uuidimport base64from fastapi import Body@app.post("/base64file" ) async def uploadfile (image=Body(None ), suffix=Body(None ) ): base64_string = "" if image.startswith('data:image/' ) and ';base64,' in image: header_end = image.index(';base64,' ) + len (';base64,' ) base64_string = image[header_end:] else : base64_string = base64_string imgdata = base64.b64decode(base64_string) file_name = os.getcwd() + "/images/" + str (uuid.uuid1()) + "." + suffix file = open (file_name, 'wb' ) file.write(imgdata) file.close() return {"code" : 0 , "obj" : file_name}
接口返回格式 HTML
1 2 3 4 5 from fastapi.responses import HTMLResponse@app.get('/' , response_class=HTMLResponse ) def read_root (): return "<h1>人像服务</h1>"
纯文本
1 2 3 4 5 from fastapi.responses import PlainTextResponse@app.get('/' , response_class=PlainTextResponse ) def read_root (): return "人像服务"
JSON
1 2 3 @app.get("/" ) def read_root (): return {"code" : 0 , "msg" : "请求成功" }
接口文档服务 文档无法访问 默认的swagger引用的JS和CSS无法访问了。
我们可以引用到项目下本地加载
通过百度网盘分享的文件:static.zip 链接:https://pan.baidu.com/s/1ZSF1K3gApxlRoS6qvK-TkQ?pwd=psvm 提取码:psvm
把文件解压到项目根目录
代码中配置路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from fastapi import FastAPIfrom fastapi import Bodyfrom starlette.staticfiles import StaticFilesfrom fastapi.openapi.docs import get_swagger_ui_htmlapp = FastAPI() app.mount("/static" , StaticFiles(directory="static" ), name="static" ) @app.get("/api_docs" , include_in_schema=False ) async def custom_swagger_ui_html (): return get_swagger_ui_html( openapi_url="/openapi.json" , title="接口文档" , swagger_js_url='/static/swagger/swagger-ui-bundle.js' , swagger_css_url='/static/swagger/swagger-ui.css' , swagger_favicon_url='/static/swagger/img.png' , )
路由要修改一个路由,不要用/docs
禁用文档服务 默认会自动生成文档/docs
删除文档
1 app = FastAPI(docs_url=None )
去掉跳转
1 2 3 4 @app.get("/" ) def root (): response = RedirectResponse(url="/docs" ) return response
错误状态码 1 2 3 4 5 6 7 8 9 from fastapi import FastAPI, HTTPExceptionfrom fastapi.responses import PlainTextResponsefrom starlette.exceptions import HTTPException as StarletteHTTPExceptionapp = FastAPI(docs_url=None ) @app.exception_handler(StarletteHTTPException ) async def http_exception_handler (request, exc ): return PlainTextResponse(str (exc.detail), status_code=exc.status_code)
运行 推荐按如下配置
1 2 3 4 5 6 7 8 @app.on_event("startup" ) def startup_event (): import logging logging.getLogger("uvicorn.error" ).info("Application started at http://localhost:8000" ) if __name__ == "__main__" : uvicorn.run(app="main:app" , host="0.0.0.0" , port=8000 , reload=True )
人像识别 face_recognition https://github.com/ageitgey/face_recognition
安装
1 2 3 pipenv install cmake pipenv install dlib pipenv install face_recognition
使用
1 2 3 import face_recognitionimage = face_recognition.load_image_file("your_file.jpg" ) face_locations = face_recognition.face_locations(image)
deepface https://github.com/serengil/deepface
安装
使用
1 2 3 from deepface import DeepFaceresult = DeepFace.verify(img1_path = "img1.jpg" , img2_path = "img2.jpg" ) print (result)
注意
这个库主要用于对比人脸相似度,人脸的特征等功能。
接口对接人脸识别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import uuidimport uvicornimport osfrom fastapi import FastAPI, File, UploadFilefrom starlette.responses import RedirectResponseimport face_recognitionapp = FastAPI() @app.get("/" ) def root (): response = RedirectResponse(url="/docs" ) return response @app.post("/uploadfile/" ) async def uploadfile (image: UploadFile = File(... ) ): try : if not os.path.exists("images" ): os.makedirs("images" ) except Exception as e: print (e) suffix_arr = image.filename.split("." ) suffix = suffix_arr[len (suffix_arr) - 1 ] file_name = os.getcwd() + "/images/" + str (uuid.uuid1()) + "." + suffix with open (file_name, "wb+" ) as f: f.write(image.file.read()) f.close() image = face_recognition.load_image_file(file_name) face_locations = face_recognition.face_locations(image) if len (face_locations) > 0 : return {"code" : 0 , "obj" : face_locations} else : return {"code" : 1 , "obj" : face_locations} if __name__ == "__main__" : uvicorn.run(app="main:app" , host="0.0.0.0" , port=8000 , reload=True )
WEB获取摄像头做识别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 <!doctype html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > </title > </head > <body > <div class ="imgouter" > <div class ="left_div" > <video id ="v" > </video > </div > <canvas id ="canvas" style ="display:none;" > </canvas > <img id ="photo" alt ="photo" class ="right_div" > </div > <div class ="msg" > </div > </body > <style > body { margin : 0 ; padding : 0 ; } .imgouter { display : flex; height : 90vh ; width : 100vw ; } .left_div , .right_div { width : 0 ; flex : 1 ; } #v { width : 100% ; height : 100% ; object-fit : fill; } .msg { display : flex; align-items : center; justify-content : center; height : 10vh ; font-size : 30px ; } </style > <script src ="https://unpkg.com/axios/dist/axios.min.js" > </script > <script > function upload_img (imgstr ) { let arr = imgstr.split (";base64," ); let suffix = arr[0 ].split ("/" )[1 ]; let base64str = arr[1 ]; let para = { image : base64str, suffix : suffix }; axios .post ('/base64file' , para) .then (function (response ) { let data = response.data ; if (data.code === 0 ) { document .querySelector (".msg" ).innerHTML = "发现人像:" + data.obj .length ; } else { document .querySelector (".msg" ).innerHTML = "未发现人像" ; } }) .catch (function (error ) { console .log (error); }); } !(function ( ) { if (navigator.mediaDevices === undefined ) { navigator.mediaDevices = {}; } if (navigator.mediaDevices .getUserMedia === undefined ) { navigator.mediaDevices .getUserMedia = function (constraints ) { var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia ; if (!getUserMedia) { return Promise .reject (new Error ('getUserMedia is not implemented in this browser' )); } return new Promise (function (resolve, reject ) { getUserMedia.call (navigator, constraints, resolve, reject); }); } } const constraints = { video : true , audio : false }; let videoPlaying = false ; let v = document .getElementById ('v' ); let promise = navigator.mediaDevices .getUserMedia (constraints); promise.then (stream => { if ("srcObject" in v) { v.srcObject = stream; } else { v.src = window .URL .createObjectURL (stream); } v.onloadedmetadata = function (e ) { v.play (); videoPlaying = true ; take_pic (); setInterval (() => { take_pic (); }, 3000 ); }; }).catch (err => { console .error (err.name + ": " + err.message ); }); function take_pic ( ) { if (videoPlaying) { let canvas = document .getElementById ('canvas' ); canvas.width = v.videoWidth ; canvas.height = v.videoHeight ; canvas.getContext ('2d' ).drawImage (v, 0 , 0 ); let data = canvas.toDataURL ("image/jpeg" , 0.8 ); upload_img (data); document .getElementById ('photo' ).setAttribute ('src' , data); } } })(); </script > </html >
web端只能传base64,后端也要处理base64
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import base64import uuidimport osfrom fastapi import Body@app.post("/base64file" ) async def uploadfile (image=Body(None ), suffix=Body(None ) ): imgdata = base64.b64decode(image) file_name = os.getcwd() + "/images/" + str (uuid.uuid1()) + "." + suffix file = open (file_name, 'wb' ) file.write(imgdata) file.close() try : image = face_recognition.load_image_file(file_name) face_locations = face_recognition.face_locations(image) if os.path.exists(file_name): os.remove(file_name) if len (face_locations) > 0 : return {"code" : 0 , "obj" : face_locations} else : return {"code" : 1 , "obj" : face_locations} except Exception as e: return {"code" : 1 , "obj" : [], "msg" : str (e)}
Docker 配置文件Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 FROM python:3.9 .0 LABEL maintainer="psvmc <psvmc@outlook.com>" ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US:enENV LC_ALL=en_US.UTF-8 ENV TZ=Asia/ShanghaiRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN mkdir -p /root/.pip RUN echo "[global]" > /root/.pip/pip.conf && \ echo "index-url = https://mirrors.huaweicloud.com/repository/pypi/simple" >> /root/.pip/pip.conf RUN pip install pipenv RUN pip install cmake RUN mkdir -p /opt/zapp/ COPY .env /opt/zapp/ COPY main.py /opt/zapp/ COPY Pipfile /opt/zapp/ COPY web /opt/zapp/web RUN chmod 755 -R /opt/zapp/* RUN cd /opt/zapp/ && pipenv install --skip-lock EXPOSE 8000 WORKDIR /opt/zapp/ ENTRYPOINT ["pipenv" , "run" , "python" , "main.py" ]
构建
1 docker build -t psvmc/face_recognition:v1.5 .
运行
1 docker run -d -p 8000:8000 --name face_recognition --restart=always psvmc/face_recognition:v1.5
查看启动日志
1 docker logs face_recognition
删除
1 2 docker stop face_recognition docker rm face_recognition
删除镜像
1 docker rmi psvmc/face_recognition:v1.5