Getting Started with NVIDIA LocateAnything


Getting Started with NVIDIA LocateAnything

For the last few years, VLMs (Vision Language Models) have become more powerful at grounding tasks. These include object detection, pointing, and OCR. However, one issue remains. NTP (Next Token Prediction) is suboptimal for predicting the coordinates for a single bounding box or point coordinate. Predicting the numbers for a single object (bounded by a box), which is one atomic unit, token by token, is slow and a practical bottleneck during inference. This is where the latest LocateAnything model by NVIDIA comes in. It introduces a new PBD (Parallel Box Decoding), which decodes a single bounding box in a single step.

Video object detection using LocateAnything.
Figure 1. Video object detection using LocateAnything.

In this article, we will cover an introduction to the LocateAnything model. Along with a brief of the important components of the paper, we will also create a simple Gradio application. This application will include the following experiments using the LocateAnything model:

  • Object detection
  • Text detection and OCR
  • Object Pointing
  • Object detection in videos

Let’s jump into the article.

NVIDIA LocateAnything – A Single Model for Detection, Pointing, OCR, and GUI Grounding

The LocateAnything model was released by researchers from NVIDIA in the paper – LocateAnything: Fast and High-Quality Vision-Language Grounding with Parallel Box Decoding.

In simple words, it is a vision language model that is capable of object detection, object pointing, OCR, and GUI grounding.

LocateAnything tasks - the model is capable of object detection, text detection & OCR, GUI grounding, and pointing.
Figure 2. LocateAnything tasks – the model is capable of object detection, text detection & OCR, GUI grounding, and pointing.

But the story does not end there. There are a lot of practical motivations behind building this mode.

Motivation Behind LocateAnything

The first motivation comes from the modeling side. Traditionally, VLMs break down the object detection task as a coordinate token generation problem. They serialize a 2D coordinate, which is of course a number, into multiple 1D tokens. Instead of regressing numbers as we have in object detection heads, it becomes a token prediction problem. Two issues arise here: first, the mismatch in the coupled structure of the box geometry; second, it is extremely slow.

The second motivation is more practical and caters to the real-world. We are now using VLMs for robotics, understanding navigation, and predicting the next behavior. This asks for a much faster detection and grounding model for real-world scenarios. And token-by-token prediction is a limitation here. That’s where LocateAnything’s PBD (Parallel Box Decoding) comes into the picture.

To address the above issues, the paper proposes two solutions:

  • LocateAnything: A unified framework for VLM-based visual detection and grounding.
  • Parallel Box Decoding: Treating each bounding box (or point) as one atomic unit, learning to predict the coordinate set (x1, y1, x2, y2) in one parallel step.

Locate Anything Architecture and Inference Methodology

In this section, we will summarize the primary concepts from Sections 1, 2, and 3 of the paper, which cover:

  • The LocateAnything Architecture
  • Problem formulation
  • Inference methodology

LocateAnything Architecture

LocateAnything is built upon:

An MLP projector bridges the image encoder and the language decoder.

LocateAnything architecture and output structure.
Figure 3. LocateAnything architecture and output structure.

The choice for MoonViT is crucial here, as it maintains the aspect ratio of images during the creation of patch embeddings.

In the above architecture diagram, the output structure is of special interest here. It answers the question of “how does the model generate output boxes?

The model does not predict the bounding box coordinates token-by-token (which we call NTP or Next Token Prediction). Instead, it outputs the complete box in a single parallel step. This is facilitated by PDB, where the model treats each bounding box coordinate block, normalized between [0, 1000], as a single atomic unit. A single atomic unit (or block as referred in the paper) consists of 6 elements. These include the four coordinates and two structural tokens (). These tokens enclose the coordinates of a predicted object during inference. The code block below is an example.

dog<0><639><306><795><105><456><408><751><505><488><1000><775>window<0><153><275><382><552><165><936><381>carNone<|im_end|>

