Object Counting with SageMaker Object Detection
Labeling images with Ground Truth, training a SageMaker object detection model, and counting objects from the inference results.
This example covers the following workflow:
- Labeling images using Ground Truth
- Training and deploying an object detection model
- Performing inference
Images in this post are illustrative and not associated with specific customer projects.
The example uses a SageMaker inference endpoint to test the trained model.

Labeling with Ground Truth
Creating a Labeling Workforce
Create a private workforce for the labeling task. Team members can authenticate with Amazon Cognito or an OIDC identity provider.

After the workforce is created, each worker receives an invitation email containing the labeling portal URL.
You can also retrieve the labeling portal URL by navigating to Private workforce summary > Labeling portal sign-in URL in the SageMaker management console.

Workers must follow the instructions in the invitation email to sign up and access the labeling portal.

An example of the invitation email is as follows:
Hi,
You are invited by [email protected] from <COMPANY> to work on a labeling project.
Click on the link below to log into your labeling project."https://<LABELING_PORTAL_URL>"
You will need the following username and temporary password provided below to login for the first time.User name: <USER_NAME>Temporary password: <PASSWORD>
Once you log in with your temporary password, you will be required to create a new password for your account.After creating a new password, you can log into your private team to access your labeling project.
If you have any questions, please contact us at [email protected].After accessing the URL, workers must enter the username and temporary password from the invitation email.

They will then be prompted to change their temporary password to a new one.

After signing in, workers see their assigned jobs on the labeling portal home page.

Creating a Labeling Job
Return to the SageMaker console and create a labeling job. Enter the required settings and click Complete data setup.
After creating the labeling job, it cannot be deleted. Use a unique value, such as one generated with the following command: uuidgen | tr "[:upper:]" "[:lower:]".

For complex labeling tasks, consider specifying a longer value for the Task timeout parameter.

Starting Labeling
Sign in to the labeling portal, open the new labeling job, and click Start working.
It may take some time for the labeling job to appear in the list.

Follow the job instructions to draw bounding boxes and assign labels. The following image shows an example.

Once all workers have completed their tasks, stop the labeling job.

Checking Labeling Output
After the labeling job stops, Ground Truth writes its output to the configured S3 bucket. For this object detection task, the training data is in manifests/output/output.manifest. See the official documentation for the output structure.
annotation-tool/annotations/consolidated-annotation/worker-response/manifests/intermediate/output/output.manifesttemp/Ground Truth stores the labeling results as an Augmented Manifest. See the official documentation for its object detection fields.
Training with SageMaker
After labeling is complete, configure the SageMaker training job as follows:
- Job settings
- Job name: Use a unique value (e.g.,
uuidgen | tr "[:upper:]" "[:lower:]"). - Algorithm source: SageMaker built-in algorithm
- Choose an algorithm:
- Algorithm: Vision - Object Detection (MXNet)
- Input mode:
Pipe - Resource configuration
- Instance type: Use a GPU instance like
ml.p2.xlarge. - Only GPU instances support SageMaker object detection algorithms.
- Hyperparameters
num_classes: Set to the number of object classes (e.g.,1in this post).num_training_samples: Equal to the number of lines in the manifest file.- Input data configuration
- Training channel
- Channel name:
train - Input mode:
Pipe - Content type:
application/x-recordio - Record wrapper:
RecordIO - Data source: S3 (Augmented Manifest File)
- Attribute names: Include attributes like
source-refand bounding box data keys. - S3 location: Specify the S3 URI for the training data manifest file.
- Validation channel
- Channel name:
validation - Output data configuration
- S3 location: Specify the S3 URI for storing model artifacts.

With an Augmented Manifest, the object detection algorithm can use Pipe input mode and the RecordIO wrapper without a separately generated RecordIO file. See the official documentation for details.
Inference
Creating a Model from the Training Job
To create a model from the completed training job, click Create model in the SageMaker console.

Deploying the Model
After creating the model, click Create endpoint to deploy it. For infrequent inference that fits its resource limits, consider a serverless endpoint.

Making Requests
Locate the SageMaker Runtime endpoint on the endpoint detail page. For testing, invoke it with the AWS SDK or a SigV4-capable client such as Postman.
SageMaker real-time endpoints can serve production inference, but they require AWS authentication. Do not expose AWS credentials in an untrusted client; place an authenticated application or API layer in front of the endpoint when necessary. The direct Postman call below is for testing.

