浏览代码

Aisv565 bb3d - Bounding box 3D perception changes (#82)

* Bounding box 3D

Working version of bounding box 3d with the ability to capture data in either kitti or nuscenes format. I'm going to remove it in the next commit. Just wanted to make sure that we have a commit for this

* BB3D cleaned up files

* Code commented.

* Comment fixes

* Everything appears to be work prior to code clean up

* Everything working before commit

* Cleaned up code

* Updated changelog

* Updated documentation

* Only report 3DBBs for visible, labeled objects

* Moved execution of labeler to OnBeginRenderering

* Now using rendered object info for occlusion

* Json serialization of boundingbox3d data
/main
GitHub 4 年前
当前提交
ea6e660a
共有 14 个文件被更改,包括 925 次插入22 次删除
  1. 12
      TestProjects/PerceptionURP/Assets/Scenes/SampleScene.unity
  2. 3
      TestProjects/PerceptionURP/PerceptionURP.sln.DotSettings
  3. 2
      TestProjects/PerceptionURP/ProjectSettings/QualitySettings.asset
  4. 1
      com.unity.perception/CHANGELOG.md
  5. 7
      com.unity.perception/Documentation~/PerceptionCamera.md
  6. 34
      com.unity.perception/Runtime/GroundTruth/PerceptionCamera.cs
  7. 64
      com.unity.perception/Runtime/GroundTruth/SimulationState.cs
  8. 48
      com.unity.perception/Tests/Runtime/GroundTruthTests/DatasetJsonUtilityTests.cs
  9. 10
      com.unity.perception/Tests/Runtime/GroundTruthTests/TestHelper.cs
  10. 296
      com.unity.perception/Runtime/GroundTruth/Labelers/BoundingBox3DLabeler.cs
  11. 3
      com.unity.perception/Runtime/GroundTruth/Labelers/BoundingBox3DLabeler.cs.meta
  12. 456
      com.unity.perception/Tests/Runtime/GroundTruthTests/BoundingBox3dTests.cs
  13. 3
      com.unity.perception/Tests/Runtime/GroundTruthTests/BoundingBox3dTests.cs.meta
  14. 8
      TestProjects/PerceptionURP/Assets/Scenes/SampleScene.meta

12
TestProjects/PerceptionURP/Assets/Scenes/SampleScene.unity


- id: 1
- id: 2
- id: 3
- id: 4
showVisualizations: 1
references:
version: 1

annotationId: 12f94d8d-5425-4deb-9b21-5e53ad957d66
labelConfig: {fileID: 11400000, guid: c140c5aa05dd09e4fadaa26de31b1f39, type: 2}
m_TargetTextureOverride: {fileID: 0}
00000004:
type: {class: BoundingBox3DLabeler, ns: UnityEngine.Perception.GroundTruth,
asm: Unity.Perception.Runtime}
data:
enabled: 1
annotationId: 0bfbe00d-00fa-4555-88d1-471b58449f5c
mode: 0
idLabelConfig: {fileID: 11400000, guid: cedcacfb1d9beb34fbbb231166c472fe,
type: 2}
--- !u!114 &963194231
MonoBehaviour:
m_ObjectHideFlags: 0

m_Script: {fileID: 11500000, guid: 673a227032a8e4940b9828c5b6f852ab, type: 3}
m_Name:
m_EditorClassIdentifier:
yDegreesPerSecond: 180
yDegreesPerSecond: 180

3
TestProjects/PerceptionURP/PerceptionURP.sln.DotSettings


<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Property/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Property/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=labeler_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

2
TestProjects/PerceptionURP/ProjectSettings/QualitySettings.asset


skinWeights: 2
textureQuality: 0
anisotropicTextures: 1
antiAliasing: 2
antiAliasing: 0
softParticles: 0
softVegetation: 1
realtimeReflectionProbes: 1

1
com.unity.perception/CHANGELOG.md


### Added
Added Randomizers and RandomizerTags
Added support for generating 3D bounding box ground truth data
### Changed

7
com.unity.perception/Documentation~/PerceptionCamera.md


The BoundingBox2DLabeler produces 2D bounding boxes for each visible object with a label you define in the IdLabelConfig. Unity calculates bounding boxes using the rendered image, so it only excludes occluded or out-of-frame portions of the objects.
### Bounding Box 3D Ground Truth Labeler
The Bounding Box 3D Ground Truth Labeler prouces 3D ground truth bounding boxes for each labeled game object in the scene. Unlike the 2D bounding boxes, 3D bounding boxes are calculated from the labeled meshes in the scene and all objects (independent of their occlusion state) are recorded.
```
{
"label_id": 25,

* Unity does not consider transparency and considers all geometry opaque
* Unity does not run post-processing effects, except built-in lens distortion in URP and HDRP
If you discover more incompatibilities, please open an issue in the [Perception GitHub repository](https://github.com/Unity-Technologies/com.unity.perception/issues).
If you discover more incompatibilities, please open an issue in the [Perception GitHub repository](https://github.com/Unity-Technologies/com.unity.perception/issues).

34
com.unity.perception/Runtime/GroundTruth/PerceptionCamera.cs


using System;
using System;
using Unity.Mathematics;
using Unity.Profiling;
using Unity.Simulation;
using UnityEngine;

/// Whether camera output should be captured to disk
/// </summary>
public bool captureRgbImages = true;
Camera m_AttachedCamera = null;
/// <summary>
/// Caches access to the camera attached to the perception camera
/// </summary>
public Camera attachedCamera => m_AttachedCamera;
/// <summary>
/// Event invoked after the camera finishes rendering during a frame.
/// </summary>

AsyncRequest.maxAsyncRequestFrameAge = 4; // Ensure that readbacks happen before Allocator.TempJob allocations get stale
SetupInstanceSegmentation();
var cam = GetComponent<Camera>();
m_AttachedCamera = GetComponent<Camera>();
SetupVisualizationCamera(cam);
SetupVisualizationCamera(m_AttachedCamera);
DatasetCapture.SimulationEnding += OnSimulationEnding;

void CheckForRendererFeature(ScriptableRenderContext context, Camera camera)
{
if (camera == GetComponent<Camera>())
if (camera == m_AttachedCamera)
{
#if URP_PRESENT
if (!m_GroundTruthRendererFeatureRun)

if (!SensorHandle.IsValid)
return;
var cam = GetComponent<Camera>(); // TODO I don't like this... Get the camera handle, we should have it
cam.enabled = SensorHandle.ShouldCaptureThisFrame;
m_AttachedCamera.enabled = SensorHandle.ShouldCaptureThisFrame;
bool anyVisualizing = false;
foreach (var labeler in m_Labelers)

m_Labelers = new List<CameraLabeler>();
}
// Convert the Unity 4x4 projection matrix to a 3x3 matrix
// ReSharper disable once InconsistentNaming
static float3x3 ToProjectionMatrix3x3(Matrix4x4 inMatrix)
{
return new float3x3(
inMatrix[0,0], inMatrix[0,1], inMatrix[0,2],
inMatrix[1,0], inMatrix[1,1], inMatrix[1,2],
inMatrix[2,0],inMatrix[2,1], inMatrix[2,2]);
}
// Record the camera's projection matrix
SetPersistentSensorData("camera_intrinsic", ToProjectionMatrix3x3(cam.projectionMatrix));
var captureFilename = $"{Manager.Instance.GetDirectoryFor(RgbDirectory)}/{s_RgbFilePrefix}{Time.frameCount}.png";
var dxRootPath = $"{RgbDirectory}/{s_RgbFilePrefix}{Time.frameCount}.png";

void OnBeginCameraRendering(ScriptableRenderContext _, Camera cam)
{
if (cam != GetComponent<Camera>())
if (cam != m_AttachedCamera)
return;
if (!SensorHandle.ShouldCaptureThisFrame)
return;

64
com.unity.perception/Runtime/GroundTruth/SimulationState.cs


using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using Unity.Mathematics;
using Unity.Simulation;
using UnityEngine;
using UnityEngine.Profiling;

readonly string m_OutputDirectoryName;
string m_OutputDirectoryPath;
JsonSerializer m_AnnotationSerializer;
public bool IsRunning { get; private set; }
public string OutputDirectory

public SimulationState(string outputDirectory)
{
m_AnnotationSerializer = JsonSerializer.CreateDefault();
m_AnnotationSerializer.Converters.Add(new Vector3Converter());
m_AnnotationSerializer.Converters.Add(new QuaternionConverter());
m_OutputDirectoryName = outputDirectory;
IsRunning = true;
}

ReportAsyncAnnotationResult<T>(asyncAnnotation, filename, jArray);
}
[SuppressMessage("ReSharper", "PossibleInvalidOperationException")]
public class QuaternionConverter : JsonConverter<Quaternion>
{
public override void WriteJson(JsonWriter writer, Quaternion quaternion, JsonSerializer serializer)
{
writer.WriteStartArray();
writer.WriteValue(quaternion.x);
writer.WriteValue(quaternion.y);
writer.WriteValue(quaternion.z);
writer.WriteValue(quaternion.w);
writer.WriteEndArray();
}
public override Quaternion ReadJson(JsonReader reader, Type objectType, Quaternion existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var q = Quaternion.identity;
reader.Read(); // open [ token
q.x = (float)reader.ReadAsDecimal();
q.y = (float)reader.ReadAsDecimal();
q.z = (float)reader.ReadAsDecimal();
q.w = (float)reader.ReadAsDecimal();
reader.Read(); // close ] token
return q;
}
}
[SuppressMessage("ReSharper", "PossibleInvalidOperationException")]
public class Vector3Converter : JsonConverter<Vector3>
{
public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer serializer)
{
writer.WriteStartArray();
writer.WriteValue(value.x);
writer.WriteValue(value.y);
writer.WriteValue(value.z);
writer.WriteEndArray();
}
public override Vector3 ReadJson(JsonReader reader, Type objectType, Vector3 existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var outVector = new Vector3();
reader.Read(); // open array token
outVector.x = (float)reader.ReadAsDecimal();
outVector.y = (float)reader.ReadAsDecimal();
outVector.z = (float)reader.ReadAsDecimal();
reader.Read(); // close array token
return outVector;
}
}
var jArray = values == null ? null : JArray.FromObject(values);
var jArray = values == null ? null : JArray.FromObject(values, m_AnnotationSerializer);
ReportAsyncAnnotationResult<T>(asyncAnnotation, filename, jArray);
}

48
com.unity.perception/Tests/Runtime/GroundTruthTests/DatasetJsonUtilityTests.cs


using System.IO;
using System.Text;
using Newtonsoft.Json;
using NUnit.Framework;
using Unity.Mathematics;
using UnityEngine;

{
var jsonActual = DatasetJsonUtility.ToJToken(o).ToString();
Assert.AreEqual(TestHelper.NormalizeJson(jsonExpected), TestHelper.NormalizeJson(jsonActual));
}
[Test]
[TestCase(1,2,3)]
[TestCase(0,0,0)]
[TestCase(1000, 13000,700)]
[TestCase(1.23f, 4.56f, 7.89f)]
[TestCase(-100, 27.33f, -501.78f)]
public void Vector3Converter_ConvertsProperly(float x, float y, float z)
{
var converter = new SimulationState.Vector3Converter();
var testCase = new Vector3(x,y,z);
var sb = new StringBuilder();
var sw = new StringWriter(sb);
var serializer = JsonSerializer.CreateDefault();
converter.WriteJson(new JsonTextWriter(sw), testCase, serializer);
var str = sb.ToString();
var sr = new StringReader(str);
var converted = converter.ReadJson(new JsonTextReader(sr), typeof(Vector3), Vector3.zero, false, serializer);
Assert.AreEqual(testCase, converted);
}
[Test]
[TestCase(1,2,3, 4)]
[TestCase(0,0,0, 1)]
[TestCase(0, 0.6502878f, 0, -0.7596879f)]
[TestCase(-0.3670137f, -0.1835069f, 0.1223379f, -0.9036922f)]
public void QuaternionConverter_ConvertsProperly(float x, float y, float z, float w)
{
var converter = new SimulationState.QuaternionConverter();
var testCase = new Quaternion(x, y, z, w);
var sb = new StringBuilder();
var sw = new StringWriter(sb);
var serializer = JsonSerializer.CreateDefault();
converter.WriteJson(new JsonTextWriter(sw), testCase, serializer);
var str = sb.ToString();
var sr = new StringReader(str);
var converted = converter.ReadJson(new JsonTextReader(sr), typeof(Quaternion), Quaternion.identity, false, serializer);
Assert.AreEqual(testCase, converted);
}
}
}

10
com.unity.perception/Tests/Runtime/GroundTruthTests/TestHelper.cs


return planeObject;
}
public static GameObject CreateLabeledCube(float scale = 10, string label = "label", float x = 0, float y = 0, float z = 0, float roll = 0, float pitch = 0, float yaw = 0)
{
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.transform.SetPositionAndRotation(new Vector3(x, y, z), Quaternion.Euler(pitch, yaw, roll));
cube.transform.localScale = new Vector3(scale, scale, scale);
var labeling = cube.AddComponent<Labeling>();
labeling.labels.Add(label);
return cube;
}
public static void ReadRenderTextureRawData<T>(RenderTexture renderTexture, Action<NativeArray<T>> callback) where T : struct
{
RenderTexture.active = renderTexture;

296
com.unity.perception/Runtime/GroundTruth/Labelers/BoundingBox3DLabeler.cs


using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Unity.Collections;
using Unity.Entities;
using Unity.Profiling;
namespace UnityEngine.Perception.GroundTruth
{
/// <summary>
/// Produces 3d bounding box ground truth for all visible and <see cref="Labeling"/> objects each frame.
/// </summary>
public class BoundingBox3DLabeler : CameraLabeler
{
EntityQuery m_EntityQuery;
///<inheritdoc/>
public override string description
{
get => "Produces 3D bounding box ground truth data for all visible objects that bear a label defined in this labeler's associated label configuration.";
protected set {}
}
// ReSharper disable MemberCanBePrivate.Global
/// <summary>
/// The GUID id to associate with the annotations produced by this labeler.
/// </summary>
public string annotationId = "0bfbe00d-00fa-4555-88d1-471b58449f5c";
/// <summary>
/// The <see cref="IdLabelConfig"/> which associates objects with labels.
/// </summary>
public IdLabelConfig idLabelConfig;
// ReSharper restore MemberCanBePrivate.Global
/// <summary>
/// Each 3D bounding box data record maps a tuple of (instance, label) to translation, size and rotation that draws a 3D bounding box,
/// as well as velocity and acceleration (optional) of the 3D bounding box. All location data is given with respect to the sensor coordinate system.
///
/// bounding_box_3d
/// label_id (int): Integer identifier of the label
/// label_name (str): String identifier of the label
/// instance_id (str): UUID of the instance.
/// translation (float, float, float): 3d bounding box's center location in meters as center_x, center_y, center_z with respect to global coordinate system.
/// size (float, float, float): 3d bounding box size in meters as width, length, height.
/// rotation (float, float, float, float): 3d bounding box orientation as quaternion: w, x, y, z.
/// velocity (float, float, float) [optional]: 3d bounding box velocity in meters per second as v_x, v_y, v_z.
/// acceleration (float, float, float) [optional]: 3d bounding box acceleration in meters per second^2 as a_x, a_y, a_z.
/// </summary>
/// <remarks>
/// Currently not supporting exporting velocity and acceleration. Both values will be null.
/// </remarks>
[SuppressMessage("ReSharper", "InconsistentNaming")]
[Serializable]
public struct BoxData
{
public int label_id;
public string label_name;
public uint instance_id;
public Vector3 translation;
public Vector3 size;
public Quaternion rotation;
public Vector3 velocity;
public Vector3 acceleration;
}
static ProfilerMarker s_BoundingBoxCallback = new ProfilerMarker("OnBoundingBoxes3DReceived");
AnnotationDefinition m_AnnotationDefinition;
Dictionary<int, AsyncAnnotation> m_AsyncAnnotations;
Dictionary<int, Dictionary<uint, BoxData>> m_BoundingBoxValues;
List<BoxData> m_ToReport;
int m_CurrentFrame;
/// <inheritdoc/>
protected override bool supportsVisualization => false;
/// <summary>
/// Fired when the bounding boxes are computed for a frame.
/// </summary>
public event Action<int, List<BoxData>> BoundingBoxComputed;
/// <summary>
/// Creates a new BoundingBox3DLabeler. Be sure to assign <see cref="idLabelConfig"/> before adding to a <see cref="PerceptionCamera"/>.
/// </summary>
public BoundingBox3DLabeler() {}
/// <summary>
/// Creates a new BoundingBox3DLabeler with the given <see cref="IdLabelConfig"/>.
/// </summary>
/// <param name="labelConfig">The label config for resolving the label for each object.</param>
public BoundingBox3DLabeler(IdLabelConfig labelConfig)
{
this.idLabelConfig = labelConfig;
}
/// <inheritdoc/>
protected override void Setup()
{
if (idLabelConfig == null)
throw new InvalidOperationException("BoundingBox2DLabeler's idLabelConfig field must be assigned");
m_AnnotationDefinition = DatasetCapture.RegisterAnnotationDefinition("bounding box 3D", idLabelConfig.GetAnnotationSpecification(),
"Bounding box for each labeled object visible to the sensor", id: new Guid(annotationId));
perceptionCamera.RenderedObjectInfosCalculated += OnRenderObjectInfosCalculated;
m_EntityQuery = World.DefaultGameObjectInjectionWorld.EntityManager.CreateEntityQuery(typeof(Labeling), typeof(GroundTruthInfo));
m_AsyncAnnotations = new Dictionary<int, AsyncAnnotation>();
m_BoundingBoxValues = new Dictionary<int, Dictionary<uint, BoxData>>();
m_ToReport = new List<BoxData>();
}
static BoxData ConvertToBoxData(IdLabelEntry label, uint instanceId, Vector3 center, Vector3 extents, Quaternion rot)
{
return new BoxData
{
label_id = label.id,
label_name = label.label,
instance_id = instanceId,
translation = center,
size = extents * 2,
rotation = rot,
acceleration = Vector3.zero,
velocity = Vector3.zero
};
}
static Vector3[] GetBoxCorners(Bounds bounds, Quaternion rotation)
{
var boundsCenter = bounds.center;
var right = Vector3.right * bounds.extents.x;
var up = Vector3.up * bounds.extents.y;
var forward = Vector3.forward * bounds.extents.z;
right = rotation * right;
up = rotation * up;
forward = rotation * forward;
var doubleRight = right * 2;
var doubleUp = up * 2;
var doubleForward = forward * 2;
var corners = new Vector3[8];
corners[0] = boundsCenter - right - up - forward;
corners[1] = corners[0] + doubleUp;
corners[2] = corners[1] + doubleRight;
corners[3] = corners[0] + doubleRight;
for (var i = 0; i < 4; i++)
{
corners[i + 4] = corners[i] + doubleForward;
}
return corners;
}
/// <inheritdoc/>
protected override void OnBeginRendering()
{
m_CurrentFrame = Time.frameCount;
m_BoundingBoxValues[m_CurrentFrame] = new Dictionary<uint, BoxData>();
m_AsyncAnnotations[m_CurrentFrame] = perceptionCamera.SensorHandle.ReportAnnotationAsync(m_AnnotationDefinition);
var entities = m_EntityQuery.ToEntityArray(Allocator.TempJob);
var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
foreach (var entity in entities)
{
ProcessEntity(entityManager.GetComponentObject<Labeling>(entity));
}
entities.Dispose();
}
void OnRenderObjectInfosCalculated(int frameCount, NativeArray<RenderedObjectInfo> renderedObjectInfos)
{
if (!m_AsyncAnnotations.TryGetValue(frameCount, out var asyncAnnotation))
return;
if (!m_BoundingBoxValues.TryGetValue(frameCount, out var boxes))
return;
m_AsyncAnnotations.Remove(frameCount);
m_BoundingBoxValues.Remove(frameCount);
using (s_BoundingBoxCallback.Auto())
{
m_ToReport.Clear();
for (var i = 0; i < renderedObjectInfos.Length; i++)
{
var objectInfo = renderedObjectInfos[i];
if (boxes.TryGetValue(objectInfo.instanceId, out var box))
{
m_ToReport.Add(box);
}
}
BoundingBoxComputed?.Invoke(frameCount, m_ToReport);
asyncAnnotation.ReportValues(m_ToReport);
}
}
void ProcessEntity(Labeling labeledEntity)
{
using (s_BoundingBoxCallback.Auto())
{
// Unfortunately to get the non-axis aligned bounding prism from a game object is not very
// straightforward. A game object's default bounding prism is always axis aligned. To find a "tight"
// fitting prism for a game object we must calculate the oriented bounds of all of the meshes in a
// game object. These meshes (in the object tree) may go through a series of transformations. We need
// to transform all of the children mesh bounds into the coordinate space of the "labeled" game object
// and then intersect all of those bounds together. We then need to apply the "labeled" game object's
// transform to the combined bounds to transform the bounds into world space. Finally, we then need
// to take the bounds in world space and transform it to camera space to record it to json...
//
// Currently we are only reporting objects that are a) labeled and b) are visible based on the perception
// camera's rendered object info. In the future we plan on reporting how much of the object can be seen, including
// none if it is off camera
if (idLabelConfig.TryGetLabelEntryFromInstanceId(labeledEntity.instanceId, out var labelEntry))
{
var entityGameObject = labeledEntity.gameObject;
var meshFilters = entityGameObject.GetComponentsInChildren<MeshFilter>();
if (meshFilters == null || meshFilters.Length == 0) return;
var labelTransform = entityGameObject.transform;
var cameraTransform = perceptionCamera.transform;
var combinedBounds = new Bounds(Vector3.zero, Vector3.zero);
var areBoundsUnset = true;
// Need to convert all bounds into labeling mesh space...
foreach (var mesh in meshFilters)
{
var currentTransform = mesh.gameObject.transform;
// Grab the bounds of the game object from the mesh, although these bounds are axis-aligned,
// they are axis-aligned with respect to the current component's coordinate space. This, in theory
// could still provide non-ideal fitting bounds (if the model is made strangely, but garbage in; garbage out)
var meshBounds = mesh.mesh.bounds;
var transformedBounds = new Bounds(meshBounds.center, meshBounds.size);
var transformedRotation = Quaternion.identity;
// Apply the transformations on this object until we reach the labeled transform
while (currentTransform != labelTransform)
{
transformedBounds.center += currentTransform.localPosition;
transformedBounds.extents = Vector3.Scale(transformedBounds.extents, currentTransform.localScale);
transformedRotation *= currentTransform.localRotation;
currentTransform = currentTransform.parent;
}
// Due to rotations that may be applied, we cannot simply use the extents of the bounds, but
// need to calculate all 8 corners of the bounds and combine them with the current combined
// bounds
var corners = GetBoxCorners(transformedBounds, transformedRotation);
// If this is the first time, create a new bounds struct
if (areBoundsUnset)
{
combinedBounds = new Bounds(corners[0], Vector3.zero);
areBoundsUnset = false;
}
// Go through each corner add add it to the bounds
foreach (var c2 in corners)
{
combinedBounds.Encapsulate(c2);
}
}
// Convert the combined bounds into world space
combinedBounds.center = labelTransform.TransformPoint(combinedBounds.center);
combinedBounds.extents = Vector3.Scale(combinedBounds.extents, labelTransform.localScale);
// Now convert all points into camera's space
var cameraCenter = cameraTransform.InverseTransformPoint(combinedBounds.center);
cameraCenter = Vector3.Scale(cameraTransform.localScale, cameraCenter);
// Rotation to go from label space to camera space
var cameraRotation = Quaternion.Inverse(cameraTransform.rotation) * labelTransform.rotation;
var converted = ConvertToBoxData(labelEntry, labeledEntity.instanceId, cameraCenter, combinedBounds.extents, cameraRotation);
m_BoundingBoxValues[m_CurrentFrame][labeledEntity.instanceId] = converted;
}
}
}
}
}

3
com.unity.perception/Runtime/GroundTruth/Labelers/BoundingBox3DLabeler.cs.meta


fileFormatVersion: 2
guid: da97f6ffee604bdea17edaf29854ff3a
timeCreated: 1600266084

456
com.unity.perception/Tests/Runtime/GroundTruthTests/BoundingBox3dTests.cs


using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Perception.GroundTruth;
using UnityEngine.TestTools;
namespace GroundTruthTests
{
[TestFixture]
public class BoundingBox3dTests : GroundTruthTestBase
{
const float k_Delta = 0.0001f;
static string PrintBox(BoundingBox3DLabeler.BoxData box)
{
var sb = new StringBuilder();
sb.Append("label id: " + box.label_id + " ");
sb.Append("label_name: " + box.label_name + " ");
sb.Append("instance_id: " + box.instance_id + " ");
sb.Append("translation: (" + box.translation[0] + ", " + box.translation[1] + ", " + box.translation[2] + ") ");
sb.Append("size: (" + box.size[0] + ", " + box.size[1] + ", " + box.size[2] + ") ");
sb.Append("rotation: " + box.rotation[0] + ", " + box.rotation[1] + ", " + box.rotation[2] + ", " + box.rotation[3] + ") ");
sb.Append("velocity: " + box.velocity[0] + ", " + box.velocity[1] + ", " + box.velocity[2]);
sb.Append("acceleration: (" + box.acceleration[0] + ", " + box.acceleration[1] + ", " + box.acceleration[2] + ")");
return sb.ToString();
}
[UnityTest]
public IEnumerator CameraOffset_ProduceProperTranslationTest()
{
var expected = new[]
{
new ExpectedResult
{
instanceId = 1,
labelId = 1,
labelName = "label",
position = new Vector3(0, 0, 10),
scale = new Vector3(5, 5, 5),
rotation = Quaternion.identity
}
};
var target = TestHelper.CreateLabeledCube();
var cameraPosition = new Vector3(0, 0, -10);
var cameraRotation = Quaternion.identity;
return ExecuteTest(target, cameraPosition, cameraRotation, expected);
}
[UnityTest]
public IEnumerator CameraOffsetAndRotated_ProduceProperTranslationTest()
{
var expected = new[]
{
new ExpectedResult
{
instanceId = 1,
labelId = 1,
labelName = "label",
position = new Vector3(0, 0, Mathf.Sqrt(200)),
scale = new Vector3(5, 5, 5),
rotation = Quaternion.identity
}
};
var target = TestHelper.CreateLabeledCube(x: 10, y: 0, z: 10, yaw: 45);
var cameraPosition = new Vector3(0, 0, 0);
var cameraRotation = Quaternion.Euler(0, 45, 0);
return ExecuteTest(target, cameraPosition, cameraRotation, expected);
}
[UnityTest]
public IEnumerator SimpleMultiMesh_ProduceProperTranslationTest()
{
var expected = new[]
{
new ExpectedResult
{
instanceId = 1,
labelId = 1,
labelName = "label",
position = new Vector3(0, 0, 10),
scale = new Vector3(6.5f, 2.5f, 2.5f),
rotation = Quaternion.identity
}
};
var target = CreateMultiMeshGameObject();
target.transform.position = Vector3.zero;
var cameraPosition = new Vector3(0, 0, -10);
var cameraRotation = Quaternion.identity;
return ExecuteTest(target, cameraPosition, cameraRotation, expected);
}
[UnityTest]
public IEnumerator MultiInheritedMesh_ProduceProperTranslationTest()
{
var expected = new[]
{
new ExpectedResult
{
instanceId = 1,
labelId = 2,
labelName = "car",
position = new Vector3(0, 0.525f, 20),
scale = new Vector3(2f, 0.875f, 2.4f),
rotation = Quaternion.identity
},
};
var target = CreateTestReallyBadCar(new Vector3(0, 0.35f, 20), Quaternion.identity);
target.transform.localPosition = new Vector3(0, 0, 20);
var cameraPosition = new Vector3(0, 0, 0);
var cameraRotation = Quaternion.identity;
return ExecuteTest(target, cameraPosition, cameraRotation, expected);
}
[UnityTest]
public IEnumerator MultiInheritedMeshDifferentLabels_ProduceProperTranslationTest()
{
var wheelScale = new Vector3(0.35f, 1.0f, 0.35f);
var wheelRot = Quaternion.Euler(0, 0, 90);
var expected = new[]
{
new ExpectedResult
{
instanceId = 1,
labelId = 2,
labelName = "car",
position = new Vector3(0, 1.05f, 20),
scale = new Vector3(1f, 0.7f, 2.4f),
rotation = Quaternion.identity
},
new ExpectedResult
{
instanceId = 2,
labelId = 3,
labelName = "wheel",
position = new Vector3(1, 0.35f, 18.6f),
scale = wheelScale,
rotation = wheelRot
},
new ExpectedResult
{
instanceId = 4,
labelId = 3,
labelName = "wheel",
position = new Vector3(-1, 0.35f, 18.6f),
scale = wheelScale,
rotation = wheelRot
}
};
var target = CreateTestReallyBadCar(new Vector3(0, 0.35f, 20), Quaternion.identity, false);
var cameraPosition = new Vector3(0, 0, 0);
var cameraRotation = Quaternion.identity;
return ExecuteTest(target, cameraPosition, cameraRotation, expected);
}
[UnityTest]
public IEnumerator TestOcclusion_Seen()
{
var target = TestHelper.CreateLabeledCube(scale: 15f, z: 50f);
return ExecuteSeenUnseenTest(target, Vector3.zero, quaternion.identity, 1);
}
[UnityTest]
public IEnumerator TestOcclusion_Unseen()
{
var target = TestHelper.CreateLabeledCube(scale: 15f, z: -50f);
return ExecuteSeenUnseenTest(target, Vector3.zero, quaternion.identity, 0);
}
struct ExpectedResult
{
public int labelId;
public string labelName;
public int instanceId;
public Vector3 position;
public Vector3 scale;
public Quaternion rotation;
}
IEnumerator ExecuteSeenUnseenTest(GameObject target, Vector3 cameraPos, Quaternion cameraRotation, int expectedSeen)
{
var receivedResults = new List<(int, List<BoundingBox3DLabeler.BoxData>)>();
var gameObject = SetupCamera(SetupLabelConfig(), (frame, data) =>
{
receivedResults.Add((frame, data));
});
gameObject.transform.position = cameraPos;
gameObject.transform.rotation = cameraRotation;
AddTestObjectForCleanup(gameObject);
gameObject.SetActive(false);
receivedResults.Clear();
gameObject.SetActive(true);
yield return null;
yield return null;
Assert.AreEqual(expectedSeen, receivedResults[0].Item2.Count);
DestroyTestObject(gameObject);
UnityEngine.Object.DestroyImmediate(target);
}
IEnumerator ExecuteTest(GameObject target, Vector3 cameraPos, Quaternion cameraRotation, IList<ExpectedResult> expectations)
{
var receivedResults = new List<(int, List<BoundingBox3DLabeler.BoxData>)>();
var gameObject = SetupCamera(SetupLabelConfig(), (frame, data) =>
{
receivedResults.Add((frame, data));
});
gameObject.transform.position = cameraPos;
gameObject.transform.rotation = cameraRotation;
AddTestObjectForCleanup(gameObject);
gameObject.SetActive(false);
receivedResults.Clear();
gameObject.SetActive(true);
yield return null;
yield return null;
Assert.AreEqual(expectations.Count, receivedResults[0].Item2.Count);
for (var i = 0; i < receivedResults[0].Item2.Count; i++)
{
var b = receivedResults[0].Item2[i];
Assert.AreEqual(expectations[i].labelId, b.label_id);
Assert.AreEqual(expectations[i].labelName, b.label_name);
Assert.AreEqual(expectations[i].instanceId, b.instance_id);
TestResults(b, expectations[i]);
}
DestroyTestObject(gameObject);
UnityEngine.Object.DestroyImmediate(target);
}
static IdLabelConfig SetupLabelConfig()
{
var labelConfig = ScriptableObject.CreateInstance<IdLabelConfig>();
labelConfig.Init(new List<IdLabelEntry>()
{
new IdLabelEntry
{
id = 1,
label = "label"
},
new IdLabelEntry
{
id = 2,
label= "car"
},
new IdLabelEntry
{
id = 3,
label= "wheel"
},
});
return labelConfig;
}
static GameObject SetupCamera(IdLabelConfig config, Action<int, List<BoundingBox3DLabeler.BoxData>> computeListener)
{
var cameraObject = new GameObject();
cameraObject.SetActive(false);
var camera = cameraObject.AddComponent<Camera>();
camera.orthographic = false;
camera.fieldOfView = 60;
camera.nearClipPlane = 0.3f;
camera.farClipPlane = 1000;
var perceptionCamera = cameraObject.AddComponent<PerceptionCamera>();
perceptionCamera.captureRgbImages = false;
var bboxLabeler = new BoundingBox3DLabeler(config);
if (computeListener != null)
bboxLabeler.BoundingBoxComputed += computeListener;
perceptionCamera.AddLabeler(bboxLabeler);
return cameraObject;
}
static void TestResults(BoundingBox3DLabeler.BoxData data, ExpectedResult e)
{
var scale = e.scale * 2;
Assert.IsNotNull(data);
Assert.AreEqual(e.position[0], data.translation[0], k_Delta);
Assert.AreEqual(e.position[1], data.translation[1], k_Delta);
Assert.AreEqual(e.position[2], data.translation[2], k_Delta);
Assert.AreEqual(scale[0], data.size[0], k_Delta);
Assert.AreEqual(scale[1], data.size[1], k_Delta);
Assert.AreEqual(scale[2], data.size[2], k_Delta);
Assert.AreEqual(e.rotation[0], data.rotation[0], k_Delta);
Assert.AreEqual(e.rotation[1], data.rotation[1], k_Delta);
Assert.AreEqual(e.rotation[2], data.rotation[2], k_Delta);
Assert.AreEqual(e.rotation[3], data.rotation[3], k_Delta);
Assert.AreEqual(Vector3.zero, data.velocity);
Assert.AreEqual(Vector3.zero, data.acceleration);
}
static GameObject CreateTestReallyBadCar(Vector3 position, Quaternion rotation, bool underOneLabel = true)
{
var badCar = new GameObject("BadCar");
badCar.transform.position = position;
badCar.transform.rotation = rotation;
if (underOneLabel)
{
var labeling = badCar.AddComponent<Labeling>();
labeling.labels.Add("car");
}
var body = GameObject.CreatePrimitive(PrimitiveType.Cube);
body.name = "body";
body.transform.parent = badCar.transform;
body.transform.localPosition = new Vector3(0, 0.7f, 0);
body.transform.localScale = new Vector3(2f, 1.4f, 4.8f);
if (!underOneLabel)
{
var labeling = body.AddComponent<Labeling>();
labeling.labels.Add("car");
}
var wheel = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
wheel.name = "wheel1";
wheel.transform.parent = badCar.transform;
wheel.transform.localPosition = new Vector3(1f, 0, -1.4f);
wheel.transform.localRotation = Quaternion.Euler(0, 0, 90);
wheel.transform.localScale = new Vector3(0.7f, 1, 0.7f);
if (!underOneLabel)
{
var labeling = wheel.AddComponent<Labeling>();
labeling.labels.Add("wheel");
}
wheel = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
wheel.name = "wheel2";
wheel.transform.parent = badCar.transform;
wheel.transform.localPosition = new Vector3(1f, 0, 1.45f);
wheel.transform.localRotation = Quaternion.Euler(0, 0, 90);
wheel.transform.localScale = new Vector3(0.7f, 1, 0.7f);
if (!underOneLabel)
{
var labeling = wheel.AddComponent<Labeling>();
labeling.labels.Add("wheel");
}
wheel = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
wheel.name = "wheel3";
wheel.transform.parent = badCar.transform;
wheel.transform.localPosition = new Vector3(-1f, 0, -1.4f);
wheel.transform.localRotation = Quaternion.Euler(0, 0, 90);
wheel.transform.localScale = new Vector3(0.7f, 1, 0.7f);
if (!underOneLabel)
{
var labeling = wheel.AddComponent<Labeling>();
labeling.labels.Add("wheel");
}
wheel = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
wheel.name = "wheel4";
wheel.transform.parent = badCar.transform;
wheel.transform.localPosition = new Vector3(-1f, 0, 1.45f);
wheel.transform.localRotation = Quaternion.Euler(0, 0, 90);
wheel.transform.localScale = new Vector3(0.7f, 1, 0.7f);
if (!underOneLabel)
{
var labeling = wheel.AddComponent<Labeling>();
labeling.labels.Add("wheel");
}
return badCar;
}
static GameObject CreateMultiMeshGameObject()
{
var go = new GameObject();
var labeling = go.AddComponent<Labeling>();
labeling.labels.Add("label");
var left = GameObject.CreatePrimitive(PrimitiveType.Cube);
left.transform.parent = go.transform;
left.transform.localPosition = new Vector3(-4, 0, 0);
left.transform.localScale = new Vector3(5, 5, 5);
var right = GameObject.CreatePrimitive(PrimitiveType.Cube);
right.transform.parent = go.transform;
right.transform.localPosition = new Vector3(4, 0, 0);
right.transform.localScale = new Vector3(5, 5, 5);
var center = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
center.transform.parent = go.transform;
center.transform.localPosition = Vector3.zero;
center.transform.localRotation = Quaternion.Euler(0, 0, 90);
center.transform.localScale = new Vector3(1, 3, 1);
return go;
}
class CubeSpinner : MonoBehaviour
{
public Quaternion rotationPerFrame = Quaternion.Euler(0, 15, 0);
void Update()
{
transform.localRotation *= rotationPerFrame;
}
}
class CubeMover : MonoBehaviour
{
public Vector3 distancePerFrame = new Vector3(5, 0 , 0);
void Update()
{
transform.localPosition += distancePerFrame;
}
}
static GameObject CreateDynamicBox(bool spinning = false, bool moving = false, Vector3? translation = null, Quaternion? rotation = null)
{
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.name = "Cube";
var labeling = cube.AddComponent<Labeling>();
labeling.labels.Add("label");
cube.transform.position = new Vector3(0f, 0f, 10f);
cube.transform.localScale = new Vector3(30f, 30f, 30f);
if (spinning)
{
var spin = cube.AddComponent<CubeSpinner>();
spin.rotationPerFrame = rotation ?? spin.rotationPerFrame;
}
if (moving)
{
var move = cube.AddComponent<CubeMover>();
move.distancePerFrame = translation ?? move.distancePerFrame;
}
return cube;
}
}
}

3
com.unity.perception/Tests/Runtime/GroundTruthTests/BoundingBox3dTests.cs.meta


fileFormatVersion: 2
guid: 783926dac50d43889ccc914436bdf544
timeCreated: 1600266800

8
TestProjects/PerceptionURP/Assets/Scenes/SampleScene.meta


fileFormatVersion: 2
guid: c63d11a708fe3f047a545d7e457f6e81
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存