If we observe the above block, the model detects two classes of objects: dog and window. The output block format separates each semantic block (seen in Figure 3) with the tokens. And it separates different objects within the same class with tokens. When no objects are present belonging to a class, the model outputs None within the box tokens. And finally, we have an |im_end| token.

Summarizing the above output structure, with PBD, we have four functional blocks as output:

  • Semantic block enclosing the class names
  • Box block enclosing the coordinates
  • Negative block when no objects are present for a given class
  • And an End block

LocateAnything Inference Modes

During inference, the NVIDIA LocateAnything model supports three different prediction modes to balance between speed and accuracy.

Different decoding techniques used by LocateAnything.
Figure 4. Different decoding techniques used by LocateAnything.
  • Fast Mode: This is an MTP (Multi-Token Prediction) mode, which predicts full boxes in parallel. This provides maximum throughput and is best suited for on-device inference, such as robotics and embodied agents.
  • Slow Mode: This is the traditional NTP (Next Token Prediction) mode. This is specifically built for predicting tokens autoregressively for maximum stability. It is best suited for high-precision tasks such as data labeling and offline evaluation.
  • Hybrid Mode: Hybrid mode is a combination of the above two and uses Fast Mode by default. When the MTP decoding is unreliable or erroneous (which can happen due to malformed box predictions), then it falls back to the Slow Mode. This provides a good balance between speed and accuracy.

We will stop our discussion of the paper here. However, I recommend going through the paper at least once to understand the architecture and evaluations in more detail.

Inference using LocateAnything

In this section, we will cover inference with LocateAnything.

Project Directory Structure

Let’s take a look at the project directory structure.

├── input
│   ├── image_1.jpg
│   ├── image_2 (Copy).jpg
│   ├── image_2.jpg
│   └── video_1.mp4
├── app.py
├── inference.py
└── requirements.txt
  • We have two Python scripts, app.py and inference.py. The former contains the Gradio frontend code, and the latter contains the LocateAnything model inference logic.
  • The input directory contains a few images and videos that we will use for experiments.
  • We also have a requirements file for streamlined setup.

All the source code and files are available in zip format for download along with this article.

Download Code

Installing Requirements

We can install all the necessary dependencies via the requirements file.

pip install -r requirements.txt

This is all the setup we need.

LocateAnything Inference Logic

We will skip most of the Gradio and object annotation code here. We will take a look at the core inference class, which is part of the inference.py script. In the inference class, we support the following workflows:

  • Object detection
  • Object pointing
  • Text detection and OCR
  • GUI grounding
  • Object detection in videos

The following code block shows the entirety of LocateAnythingWorker.