Example: Postman Configuration
Configure AWS Signature Version 4 authentication with the following values:
- AccessKey
- SecretKey
- Session Token: Use temporary credentials instead of long-term credentials.
- AWS Region: The region of your SageMaker endpoint.
- Service Name:
sagemaker

Set Accept: application/json and the image content type expected by the model, such as Content-Type: application/x-image.

Send the image as the binary request body.

Example: Using AWS SDK (boto3)
The following example invokes the endpoint with boto3 invoke_endpoint:
import json
import boto3
# Initialize SageMaker runtime clientruntime = boto3.client('sagemaker-runtime')
# Define endpoint and input detailsendpoint_name = '<YOUR_ENDPOINT_NAME>'content_type = 'application/x-image'payload = None
# Read the image file in binary modewith open('/path/to/image.jpg', 'rb') as f: payload = f.read()
# Invoke the endpointresponse = runtime.invoke_endpoint( EndpointName=endpoint_name, ContentType=content_type, Body=payload)
# Parse and display the responsebody = response['Body'].read()predictions = json.loads(body.decode())print(json.dumps(predictions, indent=2))
# Save the response to a filewith open('./response.json', 'w') as f: json.dump(predictions, f, indent=2)Checking Response
The JSON response contains the following values for each detection:
- Class Label IDs
- Confidence Scores
- Bounding Box Coordinates
The bounding box coordinates are relative to the actual image dimensions. For more details, refer to the official documentation.
{ "prediction": [ [ 0.0, 0.9953756332397461, 0.3821756839752197, 0.007661208510398865, 0.525381863117218, 0.19436971843242645 ], [ 0.0, 0.9928023219108582, 0.3435703217983246, 0.23781903088092804, 0.5533013343811035, 0.6385164260864258 ], [ 0.0, 0.9911478757858276, 0.15510153770446777,... 0.9990172982215881 ] ]}Visualizing Response
To interpret the results of the inference visually, you can use Jupyter Notebook along with matplotlib.
The following Python script overlays the detected bounding boxes and count labels on the input image.
import json
import matplotlib.patches as patchesimport matplotlib.pyplot as pltfrom PIL import Image
# Configure plotplt.figure()axes = plt.axes()
# Read an imageim = Image.open('/path/to/image.jpg')# Display the imageplt.imshow(im)
# Read SageMaker inference predictionswith open('response.json') as f: predictions = json.loads(f.read())['prediction']
# Set initial countcount = 0
# Create rectanglesfor prediction in predictions: score = prediction[1] if score < 0.2: continue
# Count up count += 1
x = prediction[2] * im.width y = prediction[3] * im.height width = prediction[4] * im.width - x height = prediction[5] * im.height - y
rect = patches.Rectangle((x, y), width, height, linewidth=1, edgecolor='r', facecolor='none') axes.annotate(count, (x + width / 2, y + height / 2), color='yellow', weight='bold', fontsize=18, ha='center', va='center') axes.add_patch(rect)
# Display the rectanglesplt.show()The script filters detections by confidence score, converts normalized coordinates to pixels, draws each bounding box, and assigns a sequential count label.

Conclusion
Ground Truth labels and the SageMaker built-in object detection algorithm produce normalized bounding boxes that can be filtered, counted, and visualized with matplotlib.
The score < 0.2 filter determines which detections contribute to the count. Tune this confidence threshold against validation data instead of choosing it from a single test image.
Count accuracy also depends on consistent Ground Truth annotations, especially for overlapping or partially visible objects. Review labeling guidelines and validation results before using the counts at scale.
Related posts
Calling SageMaker from API Gateway Without Lambda
Configure an API Gateway AWS service integration to invoke a SageMaker inference endpoint without a Lambda proxy.
Getting Started with Amazon SageMaker: Using Built-in Algorithms
Train and deploy a k-NN classifier on the Iris dataset using SageMaker Studio and built-in algorithms.
Sign in with Slack Using Cognito User Pools and OIDC
Federating Cognito user pools with Slack over OIDC and wiring "Sign in with Slack" into a Next.js app with Amplify.
Deploying FastAPI on AWS Lambda with Lambda Web Adapter
Containerizing a FastAPI backend and deploying it to a single Lambda function with Lambda Web Adapter and AWS CDK.
API Gateway WebSocket: Implementing a Mock Integration
Building an API Gateway WebSocket API entirely with mock integrations, returning canned responses with no backend Lambda involved.
