-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: zhd5120153951 <[email protected]>
- Loading branch information
1 parent
89c5df1
commit 7c7735e
Showing
16 changed files
with
375 additions
and
454 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# scrfd-opencv | ||
使用OpenCV部署SCRFD人脸检测,包含C++和Python两种版本的程序实现,本套程序只依赖opencv库就可以运行, 从而彻底摆脱对任何深度学习框架的依赖。 | ||
|
||
SCRFD是一个FCOS式的人脸检测器,2021年5月刚发出来的,SCRFD 是高效率高精度人脸检测算法,速度和精度相比其他算法都有提升。 | ||
你的机器里只要安装里OpenCV库,就能运行本套程序。C++版本的主程序是main.cpp,Python版本的主程序是main.py。 | ||
程序输出检测到的人脸矩形框和5个关键点 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
#include <fstream> | ||
#include <sstream> | ||
#include <iostream> | ||
#include <opencv2/dnn.hpp> | ||
#include <opencv2/imgproc.hpp> | ||
#include <opencv2/highgui.hpp> | ||
|
||
using namespace cv; | ||
using namespace dnn; | ||
using namespace std; | ||
|
||
struct Net_config | ||
{ | ||
float confThreshold; // class Confidence threshold | ||
float nmsThreshold; // Non-maximum suppression threshold | ||
string modelfile; | ||
}; | ||
|
||
class SCRFD | ||
{ | ||
public: | ||
SCRFD(Net_config config); | ||
void detect(Mat& frame); | ||
private: | ||
|
||
const float stride[3] = { 8.0, 16.0, 32.0 }; | ||
const int inpWidth = 640; | ||
const int inpHeight = 640; | ||
float confThreshold; | ||
float nmsThreshold; | ||
const bool keep_ratio = true; | ||
Net net; | ||
Mat resize_image(Mat srcimg, int* newh, int* neww, int* top, int* left); | ||
}; | ||
|
||
SCRFD::SCRFD(Net_config config) | ||
{ | ||
this->confThreshold = config.confThreshold; | ||
this->nmsThreshold = config.nmsThreshold; | ||
this->net = readNet(config.modelfile); | ||
} | ||
|
||
Mat SCRFD::resize_image(Mat srcimg, int* newh, int* neww, int* top, int* left) | ||
{ | ||
int srch = srcimg.rows, srcw = srcimg.cols; | ||
*newh = this->inpHeight; | ||
*neww = this->inpWidth; | ||
Mat dstimg; | ||
if (this->keep_ratio && srch != srcw) | ||
{ | ||
float hw_scale = (float)srch / srcw; | ||
if (hw_scale > 1) | ||
{ | ||
*newh = this->inpHeight; | ||
*neww = int(this->inpWidth / hw_scale); | ||
resize(srcimg, dstimg, Size(*neww, *newh), INTER_AREA); | ||
*left = int((this->inpWidth - *neww) * 0.5); | ||
copyMakeBorder(dstimg, dstimg, 0, 0, *left, this->inpWidth - *neww - *left, BORDER_CONSTANT, 0); | ||
} | ||
else | ||
{ | ||
*newh = (int)this->inpHeight * hw_scale; | ||
*neww = this->inpWidth; | ||
resize(srcimg, dstimg, Size(*neww, *newh), INTER_AREA); | ||
*top = (int)(this->inpHeight - *newh) * 0.5; | ||
copyMakeBorder(dstimg, dstimg, *top, this->inpHeight - *newh - *top, 0, 0, BORDER_CONSTANT, 0); | ||
} | ||
} | ||
else | ||
{ | ||
resize(srcimg, dstimg, Size(*neww, *newh), INTER_AREA); | ||
} | ||
return dstimg; | ||
} | ||
|
||
void SCRFD::detect(Mat& frame) | ||
{ | ||
int newh = 0, neww = 0, padh = 0, padw = 0; | ||
Mat img = this->resize_image(frame, &newh, &neww, &padh, &padw); | ||
Mat blob; | ||
blobFromImage(img, blob, 1 / 128.0, Size(this->inpWidth, this->inpHeight), Scalar(127.5, 127.5, 127.5), true, false); | ||
this->net.setInput(blob); | ||
vector<Mat> outs; | ||
this->net.forward(outs, this->net.getUnconnectedOutLayersNames()); | ||
|
||
/////generate proposals | ||
vector<float> confidences; | ||
vector<Rect> boxes; | ||
vector< vector<int>> landmarks; | ||
float ratioh = (float)frame.rows / newh, ratiow = (float)frame.cols / neww; | ||
int n = 0, i = 0, j = 0, k = 0, l = 0; | ||
for (n = 0; n < 3; n++) ///�߶� | ||
{ | ||
int num_grid_x = (int)(this->inpWidth / this->stride[n]); | ||
int num_grid_y = (int)(this->inpHeight / this->stride[n]); | ||
float* pdata_score = (float*)outs[n * 3].data; ///score | ||
float* pdata_bbox = (float*)outs[n * 3 + 1].data; ///bounding box | ||
float* pdata_kps = (float*)outs[n * 3 + 2].data; ///face landmark | ||
for (i = 0; i < num_grid_y; i++) | ||
{ | ||
for (j = 0; j < num_grid_x; j++) | ||
{ | ||
for (k = 0; k < 2; k++) | ||
{ | ||
if (pdata_score[0] > this->confThreshold) | ||
{ | ||
const int xmin = (int)(((j - pdata_bbox[0]) * this->stride[n] - padw) * ratiow); | ||
const int ymin = (int)(((i - pdata_bbox[1]) * this->stride[n] - padh) * ratioh); | ||
const int width = (int)((pdata_bbox[2] + pdata_bbox[0])*this->stride[n] * ratiow); | ||
const int height = (int)((pdata_bbox[3] + pdata_bbox[1])*this->stride[n] * ratioh); | ||
confidences.push_back(pdata_score[0]); | ||
boxes.push_back(Rect(xmin, ymin, width, height)); | ||
vector<int> landmark(10, 0); | ||
for (l = 0; l < 10; l+=2) | ||
{ | ||
landmark[l] = (int)(((j + pdata_kps[l]) * this->stride[n] - padw) * ratiow); | ||
landmark[l + 1] = (int)(((i + pdata_kps[l + 1]) * this->stride[n] - padh) * ratioh); | ||
} | ||
landmarks.push_back(landmark); | ||
} | ||
pdata_score++; | ||
pdata_bbox += 4; | ||
pdata_kps += 10; | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Perform non maximum suppression to eliminate redundant overlapping boxes with | ||
// lower confidences | ||
vector<int> indices; | ||
dnn::NMSBoxes(boxes, confidences, this->confThreshold, this->nmsThreshold, indices); | ||
for (i = 0; i < indices.size(); ++i) | ||
{ | ||
int idx = indices[i]; | ||
Rect box = boxes[idx]; | ||
rectangle(frame, Point(box.x, box.y), Point(box.x + box.width, box.y + box.height), Scalar(0, 0, 255), 2); | ||
for (k = 0; k < 10; k+=2) | ||
{ | ||
circle(frame, Point(landmarks[idx][k], landmarks[idx][k + 1]), 1, Scalar(0, 255, 0), -1); | ||
} | ||
|
||
//Get the label for the class name and its confidence | ||
string label = format("%.2f", confidences[idx]); | ||
//Display the label at the top of the bounding box | ||
int baseLine; | ||
Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine); | ||
int top = max(box.y, labelSize.height); | ||
//rectangle(frame, Point(left, top - int(1.5 * labelSize.height)), Point(left + int(1.5 * labelSize.width), top + baseLine), Scalar(0, 255, 0), FILLED); | ||
putText(frame, label, Point(box.x, top), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 255, 0), 1); | ||
} | ||
} | ||
|
||
int main() | ||
{ | ||
Net_config cfg = { 0.5, 0.5, "weights/scrfd_2.5g_kps.onnx" }; ///choices = ["weights/scrfd_500m_kps.onnx", "weights/scrfd_2.5g_kps.onnx", "weights/scrfd_10g_kps.onnx"] | ||
SCRFD mynet(cfg); | ||
string imgpath = "selfie.jpg"; | ||
Mat srcimg = imread(imgpath); | ||
mynet.detect(srcimg); | ||
|
||
static const string kWinName = "Deep learning object detection in OpenCV"; | ||
namedWindow(kWinName, WINDOW_NORMAL); | ||
imshow(kWinName, srcimg); | ||
waitKey(0); | ||
destroyAllWindows(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import cv2 | ||
import argparse | ||
import numpy as np | ||
|
||
class SCRFD(): | ||
def __init__(self, onnxmodel, confThreshold=0.5, nmsThreshold=0.5): | ||
self.inpWidth = 640 | ||
self.inpHeight = 640 | ||
self.confThreshold = confThreshold | ||
self.nmsThreshold = nmsThreshold | ||
self.net = cv2.dnn.readNet(onnxmodel) | ||
self.keep_ratio = True | ||
self.fmc = 3 | ||
self._feat_stride_fpn = [8, 16, 32] | ||
self._num_anchors = 2 | ||
def resize_image(self, srcimg): | ||
padh, padw, newh, neww = 0, 0, self.inpHeight, self.inpWidth | ||
if self.keep_ratio and srcimg.shape[0] != srcimg.shape[1]: | ||
hw_scale = srcimg.shape[0] / srcimg.shape[1] | ||
if hw_scale > 1: | ||
newh, neww = self.inpHeight, int(self.inpWidth / hw_scale) | ||
img = cv2.resize(srcimg, (neww, newh), interpolation=cv2.INTER_AREA) | ||
padw = int((self.inpWidth - neww) * 0.5) | ||
img = cv2.copyMakeBorder(img, 0, 0, padw, self.inpWidth - neww - padw, cv2.BORDER_CONSTANT, | ||
value=0) # add border | ||
else: | ||
newh, neww = int(self.inpHeight * hw_scale) + 1, self.inpWidth | ||
img = cv2.resize(srcimg, (neww, newh), interpolation=cv2.INTER_AREA) | ||
padh = int((self.inpHeight - newh) * 0.5) | ||
img = cv2.copyMakeBorder(img, padh, self.inpHeight - newh - padh, 0, 0, cv2.BORDER_CONSTANT, value=0) | ||
else: | ||
img = cv2.resize(srcimg, (self.inpWidth, self.inpHeight), interpolation=cv2.INTER_AREA) | ||
return img, newh, neww, padh, padw | ||
def distance2bbox(self, points, distance, max_shape=None): | ||
x1 = points[:, 0] - distance[:, 0] | ||
y1 = points[:, 1] - distance[:, 1] | ||
x2 = points[:, 0] + distance[:, 2] | ||
y2 = points[:, 1] + distance[:, 3] | ||
if max_shape is not None: | ||
x1 = x1.clamp(min=0, max=max_shape[1]) | ||
y1 = y1.clamp(min=0, max=max_shape[0]) | ||
x2 = x2.clamp(min=0, max=max_shape[1]) | ||
y2 = y2.clamp(min=0, max=max_shape[0]) | ||
return np.stack([x1, y1, x2, y2], axis=-1) | ||
def distance2kps(self, points, distance, max_shape=None): | ||
preds = [] | ||
for i in range(0, distance.shape[1], 2): | ||
px = points[:, i % 2] + distance[:, i] | ||
py = points[:, i % 2 + 1] + distance[:, i + 1] | ||
if max_shape is not None: | ||
px = px.clamp(min=0, max=max_shape[1]) | ||
py = py.clamp(min=0, max=max_shape[0]) | ||
preds.append(px) | ||
preds.append(py) | ||
return np.stack(preds, axis=-1) | ||
def detect(self, srcimg): | ||
img, newh, neww, padh, padw = self.resize_image(srcimg) | ||
blob = cv2.dnn.blobFromImage(img, 1.0 / 128, (self.inpWidth, self.inpHeight), (127.5, 127.5, 127.5), swapRB=True) | ||
# Sets the input to the network | ||
self.net.setInput(blob) | ||
|
||
# Runs the forward pass to get output of the output layers | ||
outs = self.net.forward(self.net.getUnconnectedOutLayersNames()) | ||
# inference output | ||
scores_list, bboxes_list, kpss_list = [], [], [] | ||
for idx, stride in enumerate(self._feat_stride_fpn): | ||
scores = outs[idx * self.fmc][0] | ||
bbox_preds = outs[idx * self.fmc + 1][0] * stride | ||
kps_preds = outs[idx * self.fmc + 2][0] * stride | ||
height = blob.shape[2] // stride | ||
width = blob.shape[3] // stride | ||
anchor_centers = np.stack(np.mgrid[:height, :width][::-1], axis=-1).astype(np.float32) | ||
anchor_centers = (anchor_centers * stride).reshape((-1, 2)) | ||
if self._num_anchors > 1: | ||
anchor_centers = np.stack([anchor_centers] * self._num_anchors, axis=1).reshape((-1, 2)) | ||
|
||
pos_inds = np.where(scores >= self.confThreshold)[0] | ||
bboxes = self.distance2bbox(anchor_centers, bbox_preds) | ||
pos_scores = scores[pos_inds] | ||
pos_bboxes = bboxes[pos_inds] | ||
scores_list.append(pos_scores) | ||
bboxes_list.append(pos_bboxes) | ||
|
||
kpss = self.distance2kps(anchor_centers, kps_preds) | ||
# kpss = kps_preds | ||
kpss = kpss.reshape((kpss.shape[0], -1, 2)) | ||
pos_kpss = kpss[pos_inds] | ||
kpss_list.append(pos_kpss) | ||
|
||
scores = np.vstack(scores_list).ravel() | ||
# bboxes = np.vstack(bboxes_list) / det_scale | ||
# kpss = np.vstack(kpss_list) / det_scale | ||
bboxes = np.vstack(bboxes_list) | ||
kpss = np.vstack(kpss_list) | ||
bboxes[:, 2:4] = bboxes[:, 2:4] - bboxes[:, 0:2] | ||
ratioh, ratiow = srcimg.shape[0] / newh, srcimg.shape[1] / neww | ||
bboxes[:, 0] = (bboxes[:, 0] - padw) * ratiow | ||
bboxes[:, 1] = (bboxes[:, 1] - padh) * ratioh | ||
bboxes[:, 2] = bboxes[:, 2] * ratiow | ||
bboxes[:, 3] = bboxes[:, 3] * ratioh | ||
kpss[:, :, 0] = (kpss[:, :, 0] - padw) * ratiow | ||
kpss[:, :, 1] = (kpss[:, :, 1] - padh) * ratioh | ||
indices = cv2.dnn.NMSBoxes(bboxes.tolist(), scores.tolist(), self.confThreshold, self.nmsThreshold) | ||
for i in indices: | ||
i = i[0] | ||
xmin, ymin, xamx, ymax = int(bboxes[i, 0]), int(bboxes[i, 1]), int(bboxes[i, 0] + bboxes[i, 2]), int(bboxes[i, 1] + bboxes[i, 3]) | ||
cv2.rectangle(srcimg, (xmin, ymin), (xamx, ymax), (0, 0, 255), thickness=2) | ||
for j in range(5): | ||
cv2.circle(srcimg, (int(kpss[i, j, 0]), int(kpss[i, j, 1])), 1, (0,255,0), thickness=-1) | ||
cv2.putText(srcimg, str(round(scores[i], 3)), (xmin, ymin - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), thickness=1) | ||
return srcimg | ||
|
||
if __name__ == '__main__': | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument('--imgpath', type=str, default='s_l.jpg', help='image path') | ||
parser.add_argument('--onnxmodel', default='weights/scrfd_500m_kps.onnx', type=str, choices=['weights/scrfd_500m_kps.onnx', 'weights/scrfd_2.5g_kps.onnx', 'weights/scrfd_10g_kps.onnx'], help='onnx model') | ||
parser.add_argument('--confThreshold', default=0.5, type=float, help='class confidence') | ||
parser.add_argument('--nmsThreshold', default=0.5, type=float, help='nms iou thresh') | ||
args = parser.parse_args() | ||
|
||
mynet = SCRFD(args.onnxmodel, confThreshold=args.confThreshold, nmsThreshold=args.nmsThreshold) | ||
srcimg = cv2.imread(args.imgpath) | ||
outimg = mynet.detect(srcimg) | ||
|
||
winName = 'Deep learning object detection in OpenCV' | ||
cv2.namedWindow(winName, 0) | ||
cv2.imshow(winName, outimg) | ||
cv2.waitKey(0) | ||
cv2.destroyAllWindows() |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.