class LocateAnythingWorker:
    """Stateful worker that loads the model once and serves perception queries."""

    def __init__(self, model_path: str, device: str = "cuda", dtype=torch.bfloat16):
        self.device = device
        self.dtype = dtype

        self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
        self.processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True)
        self.model = AutoModel.from_pretrained(
            model_path,
            torch_dtype=dtype,
            trust_remote_code=True,
        ).to(device).eval()

    @torch.no_grad()
    def predict(
        self,
        image: Image.Image,
        question: str,
        generation_mode: str = "hybrid",   # "fast" (MTP) | "slow" (NTP/AR) | "hybrid"
        max_new_tokens: int = 2048,
        temperature: float = 0.7,
        verbose: bool = True,
    ) -> dict:
        messages = [
            {"role": "user", "content": [
                {"type": "image", "image": image},
                {"type": "text", "text": question},
            ]}
        ]

        text = self.processor.py_apply_chat_template(
            messages, tokenize=False, add_generation_prompt=True
        )
        images, videos = self.processor.process_vision_info(messages)
        inputs = self.processor(
            text=[text], images=images, videos=videos, return_tensors="pt"
        ).to(self.device)

        pixel_values = inputs["pixel_values"].to(self.dtype)
        input_ids = inputs["input_ids"]
        image_grid_hws = inputs.get("image_grid_hws", None)

        response = self.model.generate(
            pixel_values=pixel_values,
            input_ids=input_ids,
            attention_mask=inputs["attention_mask"],
            image_grid_hws=image_grid_hws,
            tokenizer=self.tokenizer,
            max_new_tokens=max_new_tokens,
            use_cache=True,
            generation_mode=generation_mode,
            temperature=temperature,
            do_sample=True,
            top_p=0.9,
            repetition_penalty=1.1,
            verbose=verbose,
        )

        result = {"answer": response[0] if isinstance(response, tuple) else response}
        if isinstance(response, tuple) and len(response) >= 3:
            result["history"] = response[1]
            result["stats"] = response[2]
        return result

    # Convenience methods for each task

    def detect(self, image: Image.Image, categories: list[str], **kwargs) -> dict:
        """Object detection / document layout analysis."""
        cats = "".join(categories)
        prompt = f"Locate all the instances that matches the following description: {cats}."
        return self.predict(image, prompt, **kwargs)

    def ground_single(self, image: Image.Image, phrase: str, **kwargs) -> dict:
        """Phrase grounding — single instance."""
        prompt = f"Locate a single instance that matches the following description: {phrase}."
        return self.predict(image, prompt, **kwargs)

    def ground_multi(self, image: Image.Image, phrase: str, **kwargs) -> dict:
        """Phrase grounding — multiple instances."""
        prompt = f"Locate all the instances that match the following description: {phrase}."
        return self.predict(image, prompt, **kwargs)

    def ground_text(self, image: Image.Image, phrase: str, **kwargs) -> dict:
        """Text grounding."""
        prompt = f"Please locate the text referred as {phrase}."
        return self.predict(image, prompt, **kwargs)

    def detect_text(self, image: Image.Image, **kwargs) -> dict:
        """Scene text detection."""
        prompt = "Detect all the text in box format."
        return self.predict(image, prompt, **kwargs)

    def ground_gui(self, image: Image.Image, phrase: str, output_type: str = "box", **kwargs) -> dict:
        """GUI grounding (box or point)."""
        if output_type == "point":
            prompt = f"Point to: {phrase}."
        else:
            prompt = f"Locate the region that matches the following description: {phrase}."
        return self.predict(image, prompt, **kwargs)

    def point(self, image: Image.Image, phrase: str, **kwargs) -> dict:
        """Pointing."""
        prompt = f"Point to: {phrase}."
        return self.predict(image, prompt, **kwargs)

    # Utility: parse model output

    @staticmethod
    def parse_boxes(answer: str, image_width: int, image_height: int) -> list[dict]:
        """Parse model output into pixel-coordinate bounding boxes.

        Coordinates in model output are normalized integers in [0, 1000].
        """
        boxes = []
        for m in re.finditer(r"<(\d+)><(\d+)><(\d+)><(\d+)>", answer):
            x1, y1, x2, y2 = [int(g) for g in m.groups()]
            boxes.append({
                "x1": x1 / 1000 * image_width,
                "y1": y1 / 1000 * image_height,
                "x2": x2 / 1000 * image_width,
                "y2": y2 / 1000 * image_height,
            })
        return boxes

    @staticmethod
    def parse_points(answer: str, image_width: int, image_height: int) -> list[dict]:
        """Parse model output into pixel-coordinate points."""
        points = []
        for m in re.finditer(r"<(\d+)><(\d+)>", answer):
            x, y = int(m.group(1)), int(m.group(2))
            points.append({
                "x": x / 1000 * image_width,
                "y": y / 1000 * image_height,
            })
        return points

    @staticmethod
    def parse_text_boxes(answer: str, image_width: int, image_height: int) -> list[dict]:
        """Parse model output into text boxes with associated text.
        
        Handles cases where one  label is followed by multiple  elements.
        Each box is associated with the most recent  that came before it.
        Format: text content...
        """
        text_boxes = []
        
        # Find all ref tags and their end positions
        refs = [(m.group(1), m.end()) for m in re.finditer(r"([^<]*)", answer)]
        
        # Find all box tags with their coordinates and start positions
        boxes = [(m.group(1), m.group(2), m.group(3), m.group(4), m.start())
                 for m in re.finditer(r"<(\d+)><(\d+)><(\d+)><(\d+)>", answer)]
        
        for box_coords in boxes:
            x1_str, y1_str, x2_str, y2_str, box_pos = box_coords
            
            # Find the most recent ref that came before this box
            current_label = ""
            for ref_text, ref_end_pos in reversed(refs):
                if ref_end_pos <= box_pos:
                    current_label = ref_text
                    break
            
            x1, y1, x2, y2 = [int(g) for g in [x1_str, y1_str, x2_str, y2_str]]
            text_boxes.append({
                "text": current_label,
                "x1": x1 / 1000 * image_width,
                "y1": y1 / 1000 * image_height,
                "x2": x2 / 1000 * image_width,
                "y2": y2 / 1000 * image_height,
            })
        
        return text_boxes

    def detect_video(
        self,
        video_path: str,
        categories: list[str],
        output_path: str,
        generation_mode: str = "hybrid",
        target_fps: int = None,
        max_seconds: int = None,
        **kwargs
    ) -> dict:
        """Run detection on a video file.
        
        Args:
            video_path: Path to input video file
            categories: List of category strings to detect
            output_path: Path to save output video
            generation_mode: Generation mode for inference
            target_fps: Target FPS to sample (None = process all frames)
            max_seconds: Maximum seconds of video to process (None = process all)
            **kwargs: Additional arguments passed to predict()
        
        Returns:
            Dict with stats about the video processing
        """
        import gc
        
        # Read video
        cap = cv2.VideoCapture(video_path)
        if not cap.isOpened():
            raise ValueError(f"Failed to open video file: {video_path}")
        
        fps = cap.get(cv2.CAP_PROP_FPS)
        vid_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        vid_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        
        # Read all frames
        all_frames = []
        while cap.isOpened():
            ret, frame = cap.read()
            if not ret:
                break
            all_frames.append(frame)
        cap.release()
        
        total_frames = len(all_frames)
        if total_frames == 0:
            raise ValueError("Failed to read any frames from the video.")
        
        # Calculate max frames based on max_seconds
        if max_seconds is not None and max_seconds > 0:
            max_frames_limit = int(max_seconds * fps)
            all_frames = all_frames[:max_frames_limit]
            total_frames = len(all_frames)
        
        # Sample frames based on target_fps
        if target_fps is not None and target_fps > 0 and fps > target_fps:
            # Calculate sampling interval
            sample_interval = int(fps / target_fps)
            sampled_indices = list(range(0, total_frames, sample_interval))
            frames_to_process = [all_frames[i] for i in sampled_indices]
        else:
            # Process all frames
            sampled_indices = list(range(total_frames))
            frames_to_process = all_frames
        
        category_str = "".join(categories)
        
        # Run inference on sampled frames
        inference_results = []
        processed_count = 0
        
        for i, frame in enumerate(frames_to_process):
            # Convert BGR to RGB for PIL
            pil_img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            
            # Run inference
            result = self.detect(pil_img, categories, generation_mode=generation_mode, verbose=False, **kwargs)
            output_text = result["answer"]
            
            inference_results.append(output_text)
            processed_count += 1
            
            # Clean up GPU memory
            if torch.cuda.is_available():
                torch.cuda.empty_cache()
            gc.collect()
        
        # Draw detections and create output video
        # Calculate output FPS based on sampling
        if target_fps is not None and target_fps > 0 and fps > target_fps:
            out_fps = target_fps
        else:
            out_fps = fps
        
        fourcc = cv2.VideoWriter_fourcc(*"mp4v")
        out = cv2.VideoWriter(output_path, fourcc, out_fps, (vid_w, vid_h))
        
        detections_summary = []
        
        for i, (frame, output_text) in enumerate(zip(frames_to_process, inference_results)):
            # Parse detections
            detections = parse_mixed_results(output_text, category_str)
            valid_results = postprocess_detections(detections, vid_w, vid_h)
            
            # Draw on frame
            frame_to_draw = draw_on_frame(frame, valid_results, draw_label=True)
            out.write(frame_to_draw)
            
            # Collect summary
            for det in valid_results:
                detections_summary.append({
                    "frame": sampled_indices[i] + 1,
                    "label": det.get("label", "object"),
                    "type": det.get("type", "box"),
                    "coords": det.get("coords", [])
                })
        
        out.release()
        
        stats = {
            "total_frames": total_frames,
            "sampled_frames": len(sampled_indices),
            "processed_frames": processed_count,
            "original_fps": fps,
            "target_fps": target_fps if target_fps else fps,
            "max_seconds": max_seconds,
            "processed_seconds": total_frames / fps if fps > 0 else 0,
            "output_fps": out_fps,
            "detections_count": len(detections_summary)
        }
        
        return stats

