├── .gitignore ├── Makefile ├── README.md ├── face.jpg ├── face_detection.jpg ├── go.mod ├── libfaced ├── faced.cpp ├── faced.h └── faced_cmd.cpp └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | /libfaced/faced 2 | /libfaced/libfaced.so 3 | /SeetaFaceEngine 4 | /server 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | src_dir=$(shell pwd) 2 | seeta_commit_id=0f73c0964cf229d16fe584db14c08c61b1d84105 3 | seeta_fd_src_dir=$(src_dir)/SeetaFaceEngine/FaceDetection 4 | cmake=cmake 5 | 6 | seeta/clone: 7 | cd $(src_dir) && git clone https://github.com/seetaface/SeetaFaceEngine.git 8 | cd $(src_dir)/SeetaFaceEngine && git checkout $(seeta_commit_id) 9 | 10 | seeta/build: 11 | rm -rf $(seeta_fd_src_dir)/build 12 | mkdir $(seeta_fd_src_dir)/build 13 | cd $(seeta_fd_src_dir)/build && $(cmake) .. && make -j${nproc} 14 | 15 | seeta: seeta/clone seeta/build 16 | 17 | faced: 18 | cd libfaced && g++ -std=c++11 faced.cpp -fPIC -shared -o libfaced.so `pkg-config opencv4 --cflags --libs` \ 19 | -I$(seeta_fd_src_dir)/include/ \ 20 | -L$(seeta_fd_src_dir)/build \ 21 | -lseeta_facedet_lib -ljsoncpp 22 | cd libfaced && g++ -std=c++11 faced_cmd.cpp -o faced \ 23 | -I$(seeta_fd_src_dir)/include \ 24 | -L$(seeta_fd_src_dir)/build -L. \ 25 | -lfaced -lseeta_facedet_lib 26 | LD_LIBRARY_PATH=$(src_dir)/libfaced:$(seeta_fd_src_dir)/build libfaced/faced face.jpg 27 | 28 | goserver: 29 | go build server.go 30 | LD_LIBRARY_PATH=$(src_dir)/libfaced:$(seeta_fd_src_dir)/build ./server 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FaceDetectionServer 2 | 3 | 基于 [SeetaFace](https://github.com/seetaface/SeetaFaceEngine) 的高性能人脸识别服务, 使用 Golang 与 CPP 混合开发. 该项目演示了使用 Cgo 与 CPP 代码进行复杂交互的一种方式. 4 | 5 | ![img](./face_detection.jpg) 6 | 7 | # Requirements 8 | 9 | ```sh 10 | $ apt install libopencv-dev libjsoncpp-dev 11 | ``` 12 | 13 | # Build and Usage 14 | 15 | ```sh 16 | $ make seeta # 下载 SeetaFace 源码到 /src, 切换到指定版本并进行编译, 该过程需要 cmake3 支持. 17 | $ make faced # 编译胶水部分 c++ 代码, 提供可供 golang 使用的 c 语法 lib. 18 | $ make goserver # 混合编译 golang/c++ 服务到单独二进制文件并启动服务 19 | ``` 20 | 21 | ```sh 22 | $ http POST :8080/image/bin/detection < ./face.jpg # 对服务进行命令行测试 23 | ``` 24 | 25 | ```json 26 | HTTP/1.1 200 OK 27 | Content-Length: 70 28 | Content-Type: application/json 29 | Date: Wed, 12 Oct 2016 02:47:09 GMT 30 | 31 | { 32 | "face": [ 33 | { 34 | "Y": 167, 35 | "height": 287, 36 | "width": 287, 37 | "x": 103 38 | } 39 | ], 40 | "size": [ 41 | 500, 42 | 650 43 | ] 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /face.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohanson/face-detection-server/9c58fccf66f98d4367e7f5e45caabecdeb51b9ab/face.jpg -------------------------------------------------------------------------------- /face_detection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohanson/face-detection-server/9c58fccf66f98d4367e7f5e45caabecdeb51b9ab/face_detection.jpg -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mohanson/face-detection-server 2 | 3 | go 1.20 4 | -------------------------------------------------------------------------------- /libfaced/faced.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "opencv2/highgui/highgui.hpp" 7 | #include "opencv2/imgproc/imgproc.hpp" 8 | #include "jsoncpp/json/json.h" 9 | #include "face_detection.h" 10 | 11 | #include "faced.h" 12 | 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | 18 | seeta::FaceDetection detector("SeetaFaceEngine/FaceDetection/model/seeta_fd_frontal_v1.0.bin"); 19 | 20 | const char* FaceDetect(char* path) { 21 | detector.SetMinFaceSize(40); 22 | detector.SetScoreThresh(2.f); 23 | detector.SetImagePyramidScaleFactor(0.8f); 24 | detector.SetWindowStep(4, 4); 25 | 26 | cv::Mat img = cv::imread(path, cv::IMREAD_UNCHANGED); 27 | cv::Mat img_gray; 28 | 29 | if (img.channels() != 1) 30 | cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY); 31 | else 32 | img_gray = img; 33 | 34 | seeta::ImageData img_data; 35 | img_data.data = img_gray.data; 36 | img_data.width = img_gray.cols; 37 | img_data.height = img_gray.rows; 38 | img_data.num_channels = 1; 39 | 40 | std::vector faces = detector.Detect(img_data); 41 | 42 | Json::Value root; 43 | root["size"].append(Json::Value(img_data.width)); 44 | root["size"].append(Json::Value(img_data.height)); 45 | 46 | int32_t num_face = static_cast(faces.size()); 47 | 48 | for (int32_t i = 0; i < num_face; i++) { 49 | Json::Value innerResp; 50 | innerResp["x"] = Json::Value(faces[i].bbox.x); 51 | innerResp["y"] = Json::Value(faces[i].bbox.y); 52 | innerResp["width"] = Json::Value(faces[i].bbox.width); 53 | innerResp["height"] = Json::Value(faces[i].bbox.height); 54 | root["face"].append(Json::Value(innerResp)); 55 | } 56 | 57 | char *out = new char[root.toStyledString().length() + 1]; 58 | std::strcpy(out, root.toStyledString().c_str()); 59 | return (const char*)out; 60 | } 61 | -------------------------------------------------------------------------------- /libfaced/faced.h: -------------------------------------------------------------------------------- 1 | #ifdef __cplusplus 2 | extern "C" { 3 | #endif 4 | const char* FaceDetect(char* path); 5 | #ifdef __cplusplus 6 | } 7 | #endif 8 | -------------------------------------------------------------------------------- /libfaced/faced_cmd.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "faced.h" 3 | 4 | using namespace std; 5 | 6 | int main(int argc, char** argv) { 7 | cout << FaceDetect(argv[1]) << endl; 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | #cgo CFLAGS: -Ilibfaced -ISeetaFaceEngine/FaceDetection/include 5 | #cgo LDFLAGS: -Llibfaced -LSeetaFaceEngine/FaceDetection/build -lfaced -lseeta_facedet_lib 6 | #include "faced.h" 7 | #include 8 | */ 9 | import "C" 10 | import ( 11 | "encoding/json" 12 | "fmt" 13 | "io" 14 | "log" 15 | "net/http" 16 | "os" 17 | "sync" 18 | "unsafe" 19 | ) 20 | 21 | const ( 22 | Version = "0.0.2" 23 | Port = "8080" 24 | ) 25 | 26 | type DetectionResultFace struct { 27 | X int `json:"x"` 28 | Y int `josn:"y"` 29 | Width int `json:"width"` 30 | Height int `json:"height"` 31 | } 32 | 33 | type DetectionResult struct { 34 | Size []int `json:"size"` 35 | Face []DetectionResultFace `json:"face"` 36 | } 37 | 38 | var mutex sync.Mutex 39 | 40 | func FaceDetect(path string) *DetectionResult { 41 | mutex.Lock() 42 | defer mutex.Unlock() 43 | 44 | cpath := C.CString(path) 45 | defer C.free(unsafe.Pointer(cpath)) 46 | 47 | cresp := C.FaceDetect(cpath) 48 | defer C.free(unsafe.Pointer(cresp)) 49 | 50 | resp := C.GoString(cresp) 51 | 52 | dr := &DetectionResult{} 53 | json.Unmarshal([]byte(resp), dr) 54 | 55 | return dr 56 | } 57 | 58 | // ============================================================================ 59 | // HTTP Server 60 | // ============================================================================ 61 | func HandlerRoot(w http.ResponseWriter, r *http.Request) { 62 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 63 | w.WriteHeader(200) 64 | w.Write([]byte(fmt.Sprintf("

