Views
No views yet
Visual Question Answering for Autonomous Driving Dmytro Khursenko, Czech Technical University in Prague, Faculty of Electrical Engineering, 2026. Supervised by Ing. David Hurych, Ph.D. (Valeo) and doc. Georgios Tolias, Ph.D. (CTU FEE).
📄 Thesis PDF expected by end of June 2026, following successful defense (CTU FEE).
This checkpoint contains fully merged weights — the LoRA adapter is already merged into the base model. Load and use it directly withAutoModel.
| Model | HF repo | Final |
|---|---|---|
| LoRA-25k | dkhursen/InternVL2-2b-LoRA-25k-drivelm | 0.560 |
| LoRA-300k (this model) | dkhursen/InternVL2-2b-LoRA-300k-drivelm | 0.493 |
| LoRA-25k + DL-PL 10% ⭐ | dkhursen/InternVL2-2b-LoRA-25k_plus_DL-PL-10pct | 0.589 |
| LoRA-25k + Oracle annotation | dkhursen/InternVL2-2b-LoRA-25k-drivelm-offline-redcircle-ctag-bkgd | 0.775 |
| Metric | Score |
|---|---|
| Final | 0.493 |
| Accuracy | 0.339 |
| ChatGPT | 0.706 |
| Language | 0.412 |
| BLEU-1 | 0.607 |
| BLEU-2 | 0.552 |
| BLEU-3 | 0.501 |
| BLEU-4 | 0.452 |
| ROUGE-L | 0.676 |
| CIDEr | 0.323 |
| Match | 0.303 |
| Coord | 0.006 |
Score definitions (all table values normalised to [0, 1])
- Accuracy — exact-match on MCQ (A/B/C/D) and Yes/No questions; strict letter-only format required
- Language — mean of BLEU-1–4, ROUGE-L, and CIDEr/10
- ChatGPT — GPT-3.5-turbo semantic similarity judge (0–100 scale, ÷100 in Final)
- Match —
(F1_coord × 100 + GPT_match) / 2on prediction answers (0–100 scale, ÷100 in Final); blends spatial coordinate F1 with a GPT judge- Coord — pure coordinate token F1 at L1 < 16 px; diagnostic only, not included in Final
- Final —
0.4 × (GPT/100) + 0.2 × Language + 0.2 × (Match/100) + 0.2 × AccuracyFull metric breakdown: evaluation/README.md
Note on evaluation scope. All scores are measured on the custom i.i.d. local test split (3,340 QA pairs) using the local DriveLM evaluation script. The official DriveLM evaluation server was not used as the primary reporting source: it returns only aggregate scores without per-metric breakdown, the infrastructure was intermittently unreliable (model output parsing errors could not be diagnosed directly — only via GitHub issues), and ChatGPT/Match metrics depend on OpenAI API calls that fail when the API quota for the billing period is exhausted.
| Model | Final | Acc | ChatGPT | Lang | B1 | B2 | B3 | B4 | RL | CIDEr | Match | Coord |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Mini-DA† | 0.606 | 0.898 | 0.668 | 0.416 | 0.596 | 0.564 | 0.533 | 0.503 | 0.651 | 0.470 | 0.381 | 0.000 |
| LoRA-25k | 0.560 | 0.826 | 0.589 | 0.459 | 0.732 | 0.668 | 0.606 | 0.547 | 0.714 | 0.230 | 0.338 | 0.015 |
| LoRA-25k + DL-PL 10% | 0.589 | 0.836 | 0.676 | 0.451 | 0.719 | 0.654 | 0.589 | 0.525 | 0.710 | 0.222 | 0.304 | 0.013 |
| LoRA-25k + DL-PL 30% | 0.548 | 0.832 | 0.605 | 0.434 | 0.695 | 0.625 | 0.554 | 0.483 | 0.692 | 0.201 | 0.264 | 0.008 |
| LoRA-25k + DL-PL 50% | 0.532 | 0.832 | 0.584 | 0.433 | 0.691 | 0.622 | 0.551 | 0.481 | 0.699 | 0.171 | 0.230 | 0.007 |
| LoRA-25k + DL-PL 100% | 0.511 | 0.805 | 0.544 | 0.430 | 0.687 | 0.616 | 0.543 | 0.470 | 0.695 | 0.165 | 0.232 | 0.007 |
| LoRA-300k (this model) | 0.493 | 0.339 | 0.706 | 0.412 | 0.607 | 0.552 | 0.501 | 0.452 | 0.676 | 0.323 | 0.303 | 0.006 |
| Parameter | Value |
|---|---|
| Base model | OpenGVLab/InternVL2-2B |
| Fine-tuning method | LoRA (merged into weights in this checkpoint) |
| LoRA rank | 16 |
| LoRA alpha | 32 |
| LoRA dropout | 0.05 |
| LoRA target modules | all linear layers in the LLM (backbone and MLP frozen) |
| Learning rate | 4e-5 (cosine, warmup 0.03) |
| Effective batch size | 64 (8 GPU × 4 × grad-acc 2) |
| Epochs | 10 |
| Max sequence length | 8192 |
| Precision | bfloat16 |
| Hardware | 8 × NVIDIA A100 40 GB |
configs/finetune/internvl2_2b_lora.yaml1import torch
2from PIL import Image
3from transformers import AutoTokenizer, AutoModel
4import torchvision.transforms as T
5from torchvision.transforms.functional import InterpolationMode
6
7MODEL_ID = "dkhursen/InternVL2-2b-LoRA-300k-drivelm"
8IMAGE_PATH = "path/to/stitched_6camera.jpg"
9QUESTION = "<image>\nWhat are the important objects in the current scene? Those objects will be considered for the future reasoning and driving decision."
10
11IMAGENET_MEAN = (0.485, 0.456, 0.406)
12IMAGENET_STD = (0.229, 0.224, 0.225)
13
14
15def build_transform(input_size):
16 return T.Compose([
17 T.Lambda(lambda img: img.convert("RGB") if img.mode != "RGB" else img),
18 T.Resize((input_size, input_size), interpolation=InterpolationMode.BICUBIC),
19 T.ToTensor(),
20 T.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),
21 ])
22
23
24def find_closest_aspect_ratio(aspect_ratio, target_ratios, width, height, image_size):
25 best_ratio_diff = float("inf")
26 best_ratio = (1, 1)
27 area = width * height
28 for ratio in target_ratios:
29 ratio_diff = abs(aspect_ratio - ratio[0] / ratio[1])
30 if ratio_diff < best_ratio_diff:
31 best_ratio_diff = ratio_diff
32 best_ratio = ratio
33 elif ratio_diff == best_ratio_diff:
34 if area > 0.5 * image_size * image_size * ratio[0] * ratio[1]:
35 best_ratio = ratio
36 return best_ratio
37
38
39def dynamic_preprocess(image, min_num=1, max_num=12, image_size=448, use_thumbnail=False):
40 orig_width, orig_height = image.size
41 aspect_ratio = orig_width / orig_height
42 target_ratios = sorted(
43 {(i, j)
44 for n in range(min_num, max_num + 1)
45 for i in range(1, n + 1)
46 for j in range(1, n + 1)
47 if min_num <= i * j <= max_num},
48 key=lambda x: x[0] * x[1],
49 )
50 best_ratio = find_closest_aspect_ratio(
51 aspect_ratio, target_ratios, orig_width, orig_height, image_size
52 )
53 target_w = image_size * best_ratio[0]
54 target_h = image_size * best_ratio[1]
55 cols = best_ratio[0]
56 resized = image.resize((target_w, target_h))
57 tiles = []
58 for i in range(best_ratio[0] * best_ratio[1]):
59 col = i % cols
60 row = i // cols
61 box = (col * image_size, row * image_size,
62 (col + 1) * image_size, (row + 1) * image_size)
63 tiles.append(resized.crop(box))
64 if use_thumbnail and len(tiles) != 1:
65 tiles.append(image.resize((image_size, image_size)))
66 return tiles
67
68
69def load_image(image_path, input_size=448, max_num=12):
70 image = Image.open(image_path).convert("RGB")
71 transform = build_transform(input_size)
72 tiles = dynamic_preprocess(image, image_size=input_size, use_thumbnail=True, max_num=max_num)
73 return torch.stack([transform(tile) for tile in tiles])
74
75
76tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True, use_fast=False)
77model = AutoModel.from_pretrained(
78 MODEL_ID,
79 torch_dtype=torch.bfloat16,
80 low_cpu_mem_usage=True,
81 trust_remote_code=True,
82).eval().cuda()
83
84pixel_values = load_image(IMAGE_PATH, max_num=12).to(torch.bfloat16).cuda()
85generation_config = {"max_new_tokens": 512, "do_sample": False}
86
87response = model.chat(tokenizer, pixel_values, QUESTION, generation_config)
88print("Answer:", response)1git clone https://github.com/dmitrykhursen/VQA-AD-CTU
2cd VQA-AD-CTU
3bash scripts/setup_env.sh && source vqa-ad-ctu-env/bin/activate
4
5# Run inference (edit MODEL= inside the script)
6sbatch scripts/05_inference.sh
7
8# Evaluate
9bash scripts/06_evaluate.sh inference/outputs/<MODEL>/local_test.json| Resource | Link |
|---|---|
| Code & scripts | github.com/dmitrykhursen/VQA-AD-CTU |
| Demo gallery | dmitrykhursen.github.io/VQA-AD-CTU |
| Pseudo-label dataset | dkhursen/drivelm-pseudo-labels |
| Best checkpoint | dkhursen/InternVL2-2b-LoRA-25k_plus_DL-PL-10pct |
| Base model | OpenGVLab/InternVL2-2B |
| DriveLM benchmark | github.com/OpenDriveLab/DriveLM |
| nuScenes dataset | nuscenes.org |
1@mastersthesis{khursenko2026vqa,
2 author = {Khursenko, Dmytro},
3 title = {Visual Question Answering for Autonomous Driving},
4 school = {Czech Technical University in Prague, Faculty of Electrical Engineering},
5 year = {2026},
6 supervisor = {Hurych, David and Tolias, Georgios}
7}1@inproceedings{sima2024drivelm,
2 title = {DriveLM: Driving with Graph Visual Question Answering},
3 author = {Sima, Chonghao and Renz, Katrin and Chitta, Kashyap and others},
4 booktitle = {European Conference on Computer Vision (ECCV)},
5 year = {2024}
6}