Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modified key point tracking #151

Merged
merged 1 commit into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cpp/inspireface/common/face_data/face_serialize_tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "../face_info/face_object_internal.h"
#include "herror.h"
#include "data_type.h"
#include "track_module/landmark/all.h"

// Define the namespace "inspire" for encapsulation
namespace inspire {
Expand Down Expand Up @@ -100,7 +101,7 @@ inline HyperFaceData INSPIRE_API FaceObjectInternalToHyperFaceData(const FaceObj
if (!obj.landmark_smooth_aux_.empty()) {
data.densityLandmarkEnable = 1;
const auto& lmk = obj.landmark_smooth_aux_.back();
for (size_t i = 0; i < lmk.size(); i++) {
for (size_t i = 0; i < FaceLandmarkAdapt::NUM_OF_LANDMARK; i++) {
data.densityLandmark[i].x = lmk[i].GetX();
data.densityLandmark[i].y = lmk[i].GetY();
}
Expand Down
16 changes: 10 additions & 6 deletions cpp/inspireface/common/face_info/face_object_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@ class INSPIRE_API FaceObjectInternal {
pose_euler_angle_.resize(3);
keyPointFive.resize(5);
face_action_ = std::make_shared<FaceActionPredictor>(10);
num_of_dense_landmark_ = num_landmark;
}

void SetLandmark(const std::vector<inspirecv::Point2f> &lmk, bool update_rect = true, bool update_matrix = true, float h = 0.06f, int n = 5) {
if (lmk.size() != landmark_.size()) {
INSPIRE_LOGW("The SetLandmark function displays an exception indicating that the lmk number does not match");
return;
}
void SetLandmark(const std::vector<inspirecv::Point2f> &lmk, bool update_rect = true, bool update_matrix = true, float h = 0.06f, int n = 5,
int num_of_lmk = 106 * 2) {
// if (lmk.size() != landmark_.size()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The size check for landmarks should not be commented out. This is an important validation that prevents buffer overflows and ensures data consistency. If the landmark size needs to be flexible, consider adding a resize operation instead of removing the check.

// INSPIRE_LOGW("The SetLandmark function displays an exception indicating that the lmk number does not match");
// return;
// }
std::copy(lmk.begin(), lmk.end(), landmark_.begin());
DynamicSmoothParamUpdate(landmark_, landmark_smooth_aux_, 106 * 2, h, n);
DynamicSmoothParamUpdate(landmark_, landmark_smooth_aux_, num_of_lmk, h, n);
// std::cout << "smooth ratio: " << h << " num smooth cache frame: " << n << std::endl;

// cv::Vec3d euler_angle;
Expand Down Expand Up @@ -202,6 +204,8 @@ class INSPIRE_API FaceObjectInternal {
inspirecv::Vec3f euler_angle_;
std::vector<float> pose_euler_angle_;

int num_of_dense_landmark_;

float align_mse_{};

const inspirecv::Vec3f &getEulerAngle() const {
Expand Down
30 changes: 15 additions & 15 deletions cpp/inspireface/track_module/face_track_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ bool FaceTrackModule::TrackFace(inspirecv::InspireImageProcess &image, FaceObjec
// If it is a detection state, calculate the affine transformation matrix
if (face.TrackingState() == ISF_DETECT) {
COST_TIME_SIMPLE(GetRectSquare);
inspirecv::Rect2i rect_square = face.GetRectSquare(0.0);
inspirecv::Rect2i rect_square = face.GetRectSquare(0);

std::vector<inspirecv::Point2f> rect_pts = rect_square.As<float>().ToFourVertices();
inspirecv::TransformMatrix rotation_mode_affine = image.GetAffineMatrix();
Expand Down Expand Up @@ -108,6 +108,7 @@ bool FaceTrackModule::TrackFace(inspirecv::InspireImageProcess &image, FaceObjec
auto affine_extensive = face.getTransMatrixExtensive();
auto pre_crop = image.ExecuteImageAffineProcessing(affine_extensive, m_crop_extensive_size_, m_crop_extensive_size_);
auto res = (*m_face_quality_)(pre_crop);

auto affine_extensive_inv = affine_extensive.GetInverse();
std::vector<inspirecv::Point2f> lmk_extensive = ApplyTransformToPoints(res.lmk, affine_extensive_inv);
res.lmk = lmk_extensive;
Expand Down Expand Up @@ -186,22 +187,21 @@ bool FaceTrackModule::TrackFace(inspirecv::InspireImageProcess &image, FaceObjec
// INSPIRE_LOGD("Extensive Affine Cost %f", extensive_cost_time.GetCostTimeUpdate());
}
}
// Replace the landmark with the high-quality landmark
landmark_back[FaceLandmarkAdapt::LEFT_EYE_CENTER] = face.high_result.lmk[0];
landmark_back[FaceLandmarkAdapt::RIGHT_EYE_CENTER] = face.high_result.lmk[1];
landmark_back[FaceLandmarkAdapt::NOSE_CORNER] = face.high_result.lmk[2];
landmark_back[FaceLandmarkAdapt::MOUTH_LEFT_CORNER] = face.high_result.lmk[3];
landmark_back[FaceLandmarkAdapt::MOUTH_RIGHT_CORNER] = face.high_result.lmk[4];
// Add five key points to landmark_back
for (int i = 0; i < 5; i++) {
landmark_back.push_back(face.high_result.lmk[i]);
}
// Update face key points
face.SetLandmark(landmark_back, true, true, m_track_mode_smooth_ratio_, m_track_mode_num_smooth_cache_frame_);
face.SetLandmark(landmark_back, true, true, m_track_mode_smooth_ratio_, m_track_mode_num_smooth_cache_frame_,
(FaceLandmarkAdapt::NUM_OF_LANDMARK + 10) * 2);
// Get the smoothed landmark
auto &landmark_smooth = face.landmark_smooth_aux_.back();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The landmark handling has been changed from using explicit enum indices to using NUM_OF_LANDMARK offset. While this makes the code more maintainable, consider adding a comment explaining the layout of the landmark array and why the offset is needed. This will help future maintainers understand the data structure.

// Update the face key points
face.high_result.lmk[0] = landmark_smooth[FaceLandmarkAdapt::LEFT_EYE_CENTER];
face.high_result.lmk[1] = landmark_smooth[FaceLandmarkAdapt::RIGHT_EYE_CENTER];
face.high_result.lmk[2] = landmark_smooth[FaceLandmarkAdapt::NOSE_CORNER];
face.high_result.lmk[3] = landmark_smooth[FaceLandmarkAdapt::MOUTH_LEFT_CORNER];
face.high_result.lmk[4] = landmark_smooth[FaceLandmarkAdapt::MOUTH_RIGHT_CORNER];
face.high_result.lmk[0] = landmark_smooth[FaceLandmarkAdapt::NUM_OF_LANDMARK + 0];
face.high_result.lmk[1] = landmark_smooth[FaceLandmarkAdapt::NUM_OF_LANDMARK + 1];
face.high_result.lmk[2] = landmark_smooth[FaceLandmarkAdapt::NUM_OF_LANDMARK + 2];
face.high_result.lmk[3] = landmark_smooth[FaceLandmarkAdapt::NUM_OF_LANDMARK + 3];
face.high_result.lmk[4] = landmark_smooth[FaceLandmarkAdapt::NUM_OF_LANDMARK + 4];
}

// If tracking status, update the confidence level
Expand Down Expand Up @@ -315,7 +315,7 @@ void FaceTrackModule::DetectFace(const inspirecv::Image &input, float scale) {
std::vector<STrack> output_stracks = m_TbD_tracker_->update(objects);
for (const auto &st_track : output_stracks) {
inspirecv::Rect<int> rect = inspirecv::Rect<int>(st_track.tlwh[0], st_track.tlwh[1], st_track.tlwh[2], st_track.tlwh[3]);
FaceObjectInternal faceinfo(st_track.track_id, rect, FaceLandmarkAdapt::NUM_OF_LANDMARK);
FaceObjectInternal faceinfo(st_track.track_id, rect, FaceLandmarkAdapt::NUM_OF_LANDMARK + 10);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number 10 in NUM_OF_LANDMARK + 10 appears multiple times in the code. This should be defined as a named constant to improve maintainability and make the purpose of this offset clear.

faceinfo.detect_bbox_ = rect.As<int>();
candidate_faces_.push_back(faceinfo);
}
Expand All @@ -336,7 +336,7 @@ void FaceTrackModule::DetectFace(const inspirecv::Image &input, float scale) {
tracking_idx_ = tracking_idx_ + 1;
}

FaceObjectInternal faceinfo(tracking_idx_, bbox[i], FaceLandmarkAdapt::NUM_OF_LANDMARK);
FaceObjectInternal faceinfo(tracking_idx_, bbox[i], FaceLandmarkAdapt::NUM_OF_LANDMARK + 10);
faceinfo.detect_bbox_ = bbox[i];
faceinfo.SetConfidence(boxes[i].score);

Expand Down
3 changes: 2 additions & 1 deletion cpp/inspireface/track_module/face_track_module.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ class INSPIRE_API FaceTrackModule {
int m_dynamic_detection_input_level_ = -1; ///< Detector size class for dynamic input.

float m_crop_extensive_ratio_ = 1.8f; ///< Crop extensive ratio
int m_crop_extensive_size_ = 96; ///< Crop extensive size
// float m_crop_extensive_ratio_ = 1.5f; ///< Crop extensive ratio
int m_crop_extensive_size_ = 96; ///< Crop extensive size

DetectModuleMode m_mode_; ///< Detect mode

Expand Down
2 changes: 1 addition & 1 deletion cpp/sample/source/tracker_sample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ int main() {
std::string expansion_path = "";
INSPIRE_LAUNCH->Load("test_res/pack/Pikachu");
auto archive = INSPIRE_LAUNCH->getMArchive();
auto mode = inspire::DetectModuleMode::DETECT_MODE_LIGHT_TRACK;
auto mode = inspire::DetectModuleMode::DETECT_MODE_ALWAYS_DETECT;
FaceTrackModule tracker(mode, 10, 20, 320, -1);
tracker.Configuration(archive, expansion_path);

Expand Down
5 changes: 3 additions & 2 deletions doc/Error-Feedback-Codes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Error Feedback Codes
During the use of InspireFace, some error feedback codes may be generated. Here is a table of error feedback codes.
# Error Feedback Codes

During the use of InspireFace, some error feedback codes may be generated. Here is a table of error feedback codes.

| Index | Name | Code | Comment |
| --- | --- | --- | --- |
Expand Down
41 changes: 32 additions & 9 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,23 @@ pip install inspireface

## Setup Library

#### Copy the compiled dynamic library

You need to compile the dynamic linking library in the main project and then place it in **inspireface/modules/core/SYSTEM/CORE_ARCH/**.

```Bash
# copy or link
cp YOUR_BUILD_DIR/libInspireFace.so inspireface/modules/core/SYSTEM/CORE_ARCH/
```

#### Install

Run the command to install:

```
python setup.py install
```

## Require

You need to install some dependencies beforehand.
Expand All @@ -38,14 +48,11 @@ You can easily call the api to implement a number of functions:
import cv2
import inspireface as isf

# Step 1: Initialize the SDK and load the algorithm resource files.
resource_path = "pack/Pikachu"
ret = isf.launch(resource_path)
assert ret, "Launch failure. Please ensure the resource path is correct."

# Optional features, loaded during session creation based on the modules specified.
opt = isf.HF_ENABLE_NONE
session = isf.InspireFaceSession(opt, isf.HF_DETECT_MODE_IMAGE)
session = isf.InspireFaceSession(opt, isf.HF_DETECT_MODE_ALWAYS_DETECT)
# Set detection confidence threshold
session.set_detection_confidence_threshold(0.5)

# Load the image using OpenCV.
image = cv2.imread(image_path)
Expand All @@ -60,14 +67,30 @@ draw = image.copy()
for idx, face in enumerate(faces):
print(f"{'==' * 20}")
print(f"idx: {idx}")
# Print detection confidence.
print(f"detection confidence: {face.detection_confidence}")
# Print Euler angles of the face.
print(f"roll: {face.roll}, yaw: {face.yaw}, pitch: {face.pitch}")
# Draw bounding box around the detected face.

# Get face bounding box
x1, y1, x2, y2 = face.location
cv2.rectangle(draw, (x1, y1), (x2, y2), (0, 0, 255), 2)

# Calculate center, size, and angle
center = ((x1 + x2) / 2, (y1 + y2) / 2)
size = (x2 - x1, y2 - y1)
angle = face.roll

# Apply rotation to the bounding box corners
rect = ((center[0], center[1]), (size[0], size[1]), angle)
box = cv2.boxPoints(rect)
box = box.astype(int)

# Draw the rotated bounding box
cv2.drawContours(draw, [box], 0, (100, 180, 29), 2)

# Draw landmarks
lmk = session.get_face_dense_landmark(face)
for x, y in lmk.astype(int):
cv2.circle(draw, (x, y), 0, (220, 100, 0), 2)
```


Expand Down
14 changes: 14 additions & 0 deletions python/inspireface/modules/inspireface.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,20 @@ def face_detection(self, image) -> List[FaceInformation]:
return infos
else:
return []

def get_face_five_key_points(self, single_face: FaceInformation):
num_landmarks = 5
landmarks_array = (HPoint2f * num_landmarks)()
ret = HFGetFaceFiveKeyPointsFromFaceToken(single_face._token, landmarks_array, num_landmarks)
if ret != 0:
logger.error(f"An error occurred obtaining a dense landmark for a single face: {ret}")

landmark = []
for point in landmarks_array:
landmark.append(point.x)
landmark.append(point.y)

return np.asarray(landmark).reshape(-1, 2)

def get_face_dense_landmark(self, single_face: FaceInformation):
num_landmarks = HInt32()
Expand Down
8 changes: 8 additions & 0 deletions python/sample_face_track_from_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ def generate_color(id):
Returns:
tuple: A tuple representing the color in BGR format.
"""
# Handle invalid ID (-1)
if id < 0:
return (128, 128, 128) # Return gray color for invalid ID

max_id = 50 # Number of unique colors
id = id % max_id

Expand Down Expand Up @@ -119,6 +123,10 @@ def case_face_tracker_from_video(source, show, out):
for x, y in lmk.astype(int):
cv2.circle(frame, (x, y), 0, color, 4)

five_key_points = session.get_face_five_key_points(face)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The visualization code for five key points uses inverted colors which may not be visually optimal in all cases. Consider using a more distinct color scheme or making the color choice configurable. Also, the magic number 6 for circle radius should be defined as a constant.

for x, y in five_key_points.astype(int):
cv2.circle(frame, (x, y), 0, (255-color[0], 255-color[1], 255-color[2]), 6)

# Draw track ID at the top of the bounding box
text = f"ID: {face.track_id}"
text_size, _ = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)
Expand Down
2 changes: 2 additions & 0 deletions tools/error_table.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Error Feedback Codes
During the use of InspireFace, some error feedback codes may be generated. Here is a table of error feedback codes.
| Index | Name | Code | Comment |
| --- | --- | --- | --- |
| 1 | HSUCCEED | 0 | Success |
Expand Down
8 changes: 7 additions & 1 deletion tools/output_error_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,14 @@ def process_header(header_path, output_path):
# Parse and calculate the error codes from the header content
parsed_error_codes = parse_and_calculate_error_codes(header_content)

md_table = """# Error Feedback Codes

During the use of InspireFace, some error feedback codes may be generated. Here is a table of error feedback codes.

"""

# Prepare the Markdown table header
md_table = " | Index | Name | Code | Comment | \n"
md_table += " | Index | Name | Code | Comment | \n"
md_table += " | --- | --- | --- | --- | \n"

# Fill the Markdown table with parsed error codes
Expand Down
Loading