We have a few things going on in the above code block.

  • The very first thing we do in the __init__ method is loading the model and the tokenizer.
  • The predict method works as the global inference function, accepting all types of inference requests, be it detection, pointing, or GUI grounding.
  • Then we have different convenience methods for different tasks, including object detection, pointing, GUI grounding, text detection, and phrase grounding. In phrase grounding, we detect a single text in a document or image that matches the user input.
  • There are utility methods for parsing the bounding boxes, points, and text boxes as well.
  • One of the more complex workflows is the video detection method. Here, we extract all the frames from the video and carry out inference frame-by-frame. As video detection can be time-consuming, we have an option to choose the FPS and the number of seconds we want to run the detection.

This is most of the core logic we have. Let’s see some results.

LocateAnything Inference Results

All the inference experiments were carried out on an RTX 3080 10GB GPU.

We can run the application by executing the following command.

python app.py

The following figure shows what the Gradio app GUI looks like.

Along with the detection (or any other task) annotation, we also show the raw outputs.

The Gradio UI that we will use for LocateAnything inference.
Figure 5. The Gradio UI that we will use for LocateAnything inference.

LocateAnything Object Detection

Let’s take a look at multi-object detection results.

Multi-class object detection with LocateAnything.
Figure 6. Multi-class object detection with LocateAnything.

