Tracking result capture is automatically enabled when Image Capture is enabled. As each image is being captured the tracking result metadata is also being captured. This tracking result metadata is sent to a different deposit URL, but it is derived from the image capture deposit URL. More details about this process are documented in the Image Capture document so this document just focuses on the changes needed to post and retrieve the tracking result metadata.
VIRGO sends a HTTP POST request with the JSON metadata in its body to the final per-tracking result deposit URL. The final deposit URL is formed by appending "_tracking_result" to the “capture.deposite-base-url” and then adding the JSON filename. Here is an example showing a sample capture deposit base url and a final URL modified by VIRGO. The filename is optional on the server side, but it is useful for logging, which is why VIRGO appends it.
capture.deposite-base-url = https://cvos.dev.real.com/sharedStream/video_d98fe652-9a76-4578-863a-104c1b86dec3
final-deposit-tracking-result-url = https://cvos.dev.real.com/sharedStream/video_d98fe652-9a76-4578-863a-104c1b86dec3_tracking_result/Samsung_2020-02-05_56677000.json
The JSON file name is computed as follows:
<feed name>_<yyyy>-<mm>-<dd>_<pts>.json
Where “yyyy-mm-dd” is referring to the current year, month, and day, and “pts” refers to the presentation time stamp of the captured frame. The presentation time stamp is in terms of microseconds from the start of the stream.
The following HTTP custom headers are included with every capture POST request:
Header | Description |
---|---|
X-RPC-DIRECTORY | The directory that is in use for the current account. |
X-RPC-AUTHORIZATION | The authorization header for the request that contains the current user information. |
X-FEED-ID | The ID of the feed from which the image was captured. E.g.: VRGO-LNX-TRPR-16-123-camera_1 |
X-CLIENT-TYPE | The type of the client. This is a combination of the client name and the platform name. E.g.: Virgo-Linux |
X-CLIENT-ID | The client id of the virgod instance. This is immutable, factory configured, unique and descriptive vrgo instance identifier. E.g.: VRGO-LNX-TRPR-16-123 |
VIRGO will always posts the video frame images and tracking results in the proper order. This means that the timestamps are always moving forward. The client doesn’t need to synchronize the image timestamp with the tracking result timestamp because of the way VIRGO posts these they should already be synchronized. What this means is that video frame images and tracking results are posted as quickly as possible and generally have minimal delay. VIRGO posts the video frame images and the tracking results in parallel.
The SAFR client will retrieve capture images and capture tracking results metadata as fast as possible. In parallel to retrieving the video frames the client can retrieve the tracking results from the corresponding stream. Following the same approach as outlined above the client will append "_tracking_result" to the URL in which to receive capture information. It will retrieve the frames and tracking results as fast as possible, by making a new GET request right after receiving the previous one. The client needs to manage renewing the capture stream before it ends so that there is no stalling of the video. Generally a safe rule of thumb is to renew the stream after half of the frames has been posted. The formula below illustrates how to calculate the estimated posted frame count.
let configFramesPerSecond = 1 / (TimeInterval(configFrameDelay) / 1000)
let estimatedPostedFrameCount = Int((currentDate.timeIntervalSince(initialImageReceivedDate!) * min(configFramesPerSecond, videoFramesPerSecond) + 0.5))
// So if there is no image and no error we should renew the stream. In this case it is possible that Virgo quit posting the images for some
// or it just reached the limit. This should really never happen unless there is a problem, but this will recover from that case.
// If the estimated posted frame count is greater than half of the total frames then we renew the stream. This is a light weight operation that
// just makes sure that Virgo continues to post frames.
if (image == nil && error == nil) || estimatedPostedFrameCount >= maxFrames / 2 {
renewImageStream()
}
Timestamp = {
"microseconds": "Int64",
"date": "Int64"
}
Badge = {
"badgeId": "Int64",
"detectionService": "String" // "apriltags", "rhinotagsLite", "rhinotagsTeam", "rhinotagsFlex", "rhinotagsFull"
}
RecognizedObject = {
"objectId": "String", // "person"
"objectType": "String", // "person"
"idClass": "String", // "unknown", "unidentified", "stranger", "noconcern", "concern", "threat"
"enabled": "Bool"
}
DetectedObject = {
"objectType": "String", // "face", "badge", "recognizedObject"
"localId": "Int64",
"normalizedBounds": {
"x": "Double",
"y": "Double",
"width": "Double",
"height": "Double"
}
"thumbnailBoundsExpansionFactor": "Double",
"confidence": "Double",
"centerPoseQuality": "Double",
"imageSharpnessQuality": "Double",
"imageContrastQuality": "Double",
"yaw": "Double",
"pitch": "Double",
"roll": "Double",
"clipRatio": "Double",
"pixelBounds": {
"x": "Double",
"y": "Double",
"width": "Double",
"height": "Double"
},
// Face only (objectType = "face")
"validatorConfidence": "Double",
// Badge only (objectType = "badge")
"badge": "Badge",
// RecognizedObject only (objectType = "recognizedObject")
"recognizedObject": "RecognizedObject",
}
PersonUpdatableProperties = {
"name": "String",
"tags": [
"String"
],
"ignore": "Bool",
"mergedWithPersonId": "String",
"gender": "String",
"age": {
"lowerBound": "Int64",
"upperBound": "Int64",
}
"externalId": "String",
"personType": "String",
"validationPhone": "String",
"validationEmail": "String",
"homeLocation": "String",
"company": "String",
"moniker": "String",
"idClass": "String", // "unknown", "unidentified", "stranger", "noconcern", "concern", "threat"
"rootPersonExpirationDate": "Int64"
}
Person = {
"personId": "String",
"imageUrl": "String",
"unmergedImageUrl": "String",
"rootPersonAddDate": "Int64",
"sentiment": "Double",
"smile": "String",
"occlusion": "Double",
"updatableProperties": "PersonUpdatableProperties",
"similarPeople": [
"Person"
],
"similarityScore": "Double",
"similarDirectory": "String",
"confidence": "Double",
"hasMergedPeople": "Bool",
"profilePose": "Bool",
"profilePoseConfidence": "Double",
"isOccluded": "Bool",
"faceConfirmed": "Bool"
}
TrackedObject = {
"objectType": "String", // "face", "badge", "recognizedObject"
"localId": "Int64",
"person": "Person",
"isNew": "Bool",
"detectedObject": "DetectedObject",
"occluded": "Bool",
"isolated": "Bool",
"state": "String", // "detected", "recognizing", "recognized", "unconfirmed", "reconfirming"
"allowsMerging": "Bool",
"allowRecognizerToLearn": "Bool",
"timeOfInitialDetection": "Timestamp",
"timeOfMostRecentConfirmationAttempt": "Timestamp",
"lingeringCount": "Int64",
"isZombie": "Bool",
"identityRecognitionThresholdBoost": "Double",
"completedSuccessfulRecognitionAttempt": "Bool",
"completedSuccessfulIdentificationAttempt": "Bool",
"receivedNotOccludedRecognitionResult": "Bool",
"disableOcclusionForStrangerClassification": "Bool",
"identityVerificationComplete": "Bool",
"consecutiveFailedIdentityVerifications": "Int64",
"receivedPositiveFaceConfirmation": "Bool"
}
TrackingResult = {
"disappeared": [
"TrackedObject"
],
"updated": [
"TrackedObject"
],
"lingering": [
"TrackedObject"
],
"appeared": [
"TrackedObject"
],
"zombies": [
"TrackedObject"
],
"isSceneChange": "Bool",
"timestamp": "Timestamp"
}