Skip to content
This repository has been archived by the owner on Jun 4, 2024. It is now read-only.

Commit

Permalink
Fix & update yuyv/uyvy encodings (#6445)
Browse files Browse the repository at this point in the history
**User-Facing Changes**
- Renamed raw image encoding `uyuv` to `uyvy` for accuracy.
- Added support for raw image encoding `yuv422_yuy2` as an alias for
`yuyv`.

**Description**
- Fixes #6271, fixes FG-3953
- Schemas PR: foxglove/schemas#121

All [YUV 4:2:2
formats](https://en.wikipedia.org/wiki/Chroma_subsampling) encode one U
and one V value for every two Y values. The byte orders supported by
Studio and by other apps in the ecosystem are `[U, Y1, V, Y2]` and `[Y1,
U, Y2, V]`.

- Replaces `uyuv`, which seems to have been a typo introduced in
#5292, with `uyvy`, which
accurately represents the byte order we are decoding.
- Adds `yuv422_yuy2` as an encoding alias for `yuyv`, taken from ROS:
-
[sensor_msgs/image_encodings.hpp](https://github.com/ros2/common_interfaces/blob/366eea24ffce6c87f8860cbcd27f4863f46ad822/sensor_msgs/include/sensor_msgs/image_encodings.hpp)
  - ros2/common_interfaces#76
  - ros2/common_interfaces#214
- `yuv422_yuy2` is the default encoding used by
[usb_cam](https://github.com/ros-drivers/usb_cam/tree/ros2)
- Remove incorrect and useless `Int8Array` type casting
- Update YUV-to-RGB conversion formula to be faster and match RViz code
- Added story for both UYVY and YUYV
- Confirmed compatibility with rqt_image_view and rviz (although those
two look slightly different from each other):
<img width="1730" alt="Screenshot 2023-07-13 at 5 02 16 PM"
src="https://github.com/foxglove/studio/assets/14237/c7be26e5-3bee-417b-8e67-c3a305e8453c">

Other references:
-
https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/pixfmt-packed-yuv.html
  • Loading branch information
jtbandes committed Jul 14, 2023
1 parent 81e4325 commit f2b9900
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 14 deletions.
23 changes: 13 additions & 10 deletions packages/den/image/decodings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
// found at http://www.apache.org/licenses/LICENSE-2.0
// You may not use this file except in compliance with the License.

/**
* Adapted from:
* https://github.com/ros2/rviz/blob/e8838720e57b56cc0d50e05b00b28bee3c5dc9ee/rviz_default_plugins/src/rviz_default_plugins/displays/image/ros_image_texture.cpp#L332
*/
function yuvToRGBA8(
y1: number,
u: number,
Expand All @@ -20,20 +24,20 @@ function yuvToRGBA8(
output: Uint8ClampedArray,
): void {
// rgba
output[c] = y1 + 1.402 * v;
output[c + 1] = y1 - 0.34414 * u - 0.71414 * v;
output[c + 2] = y1 + 1.772 * u;
output[c] = y1 + Math.trunc((1403 * v) / 1000);
output[c + 1] = y1 - Math.trunc((344 * u) / 1000) - Math.trunc((714 * v) / 1000);
output[c + 2] = y1 + Math.trunc((1770 * u) / 1000);
output[c + 3] = 255;

// rgba
output[c + 4] = y2 + 1.402 * v;
output[c + 5] = y2 - 0.34414 * u - 0.71414 * v;
output[c + 6] = y2 + 1.772 * u;
output[c + 4] = y2 + Math.trunc((1403 * v) / 1000);
output[c + 5] = y2 - Math.trunc((344 * u) / 1000) - Math.trunc((714 * v) / 1000);
output[c + 6] = y2 + Math.trunc((1770 * u) / 1000);
output[c + 7] = 255;
}

export function decodeYUV(
yuv: Int8Array,
export function decodeUYVY(
yuv: Uint8Array,
width: number,
height: number,
output: Uint8ClampedArray,
Expand All @@ -54,9 +58,8 @@ export function decodeYUV(
}
}

// change name in the future do something more distinct
export function decodeYUYV(
yuyv: Int8Array,
yuyv: Uint8Array,
width: number,
height: number,
output: Uint8ClampedArray,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
decodeMono8,
decodeRGB8,
decodeRGBA8,
decodeYUV,
decodeUYVY,
decodeYUYV,
} from "@foxglove/den/image";
import { RawImage } from "@foxglove/schemas";
Expand All @@ -35,6 +35,10 @@ export type RawImageOptions = {
maxValue?: number;
};

/**
* See also:
* https://github.com/ros2/common_interfaces/blob/366eea24ffce6c87f8860cbcd27f4863f46ad822/sensor_msgs/include/sensor_msgs/image_encodings.hpp
*/
export function decodeRawImage(
image: RosImage | RawImage,
options: RawImageOptions,
Expand All @@ -45,11 +49,12 @@ export function decodeRawImage(
const rawData = image.data as Uint8Array;
switch (encoding) {
case "yuv422":
case "uyuv":
decodeYUV(image.data as Int8Array, width, height, output);
case "uyvy":
decodeUYVY(rawData, width, height, output);
break;
case "yuv422_yuy2":
case "yuyv":
decodeYUYV(image.data as Int8Array, width, height, output);
decodeYUYV(rawData, width, height, output);
break;
case "rgb8":
decodeRGB8(rawData, width, height, output);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -785,3 +785,132 @@ export const LargeImage: StoryObj = {
);
},
};

function makeYUYV(width: number, height: number) {
const result = new Uint8Array(2 * width * height);
for (let r = 0; r < height; r++) {
for (let c = 0; c < width; c += 2) {
const y1 = r === c ? 255 : 127;
const y2 = r === c + 1 ? 255 : 127;
const u = Math.trunc(255 * (c / width));
const v = Math.trunc(255 * (r / height));
result[2 * (r * width + c) + 0] = y1;
result[2 * (r * width + c) + 1] = u;
result[2 * (r * width + c) + 2] = y2;
result[2 * (r * width + c) + 3] = v;
}
}
return result;
}

function makeUYVY(width: number, height: number) {
const result = makeYUYV(width, height);
for (let i = 0; i < result.length; i += 4) {
const [y1, u, y2, v] = result.subarray(i, i + 4);
result[i + 0] = u!;
result[i + 1] = y1!;
result[i + 2] = v!;
result[i + 3] = y2!;
}
return result;
}

export const YUYV: StoryObj = {
render: function Story() {
const imageTopic = "camera";
const topics: Topic[] = useMemo(
() => [{ name: imageTopic, schemaName: "foxglove.RawImage" }],
[],
);

const width = 200;
const height = 150;
const cameraMessage: MessageEvent<RawImage> = {
topic: imageTopic,
receiveTime: { sec: 10, nsec: 0 },
message: {
timestamp: { sec: 10, nsec: 0 },
frame_id: "camera",
width,
height,
encoding: "yuyv",
step: width * 2,
data: makeYUYV(width, height),
},
schemaName: "foxglove.RawImage",
sizeInBytes: 0,
};

const fixture: Fixture = {
topics,
frame: {
[imageTopic]: [cameraMessage],
},
capabilities: [],
activeData: {
currentTime: { sec: 0, nsec: 0 },
},
};

return (
<PanelSetup fixture={fixture}>
<ImagePanel
overrideConfig={{
...ImagePanel.defaultConfig,
imageMode: { imageTopic },
}}
/>
</PanelSetup>
);
},
};

export const UYVY: StoryObj = {
render: function Story() {
const imageTopic = "camera";
const topics: Topic[] = useMemo(
() => [{ name: imageTopic, schemaName: "foxglove.RawImage" }],
[],
);

const width = 200;
const height = 150;
const cameraMessage: MessageEvent<RawImage> = {
topic: imageTopic,
receiveTime: { sec: 10, nsec: 0 },
message: {
timestamp: { sec: 10, nsec: 0 },
frame_id: "camera",
width,
height,
encoding: "uyvy",
step: width * 2,
data: makeUYVY(width, height),
},
schemaName: "foxglove.RawImage",
sizeInBytes: 0,
};

const fixture: Fixture = {
topics,
frame: {
[imageTopic]: [cameraMessage],
},
capabilities: [],
activeData: {
currentTime: { sec: 0, nsec: 0 },
},
};

return (
<PanelSetup fixture={fixture}>
<ImagePanel
overrideConfig={{
...ImagePanel.defaultConfig,
imageMode: { imageTopic },
}}
/>
</PanelSetup>
);
},
};

0 comments on commit f2b9900

Please sign in to comment.