Hmm… I suspect asking in the LeRobot Discord would be more reliable.
For now, seems possible, but looks like several known failure modes may be overlapping:
Yes, using a USB webcam through WSL should be possible in principle. However, your log does not yet show a successful video stream. It shows that /dev/video0 can be discovered and at least partly queried, but every frame read returns status=False, followed by a 1000 ms timeout.
The issue you found, backend=ANY failing while backend=V4L2 works, is therefore quite relevant. There is also a very similar LeRobot forum report with:
- an SO-101 setup,
lerobot-find-cameras,
Backend api: FFMPEG,
- the same
VIDIOC_QBUF and VIDIOC_G_INPUT messages,
- successful camera use outside LeRobot,
- and successful LeRobot capture after explicitly selecting V4L2.
That case was native Ubuntu rather than WSL, though, so I would not assume it has exactly the same root cause. WSL adds an independent USB/IP and UVC layer.
The quickest route is to split the problem into layers before changing LeRobot itself:
Can V4L2 read real frames directly?
├─ No
│ └─ Investigate WSL / usbipd / UVC / device node / camera mode
└─ Yes
└─ Compare OpenCV CAP_ANY with CAP_V4L2
├─ Only CAP_V4L2 works
│ └─ OpenCV backend selection is the leading candidate
├─ Both work
│ └─ Investigate LeRobot version / finder lifecycle / warm-up
└─ Neither works
└─ Investigate the OpenCV build, package conflicts, or device contention
1. First identify the actual capture node and its supported modes
Install the V4L2 tools if necessary:
sudo apt update
sudo apt install v4l-utils
Then inspect every node rather than assuming /dev/video0 is necessarily the usable image stream:
v4l2-ctl --list-devices
for dev in /dev/video*; do
echo "=== $dev ==="
v4l2-ctl -d "$dev" --all
v4l2-ctl -d "$dev" --list-formats-ext
done
For the node you test, look for:
Video Capture or Video Capture Multiplanar
Streaming
- an advertised combination of pixel format, resolution, and frame rate
One physical UVC webcam can expose multiple /dev/video* nodes. Some may be image-capture nodes, while others can be metadata or other logical interfaces. The kernel’s VIDIOC_QUERYCAP documentation explains the relevant capability flags.
It may also help to distinguish these terms:
| Setting |
Examples |
Meaning |
| Capture backend/API |
V4L2, FFMPEG |
The route OpenCV uses to access the device |
| Pixel format / FOURCC |
MJPG, YUYV |
The image representation sent by the camera |
| Stream mode |
640×480 at 30 fps |
Resolution and frame rate |
So if you “changed the format to V4L2,” the exact place where you changed it matters: V4L2 is normally the backend/API, whereas MJPG or YUYV would be the pixel format.
2. Test the exact advertised mode without LeRobot
Choose one combination that actually appears in --list-formats-ext.
For example, only if the camera advertises MJPG at 640×480 and 30 fps:
v4l2-ctl -d /dev/video0 \
--set-fmt-video=width=640,height=480,pixelformat=MJPG \
--set-parm=30 \
--stream-mmap \
--stream-count=30 \
--stream-to=/dev/null \
--verbose
For another advertised mode, replace the node, FOURCC, size, and frame rate accordingly:
v4l2-ctl -d <capture-node> \
--set-fmt-video=width=<advertised-width>,height=<advertised-height>,pixelformat=<advertised-fourcc> \
--set-parm=<advertised-fps> \
--stream-mmap \
--stream-count=30 \
--stream-to=/dev/null \
--verbose
If possible, run this in another terminal at the same time:
sudo dmesg -w
That can reveal whether capture triggers a USB reset, UVC timeout, disconnect, or USB/IP/VHCI error.
How to interpret this test
- If this direct V4L2 test fails, the problem is below LeRobot. Changing the LeRobot camera configuration is unlikely to fix it yet.
- If it reads 30 frames successfully, the WSL-to-V4L2 path is basically functioning, and the next useful comparison is the OpenCV backend.
3. Compare OpenCV’s automatic backend with explicit V4L2
This keeps the device and Python environment the same and changes only the OpenCV backend:
import cv2
device = "/dev/video0"
for name, backend in (
("ANY", cv2.CAP_ANY),
("V4L2", cv2.CAP_V4L2),
):
cap = cv2.VideoCapture(device, backend)
print(f"\n{name}")
print("opened:", cap.isOpened())
if cap.isOpened():
print("actual backend:", cap.getBackendName())
print(
"reported mode:",
int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)),
cap.get(cv2.CAP_PROP_FPS),
)
first_frame = None
for attempt in range(30):
ok, frame = cap.read()
if ok and frame is not None:
first_frame = {
"attempt": attempt,
"shape": frame.shape,
}
break
print("first valid frame:", first_frame)
cap.release()
OpenCV allows selecting a capture backend explicitly, and getBackendName() reports which backend was actually opened.
The most informative outcomes are:
-
CAP_ANY selects FFMPEG and fails, while CAP_V4L2 succeeds
This would make the backend path the strongest explanation and would closely resemble issue #3198 and the similar HF Forum case.
-
Both backends succeed
The camera, WSL, V4L2, and basic OpenCV path are working. The remaining difference is likely in lerobot-find-cameras, its version, initialization timing, or device lifecycle.
-
Both fail, although v4l2-ctl succeeds
Check how OpenCV was built and whether multiple OpenCV packages are installed.
-
The direct V4L2 test also fails
Stay on the WSL/UVC branch rather than modifying LeRobot.
If explicit V4L2 works, the corresponding LeRobot camera configuration would be along these lines, using a mode that the camera actually advertised:
from lerobot.cameras.configs import Cv2Backends
from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig
camera_config = OpenCVCameraConfig(
index_or_path="/dev/video0",
width=640,
height=480,
fps=30,
fourcc="MJPG",
backend=Cv2Backends.V4L2,
)
Again, MJPG, 640×480, and 30 fps are only examples; they should match the output from v4l2-ctl --list-formats-ext.
Why the issue you found may be relevant
LeRobot supports an explicit OpenCV backend in OpenCVCameraConfig. In the LeRobot v0.6.0 configuration source, however, the default is still:
backend: Cv2Backends = Cv2Backends.ANY
The v0.6.0 lerobot-find-cameras implementation constructs an OpenCVCameraConfig for each detected camera without passing an explicit backend. Therefore, the finder can still use ANY, which can resolve to FFMPEG for a Linux /dev/video* path—as your output shows.
The related PR #3208 proposes automatically selecting V4L2 on Linux for /dev/video* paths. At the time of writing, that PR is still open.
The important scope limitation from issue #3198 is that it does not claim that FFMPEG is universally broken. Its narrower observation is:
ANY selected FFMPEG on that machine,
- requested settings such as MJPG did not apply through that path,
- and the same settings worked with explicit V4L2.
Your case has one camera rather than two, and WSL introduces another possible failure layer. So this is a strong candidate, not a confirmed diagnosis.
WSL and usbipd checks if direct V4L2 capture fails
Microsoft’s WSL USB documentation describes USB passthrough through usbipd-win. A useful basic state check is:
Windows PowerShell:
wsl --version
usbipd --version
usbipd list
WSL:
uname -r
lsusb
lsusb -t
ls -l /dev/video*
The webcam should appear as Attached in usbipd list and as a Logitech USB device in lsusb.
A few details that are easy to miss:
usbipd bind sharing is persistent.
usbipd attach --wsl is not permanent across unplugging the device or restarting WSL.
- While the USB device is attached to WSL, Windows cannot use it.
- A Windows Camera, Teams, Zoom, browser, or OBS process should therefore not be expected to use the same physical webcam simultaneously.
The official sequence is roughly:
usbipd list
usbipd bind --busid <busid>
usbipd attach --wsl --busid <busid>
Then in WSL:
lsusb
Also check that the normal Linux camera modules exist:
modinfo uvcvideo
lsmod | grep -E 'uvcvideo|videodev|vhci_hcd|usbip'
A custom WSL kernel would not be my first step. First try:
wsl --update
wsl --shutdown
Then re-open WSL and re-attach the camera. A custom kernel becomes relevant only if the installed WSL kernel genuinely lacks a required driver or module.
If the camera node exists but direct streaming fails, the most useful evidence is the V4L2 command output together with the kernel messages produced at the same moment:
sudo dmesg -w
LeRobot version and the camera-finder lifecycle
It is worth recording the exact LeRobot version:
lerobot-info
python -c "import lerobot; print(getattr(lerobot, '__version__', 'unknown'))"
The latest PyPI release at the time of writing is LeRobot 0.6.0, released on July 6, 2026.
A separate camera-finder issue, #3592, identified cases where one physical webcam exposed multiple logical V4L2 nodes and the finder opened them in a way that could cause hardware contention. It also identified a fixed one-second warm-up as insufficient for some cameras.
The corresponding PR #3593 was merged into main on July 29, 2026. It changes the finder to use a sequential lifecycle:
connect -> warm up -> test -> disconnect
and adds a configurable --warmup-s option.
Because that merge happened after the 0.6.0 PyPI release, a normal 0.6.0 installation will not contain that finder change.
This branch becomes relevant mainly if:
- direct V4L2 streaming works,
- the simple OpenCV test works,
- but
lerobot-find-cameras still fails.
Trying current main could then test the newer finder lifecycle and a longer warm-up. However, it should not be treated as a complete backend fix: the separate automatic-V4L2 PR, #3208, is still unmerged.
Optional second reader: FFmpeg directly
If the V4L2 result is unclear, FFmpeg can serve as another reader independent of LeRobot and OpenCV’s wrapper.
First list the modes FFmpeg sees:
ffmpeg -f video4linux2 \
-list_formats all \
-i /dev/video0
Then select one mode from that list:
ffmpeg \
-f video4linux2 \
-input_format <advertised-format> \
-video_size <advertised-width>x<advertised-height> \
-framerate <advertised-fps> \
-i /dev/video0 \
-frames:v 30 \
-f null -
The FFmpeg Video4Linux2 documentation notes that V4L2 devices generally support only specific combinations of format, size, and frame rate.
This gives another useful split:
v4l2-ctl and FFmpeg both fail: likely below OpenCV/LeRobot.
v4l2-ctl works but FFmpeg fails: inspect FFmpeg’s format negotiation.
- FFmpeg works but OpenCV with the FFMPEG backend fails: the OpenCV wrapper/build path becomes more interesting.
Lower-priority environment checks
These are worth checking only after the main split tests:
python -m pip list | grep -i opencv
python - <<'PY'
import cv2
print("OpenCV:", cv2.__version__)
print(cv2.getBuildInformation())
PY
The official opencv-python packaging repository recommends installing only one of:
opencv-python
opencv-contrib-python
opencv-python-headless
opencv-contrib-python-headless
They all provide the same cv2 namespace, so multiple variants in one environment can produce an ambiguous installation.
Also check whether another Linux process has the node open:
fuser -v /dev/video*
lsof /dev/video* 2>/dev/null
And check permissions:
ls -l /dev/video*
id
For deeper OpenCV diagnostics:
OPENCV_LOG_LEVEL=DEBUG \
OPENCV_VIDEOIO_DEBUG=1 \
python opencv_camera_probe.py
Set those variables before Python imports cv2.
I would keep these below the direct V4L2 and backend tests, because the current log already shows that OpenCV can discover and partially query the device.
A useful information bundle for Discord or a GitHub issue
If none of the branches resolves it, this would be a strong evidence bundle for the official LeRobot Discord or a GitHub issue:
Windows version:
wsl --version:
usbipd --version:
usbipd list:
uname -r:
LeRobot version:
OpenCV version:
OpenCV build information:
lsusb:
lsusb -t:
v4l2-ctl --list-devices:
For every /dev/video* node:
v4l2-ctl --all
v4l2-ctl --list-formats-ext
Exact node tested:
Exact FOURCC:
Exact resolution:
Exact frame rate:
Direct v4l2-ctl streaming result:
OpenCV CAP_ANY result and actual backend:
OpenCV CAP_V4L2 result and actual backend:
Kernel messages during the failed capture:
Exact location/configuration where "V4L2" was selected:
That should let someone distinguish a WSL/USB-IP problem from a UVC/V4L2 problem, an OpenCV backend problem, and a LeRobot finder problem without repeating the whole setup process.
So my default route would be:
- Find the node with
Video Capture and Streaming.
- Select one mode actually advertised by the C270.
- Confirm 30 frames with
v4l2-ctl.
- If that works, compare
CAP_ANY and CAP_V4L2.
- Only then change the LeRobot camera backend or investigate the newer finder lifecycle.
That should narrow the problem substantially before any source modification or custom WSL kernel work.