We asked the model to detect persons and backpacks in the above image. What’s impressive is that the model is capable of detecting the person when most of their body is hidden in the frame. It can detect the backpack on one of the people accurately. However, we can also see false positives for backpacks in the top-right corner. It might so happen that using high-resolution images solves the issue.

LocateAnything Pointing

Let’s try out object pointing now.

Dense object pointing using LocateAnything.
Figure 7. Dense object pointing using LocateAnything.

Here we ask the model to point to all the birds. It was able to point to most of them. However, it missed those that are partially occluded and the ones that are out of focus at the back.

LocateAnything Text Detection and OCR

Let’s carry out text detection and OCR now.

We used a receipt from the SROIE v2 data for text detection and OCR here. The following is the result.

Text detection and OCR using LocateAnything.
Figure 8. Text detection and OCR using LocateAnything.

The results good but not great. Although all the text detection happened accurately, the OCR is not very accurate.

LocateAnything Video Object Detection

Video object detection with LocateAnything.

In the above video, we prompt the model to detect cars. We set the FPS to 0, which accounts for the original FPS of the video, and we carry out detection on 5 seconds of the video. Being a general object detection model, the model performs quite well here. One thing to note here is that we do not get real-time detection on videos with an RTX 3080 GPU. To address that, much more powerful cloud GPUs might be a necessity.

Summary and Conclusion

We covered a brief introduction to the NVIDIA LocateAnything model in this article. We started with a discussion of the important components of the paper and then moved to the inference. The inference results revealed the general strengths and weaknesses of the model. I hope this article was worth your time.

If you have any questions, thoughts, or suggestions, please leave them in the comment section. I will surely address them.

You can contact me using the Contact section. You can also find me on LinkedIn, and X.

References

Liked it? Take a second to support Sovit Ranjan Rath on Patreon!
Become a patron at Patreon!

Leave a Reply

Your email address will not be published. Required fields are marked *