200 Service Available

"+ 65 | "
"+ 66 | "
FaceDetectServer/%s
", Version))) 67 | } 68 | 69 | func HandlerImageBinDetection(w http.ResponseWriter, r *http.Request) { 70 | if r.Method != "POST" && r.Method != "PUT" { 71 | http.Error(w, "", http.StatusMethodNotAllowed) 72 | return 73 | } 74 | defer r.Body.Close() 75 | 76 | f, err := os.CreateTemp("", "fd_") 77 | if err != nil { 78 | http.Error(w, err.Error(), 500) 79 | return 80 | } 81 | defer func() { 82 | f.Close() 83 | os.Remove(f.Name()) 84 | }() 85 | 86 | if _, err := io.Copy(f, r.Body); err != nil { 87 | http.Error(w, err.Error(), 500) 88 | return 89 | } 90 | f.Close() 91 | 92 | fileinfo, err := os.Stat(f.Name()) 93 | if err != nil { 94 | http.Error(w, err.Error(), 500) 95 | return 96 | } 97 | if fileinfo.Size() > 1024*1024*8 { 98 | http.Error(w, "File size limit exceeded [8MB]", http.StatusRequestEntityTooLarge) 99 | return 100 | } 101 | 102 | dr := FaceDetect(f.Name()) 103 | if dr.Size[0] == 0 && dr.Size[1] == 0 { 104 | http.Error(w, "Invalid file operand", 400) 105 | return 106 | } 107 | drctx, _ := json.Marshal(dr) 108 | w.Header().Set("Content-Type", "application/json") 109 | w.Write([]byte(drctx)) 110 | } 111 | 112 | func main() { 113 | http.HandleFunc("/", HandlerRoot) 114 | http.HandleFunc("/image/bin/detection", HandlerImageBinDetection) 115 | log.Printf("main: listen and server on :%s", Port) 116 | http.ListenAndServe(":"+Port, nil) 117 | } 118 | --------------------------------------------------------------------------------