As someone who works on computer vision projects, I know how crucial properly annotated images are for training machine learning models. Accurate annotations are the backbone of object detection and classification tasks, especially when you’re working with models like YOLOv8. That’s why I decided to build the YOLOv8 Image Annotation Tool using AutoIt—a powerful scripting language that allows me to efficiently annotate images with both bounding boxes and polygons.
My goal was to make image annotation as simple, accurate, and user-friendly as possible. Whether you’re working with basic bounding boxes or more complex polygons, this tool is designed to streamline the process.
Why I Chose YOLOv8 and AutoIt
For object detection, I prefer using the YOLO (You Only Look Once) algorithm because of its balance between speed and accuracy. YOLOv8, in particular, brings enhanced performance when it comes to detecting small objects and working across multiple scales. But while YOLO excels at detection, the task of annotating images for training the model can be tedious.
That’s where my AutoIt-based YOLOv8 Image Annotation Tool comes in. I chose AutoIt for this project because it’s lightweight, easy to use, and offers great flexibility in creating GUI-based applications. AutoIt also allows me to leverage GDI+ for drawing annotations on the image, which makes for a seamless experience when labeling.
Key Features of My YOLOv8 Image Annotation Tool
Here’s what I’ve built into the tool to make it as functional and efficient as possible:
- Supports Both Bounding Boxes and Polygons:
- Whether you’re working with standard bounding boxes (like those used in YOLO) or more intricate polygon-based annotations, this tool can handle both. Bounding boxes are automatically scaled and positioned, while polygons allow for more precise object outlining.
- Automatic Image Scaling and Centering:
- One challenge I faced was maintaining image proportions during annotation, especially with high-resolution images. The tool automatically scales the image while preserving its aspect ratio, ensuring that your annotations are accurate regardless of screen resolution.
- Class Labels with Custom Colors:
- The tool automatically loads class names from a
classes.txt
file and assigns distinct colors to each class. This makes it easy to differentiate between the objects you’re annotating. The labels are displayed at the top center of the polygon, so they’re easy to see without cluttering the image.
- Annotation Redrawing on Window Resize:
- If the window is resized, the tool automatically recalculates and redraws the annotations to fit the new window size. This keeps everything in sync without having to manually readjust the annotations.
- Easy Image Navigation:
- I’ve made it simple to move through your dataset with ‘Previous’ and ‘Next’ buttons. You can quickly switch between images, allowing you to focus on the task at hand rather than fiddling with the interface.
- Customizable and Flexible:
- Since the tool is built in AutoIt, it’s easy to customize. Whether you want to adjust how the annotations are drawn or modify the GUI to suit your preferences, it’s all doable within the script.
How It Works
- Loading Images and Labels:
- You can select the image directory and the corresponding label directory (where the YOLOv8-format
.txt
files are stored). The tool reads the labels and automatically draws the bounding boxes or polygons based on the coordinates.
- Displaying and Annotating:
- Once the image is displayed, the tool dynamically resizes it based on the available window size. Annotations (bounding boxes or polygons) are drawn on the image, and class labels are displayed at the top center of the polygon.
- Class Label Colors:
- The class labels are loaded from a
classes.txt
file and are automatically assigned a unique color. This helps keep your annotations visually distinct and organized, especially when working with multiple classes.
- Resizing and Redrawing:
- If you resize the window, the tool recalculates the scaling factors and redraws both the image and the annotations. You won’t have to worry about things getting misaligned when working in different window sizes.
Introduction to the AutoIt Code
The backbone of the YOLOv8 Image Annotation Tool is written in AutoIt, a scripting language known for its simplicity and flexibility in building GUIs. The code leverages AutoIt’s GDI+ library for drawing and managing annotations, and the GUI framework to handle image navigation, scaling, and interaction. Below, you’ll find the latest version of the script, which you can customize to fit your own needs.
This script handles tasks like:
- Loading images and corresponding annotations in the YOLO format.
- Drawing bounding boxes and polygons on images.
- Managing resizing and redrawing to ensure everything stays aligned.
- Displaying class labels for each annotation with unique colors.
If you’re familiar with AutoIt, you’ll find the code easy to follow and modify. If you’re new to it, the structure is intuitive enough to quickly get up to speed.
Here’s the latest version of the script:
#include
#include
#include
#include
#include
#include
#include
Global $imageDir, $labelDir, $annotations, $classes, $classes_colors = []
Global $imageFiles, $currentIndex = 1
Global $originalImageWidth, $originalImageHeight, $scaleX, $scaleY
; Create the GUI (with resizing and maximizing options)
$mainGUI = GUICreate("YOLOv8 Annotation Tool", 1024, 768, -1, -1, BitOR($WS_SIZEBOX, $WS_MAXIMIZEBOX, $WS_SYSMENU, $WS_MINIMIZEBOX, $WS_CAPTION))
; Input fields
GUICtrlCreateLabel("Image Directory:", 10, 10)
GUICtrlSetResizing(-1, $GUI_DOCKAUTO)
$imageDirInput = GUICtrlCreateInput("C:\Users\Joe\Downloads\YOLO_Mask_Model\images\test", 120, 10, 700, 20)
GUICtrlSetResizing(-1, $GUI_DOCKAUTO)
GUICtrlCreateLabel("Label Directory:", 10, 40)
GUICtrlSetResizing(-1, $GUI_DOCKAUTO)
$labelDirInput = GUICtrlCreateInput("C:\Users\Joe\Downloads\YOLO_Mask_Model\labels\test", 120, 40, 700, 20)
GUICtrlSetResizing(-1, $GUI_DOCKAUTO)
; Browse buttons
$imageDirButton = GUICtrlCreateButton("Browse", 830, 10, 75, 20)
GUICtrlSetResizing(-1, $GUI_DOCKAUTO)
$labelDirButton = GUICtrlCreateButton("Browse", 830, 40, 75, 20)
GUICtrlSetResizing(-1, $GUI_DOCKAUTO)
; Load button
$loadButton = GUICtrlCreateButton("Load Annotations", 10, 70, 200, 30)
GUICtrlSetResizing(-1, $GUI_DOCKAUTO)
; Navigation buttons
$prevButton = GUICtrlCreateButton("Previous", 10, 710, 100, 30)
GUICtrlSetResizing(-1, $GUI_DOCKAUTO)
$nextButton = GUICtrlCreateButton("Next", 920, 710, 100, 30)
GUICtrlSetResizing(-1, $GUI_DOCKAUTO)
; Image display area, scalable (with the anchor set)
$picDisplay = GUICtrlCreatePic("", 10, 110, 1000, 600)
GUICtrlSetResizing($picDisplay, $GUI_DOCKHCENTER)
; Show the GUI
GUISetState(@SW_SHOW)
; Event Loop
While 1
$msg = GUIGetMsg()
Switch $msg
Case $GUI_EVENT_CLOSE
Exit
Case $GUI_EVENT_RESTORE
ConsoleWrite("RESTORE")
Sleep(10)
DisplayCurrentImage(0)
Case $GUI_EVENT_MAXIMIZE
ConsoleWrite("MAXIMIZE")
Sleep(10)
DisplayCurrentImage(0)
Case $GUI_EVENT_RESIZED
ConsoleWrite("RESIZED")
Sleep(10)
DisplayCurrentImage(0)
Case $imageDirButton
$imageDir = FileSelectFolder("Select Image Directory", "C:\Users\Joe\Downloads\YOLO_Mask_Model\images\test")
If Not @error Then GUICtrlSetData($imageDirInput, $imageDir)
Case $labelDirButton
$labelDir = FileSelectFolder("Select Label Directory", "C:\Users\Joe\Downloads\YOLO_Mask_Model\labels\test")
If Not @error Then GUICtrlSetData($labelDirInput, $labelDir)
Case $loadButton
$imageDir = GUICtrlRead($imageDirInput)
$labelDir = GUICtrlRead($labelDirInput)
If $imageDir = "" Or $labelDir = "" Then
MsgBox($MB_ICONERROR, "Error", "Please select both image and label directories.")
Else
LoadImages($imageDir, $labelDir)
EndIf
Case $prevButton
If $currentIndex > 1 Then
DisplayCurrentImage(-1)
EndIf
Case $nextButton
If $currentIndex < $imageFiles[0] Then
DisplayCurrentImage(1)
EndIf
EndSwitch
WEnd
Func AdjustImageDisplay($imageWidth, $imageHeight)
; Get the available space in the GUI for the image display
Local $winSize = WinGetClientSize($mainGUI)
Local $availableWidth = $winSize[0]
Local $availableHeight = $winSize[1] - 220 ; Adjust for top GUI elements (like buttons)
; Calculate the aspect ratio of the image
Local $imageAspectRatio = $imageWidth / $imageHeight
; Initialize new dimensions while maintaining the aspect ratio
Local $newWidth, $newHeight
; Adjust the dimensions to fit within the available space, maintaining aspect ratio
If ($availableWidth / $availableHeight) > $imageAspectRatio Then
; Available space is wider than the image's aspect ratio, so limit by height
$newHeight = $availableHeight
$newWidth = $newHeight * $imageAspectRatio
Else
; Available space is taller than the image's aspect ratio, so limit by width
$newWidth = $availableWidth
$newHeight = $newWidth / $imageAspectRatio
EndIf
; Calculate new position to center the image control horizontally and vertically
Local $xPos = ($availableWidth - $newWidth) / 2
Local $yPos = ($availableHeight - $newHeight) / 2 + 110
; Resize and reposition the image display control
GUICtrlSetPos($picDisplay, $xPos, $yPos, $newWidth, $newHeight)
EndFunc
Func LoadClasses($labelDir)
Local $classFile = $labelDir & "\classes.txt"
; Check if the classes.txt file exists in the label directory
If FileExists($classFile) Then
; Read the file into an array
Local $lines = []
_FileReadToArray($classFile, $lines)
; Check if the file was read successfully
If IsArray($lines) Then
;_ArrayDisplay($lines)
; Copy the lines to the global classes array
Global $classes = $lines
AssignClassColors()
ConsoleWrite("Classes loaded successfully: " & _ArrayToString($classes, ", ") & @CRLF)
Else
MsgBox($MB_ICONERROR, "Error", "Failed to read the classes.txt file.")
EndIf
Else
MsgBox($MB_ICONERROR, "Error", "classes.txt file not found in: " & $labelDir)
EndIf
EndFunc
Func LoadImages($imageDir, $labelDir)
; Load images
$labelDir = GUICtrlRead($labelDirInput)
$imageFiles = _FileListToArrayRec($imageDir, "*.jpg;*.png", 1)
_ArrayDelete($classes, 0)
LoadClasses($labelDir)
If @error Then
MsgBox($MB_ICONERROR, "Error", "No images found in directory: " & $imageDir)
Return
EndIf
; Set the current index to the first image
$currentIndex = 1
; Display the first image
DisplayCurrentImage(0)
EndFunc
Func DisplayCurrentImage($nextIndex)
If $imageFiles = "" Then Return
$currentIndex += $nextIndex
Local $filename = StringTrimLeft($imageFiles[$currentIndex], StringInStr($imageFiles[$currentIndex], "\", 0, -1))
; Replace the extension to match the label file
Local $labelFilename = StringReplace($filename, ".jpg", ".txt")
$labelFilename = StringReplace($labelFilename, ".png", ".txt")
Local $imagePath = $imageDir & "\" & $filename
Local $labelPath = $labelDir & "\" & $labelFilename
If FileExists($labelPath) Then
ConsoleWrite("EXISTS "&$labelPath&@crlf)
; Get the original image dimensions
Local $imageWidth = _GDIPlus_ImageGetWidth($imagePath)
Local $imageHeight = _GDIPlus_ImageGetHeight($imagePath)
AdjustImageDisplay($imageWidth, $imageHeight)
; Load and display the image
GUICtrlSetImage($picDisplay, $imagePath)
DisplayImageWithAnnotations($imagePath, $labelPath)
Else
MsgBox($MB_ICONERROR, "Error", "Label file not found: " & $labelFilename)
EndIf
EndFunc
Func DisplayImageWithAnnotations($imagePath, $labelPath)
; Clear previous annotations by deleting all elements
While UBound($annotations) > 0
_ArrayDelete($annotations, 0)
WEnd
_GDIPlus_Startup()
Local $hGraphic = _GDIPlus_GraphicsCreateFromHWND(GUICtrlGetHandle($picDisplay))
Local $hBitmap = _GDIPlus_BitmapCreateFromFile($imagePath)
; Get the image dimensions
$originalImageWidth = _GDIPlus_ImageGetWidth($hBitmap)
$originalImageHeight = _GDIPlus_ImageGetHeight($hBitmap)
; Get the dimensions of the displayed image area (accounting for GUI scaling)
Local $displayWidth = ControlGetPos($mainGUI, "", $picDisplay)[2]
Local $displayHeight = ControlGetPos($mainGUI, "", $picDisplay)[3]
; Calculate scaling factors for X and Y
$scaleX = $displayWidth / $originalImageWidth
$scaleY = $displayHeight / $originalImageHeight
; Draw the image
_GDIPlus_GraphicsDrawImage($hGraphic, $hBitmap, 0, 0)
sleep(50)
; Load and parse the annotation file
Local $lines = []
_FileReadToArray($labelPath, $lines)
If IsArray($lines) Then
For $i = 1 To $lines[0]
ConsoleWrite($lines[$i] & @CRLF)
; Ensure the line is not empty and $lines[$i] is within bounds
If IsString($lines[$i]) And StringStripWS($lines[$i], 3) <> "" Then
Local $elements = StringSplit($lines[$i], " ")
; Ensure $elements is an array and has enough elements
If IsArray($elements) And UBound($elements) >= 5 Then
; Handle bounding box (4 points) or polygon (more than 4 points)
If UBound($elements) > 6 Then
; Draw the polygon and label it
DrawPolygon($hGraphic, $elements)
LabelPolygon($hGraphic, $elements)
Else
; Draw the bounding box
DrawBoundingBox($hGraphic, $elements)
LabelPolygon($hGraphic, $elements)
EndIf
; Store annotation data
_ArrayAdd($annotations, $lines[$i])
EndIf
EndIf
Next
Else
;MsgBox($MB_ICONERROR, "Error", "Failed to read label file: " & $labelPath)
EndIf
; Cleanup
_GDIPlus_GraphicsDispose($hGraphic)
_GDIPlus_BitmapDispose($hBitmap)
_GDIPlus_Shutdown()
EndFunc
Func DrawPolygon($hGraphic, $elements)
Local $classIndex = $elements[1] ; Class index is the first element of the annotation
; Calculate the number of points in the polygon
Local $numPoints = (UBound($elements) - 2) / 2
; Declare the array to hold the polygon points as a 2D array
Local $points[$numPoints + 1][2] ; +1 to hold the number of vertices at index [0][0]
; The first element contains the number of vertices
$points[0][0] = $numPoints
; Convert normalized coordinates to scaled pixel coordinates
For $i = 0 To $numPoints - 1
$points[$i + 1][0] = Round($elements[2 + $i * 2] * $originalImageWidth * $scaleX) ; X coordinate
$points[$i + 1][1] = Round($elements[3 + $i * 2] * $originalImageHeight * $scaleY) ; Y coordinate
Next
; Create a pen for drawing
Local $hPen = _GDIPlus_PenCreate($classes_colors[$classIndex+1], 2)
; Draw the polygon using the 2D points array
_GDIPlus_GraphicsDrawPolygon($hGraphic, $points, $hPen)
; Clean up
_GDIPlus_PenDispose($hPen)
EndFunc
Func DrawBoundingBox($hGraphic, $elements)
; Scale the bounding box coordinates according to the display scaling
Local $classIndex = $elements[1] ; Class index is the first element of the annotation
; Convert normalized coordinates to pixel coordinates
Local $xCenter = $elements[2] * $originalImageWidth * $scaleX
Local $yCenter = $elements[3] * $originalImageHeight * $scaleY
Local $width = $elements[4] * $originalImageWidth * $scaleX
Local $height = $elements[5] * $originalImageHeight * $scaleY
; Calculate the top-left corner from center coordinates and width/height
Local $x = $xCenter - ($width / 2)
Local $y = $yCenter - ($height / 2)
; Create a pen for drawing, using the class color
Local $hPen = _GDIPlus_PenCreate($classes_colors[$classIndex + 1], 2)
; Draw the rectangle (bounding box)
_GDIPlus_GraphicsDrawRect($hGraphic, $x, $y, $width, $height, $hPen)
; Clean up
_GDIPlus_PenDispose($hPen)
EndFunc
Func LabelPolygon($hGraphic, $elements)
; Get the class index (first element) and calculate the top-center for the label
Local $classIndex = $elements[1] ; Class index is the first element of the annotation
Local $minX = $originalImageWidth * $scaleX
Local $minY = $originalImageHeight * $scaleY
Local $maxX = 0
Local $maxY = 0
Local $numPoints = (UBound($elements) - 2) / 2
; Find the min and max X coordinates and the minimum Y (top of the polygon)
For $i = 0 To $numPoints - 1
Local $x = $elements[2 + $i * 2] * $originalImageWidth * $scaleX
Local $y = $elements[3 + $i * 2] * $originalImageHeight * $scaleY
; Get the smallest Y (topmost point) and calculate the min/max X values
If $x < $minX Then $minX = $x
If $x > $maxX Then $maxX = $x
If $y < $minY Then $minY = $y
If $y > $maxY Then $maxY = $y
Next
; Calculate the horizontal center of the polygon
Local $centerX = ($minX + $maxX) / 2
; Draw the label (class index) at the top-center of the polygon
Local $hBrush = _GDIPlus_BrushCreateSolid($classes_colors[$classIndex + 1]) ; White color for text
Local $hFormat = _GDIPlus_StringFormatCreate()
Local $hFamily = _GDIPlus_FontFamilyCreate("Arial")
Local $hFont = _GDIPlus_FontCreate($hFamily, 16, 4) ; Font size 12, bold
Local $textRect = _GDIPlus_RectFCreate($minX - 35, $maxY - $minY + $minY, 100, 20) ; Draw the label centered horizontally
; Draw the text label at the top-center of the polygon
_GDIPlus_GraphicsDrawStringEx($hGraphic, $classes[$classIndex + 1], $hFont, $textRect, $hFormat, $hBrush)
; Clean up
_GDIPlus_BrushDispose($hBrush)
_GDIPlus_FontDispose($hFont)
_GDIPlus_StringFormatDispose($hFormat)
_GDIPlus_FontFamilyDispose($hFamily)
EndFunc
Func AssignClassColors()
Local $predefinedColors = [0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFFFFF00, 0xFFFF00FF, 0xFF00FFFF, 0xFFFFA500, 0xFF800080, 0xFF808080, 0xFF008080]
Local $numPredefined = UBound($predefinedColors)
; Loop through the classes array
For $i = 0 To UBound($classes) - 1
; Assign a predefined color if available, otherwise generate a random color
If $i < $numPredefined Then
_ArrayAdd($classes_colors, $predefinedColors[$i])
Else
; Generate a random color if predefined colors are exhausted
_ArrayAdd($classes_colors, RandomColor())
EndIf
Next
ConsoleWrite("Class colors assigned successfully: " & _ArrayToString($classes_colors, ", ") & @CRLF)
EndFunc
; Function to generate a random color in RGB format
Func RandomColor()
Local $r = Random(0, 255, 1)
Local $g = Random(0, 255, 1)
Local $b = Random(0, 255, 1)
Return ($r * 0x10000 + $g * 0x100 + $b)
EndFunc
Join the Conversation
If you're interested in trying out the tool or have feedback to share, I'd love to hear from you! I’ve posted the project on the AutoIt Script Forum, where I’m also open to suggestions and feature requests.
You can check out the forum post and get involved in the discussion here:
YOLOv8 Image Annotation Tool - AutoIt Script Forum
Feel free to ask questions, report bugs, or suggest features you'd like to see added. I’m always looking for ways to improve the tool, and the feedback from the community is invaluable.
Conclusion
I created this YOLOv8 Image Annotation Tool to simplify the often tedious process of image labeling for object detection projects. Whether you're dealing with simple bounding boxes or more detailed polygons, the tool is built to help you get the job done faster and more accurately. Plus, the customization options in AutoIt mean that you can tweak it to fit your workflow perfectly.
Give it a try and let me know what you think. And don’t forget to join the conversation on the AutoIt forum—together, we can make this tool even better!
Happy annotating!