Self Driving Car Weekly Highlights | Week 08/10/17 - 15/10/17 [DE]

General Motors kauft Strobe, solid-state lidar company

Note: This post was translated from English and reviewed with google translate

Dies ist die größte Wichtigste Neuigkeit der Woche. Wir wissen gany genau, dass viele Automobilhersteller mögen solid-state Lidars: Selbst wenn drehend Laser Scanner arbeiten für Forschung und Prototypen (hohe Leistung und Genaugigkeit, aber hohe Kosten) man denkt nicht, sie benutzen in Massenproduktion: rotierende Komponenten halten nicht lange für Anwendung wo Genauigkeit wichtig ist. Du willst nicht, dass die Benutzer ihre Autos zurückbringen, um Lidars zu reparieren

Solid State Lidars wirklich interessant aussehen: keine beweglichen Teile, alles unterbringet in ein Chip, mit geringen Kosten, vergleichbar Genaiugkeit und Reichweite. Aber es gibt ein nur Problem: Wir können sie leicht machen. Trotz sie sind bekannt für einegen Jahren, Solid State Lidars sind schwer zur produzieren. Naturlich dank Investitionen Firma verbessern ihre Lidars von Jahr zur Jahr und das Entwicklung von Konsumgütern ist in der nähe. Du erinnerst dich vielleicht, das Gleiche passiert mit ToF Cameras: Kosten ging von Tausende von Dollars bis ein paar 100$ in ein wenige Jahren (denke an Xbox One Kinect heutzutage).

Es gibt einigen Firmas wie Strobe auf dem Markt. Sie sind noch in ihrer Start-up Phase so es macht Sinn, sie jetzt zu kaufen. Die Entscheidung der Chefs von GM scheint gut wenn man bedenkt, dass andere Firma wird ähnlich tun: Ford investiert in Velodyne im letzen Jahr, Continental kauft ASC, Tesla…

…Nun, Tesls sagt: “Mann, keine Lidars, wir Haben Cameras”.

Und sie sind allein:

Comma.ai shares cool and shiny deep learning videos

Comma.ai teilt neue und glänzend deep learning Videos

Die Firma von George Hotz liebt cameras. Sie sind billig, Man kann finden sie an ein handy und am wichtigsten Sie sind menschliche Augen ähnlich. Und wenn ein Mesnch mit seinen Augen fahren kann, warum sollte dann kein Computer? Das is der gleich Ansatz von Tesla, aber Comma.ai scheint di meinsten seiner Resourcen auf Deep Learning investieren zu.

Zum Beispiel: image segmentation:


… oder, du weißt , ersetyen Lidars für Depth Data:



Es ist interessant das Trainingsdaten wurden geniert mit annotiert Bilden (dank dem adult coloring books Projekt ) und Simulationen

Self Driving Car Weekly Highlights | Week 08/10/17 - 15/10/17 [En]

So, as you can see it’s long time I don’t write anything on this blog. Despite the intention to write short tutorials, personal opinions, and other technical stuff, this blog was always at the bottom of my always-increasing to-do stack. Why am I then committing myself to write a weekly update on the status of self-driving car development and research? Well, as always there is both a practical argument and a leisure reason: I love talking about this field, which is continuously evolving with exciting changes every day, and I am currently learning German, thus I want to exercise my writing skills daily (which, at the moment, are really bad. But fear not: the posts will be published in English too.) This series of post will be mostly based on news published on other websites or forums. So don’t expect to find the latest rumors but, instead, a review of most important events happened during the week.

General Motors buys Strobe, solid-state lidar company

This is definitely the biggest news of the week. We know quite well that many cars manufacturers have high expectations on solid-state lidars: While rotating laser scanner works for research or prototypes (high performance and accuracy, yet high costs) it is unthinkable to use them in actual user mass-production: Moving an array of laser around accurately requires high manufacturing costs and, most importantly continuously high frequency rotating components do not last long for accurate applications. You don’t want users to bring their car back every year for replacing their faulty lidar.

Solid state lidars look really interesting instead: no moving parts, everything fits in a single chip, lower costs and comparable accuracy and range. Only problem: we are not quite there yet. Despite being known for a few years, solid-state lidars are quite hard to implement. Of course, now that money is poured into, companies are improving their lidars year by year and the development of off-the-shelf consumer solution looks near. You may remember the same happened to ToF cameras: cost decreased from thousands of dollars to a couple 100$ in a few years (think about the Xbox One Kinect today).

There are just a few companies like Strobe around. They are still in their start-up phase and thus it make sense to buy them now. We can agree that the decision of GM’s board seems quite good considering that other companies will probably do something similar in the future: Ford invested in Velodyne last year, Continental bought ASC, Tesla…

…Well, Tesla says “Fuck, we don’t use lidars, we have cameras”.

And they are not alone:

Comma.ai shares cool and shiny deep learning videos

George Hotz’s company loves cameras. They are cheap, they can be found on a mobile phone and most importantly they are the closest thing to an human eye. And if a human can drive a car with his eyes, why shouldn’t a computer? This is the same approach of Tesla, expect that Comma.ai seems to invest most of his resources on deep learning. We know that the Line Keeping Assistant algorithm of openpilot is based on an end-to-end deep neural network, but lately the company is looking to exploit DNN for other scopes:

For instance image segmentation:


… or, you know, replacing the previously cited Lidars for depth data



It is interesting to note how the training data was respectively generated by manually annotated images (thanks to the adult coloring books project ) and simulations.

Outline in Unity With Mesh Transparency


This post was originally published on my previous blog


Here I found a shader for Unity to obtain an outline of a mesh.

http://answers.unity3d.com/questions/141229/making-a-silhouette-outline-shader.html

This shader uses a pass to create a slightly bigger mesh behind the original one. This is a good solution (at least in Unity), but only for convex/non transparent object. The fragments of the outline will indeed appear behind the mesh:

transparency-shader

We can remove the fragments behind the mesh modifying the depth buffer with a duplicated object. The original object writes to the z-buffer, so the duplicated object (i.e. the one that act as an outline) will be partially culled by the original one.

In order to obtain this, we can use these shaders:

Transparent shader for the original object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Shader "Outline/Transparent" {
  Properties {
    _color ("Color", Color) = (1,1,1,0.5)
  }

    SubShader {
    Tags {"Queue" = "Geometry+1" }
      Pass {
        Blend SrcAlpha OneMinusSrcAlpha
        Lighting On
        ZWrite On

        Material {
          Diffuse [_color]
        }
      }
    }
}

Outline shader for the outline, it will be applied to the duplicated object (Note: this is a mod of the shader quoted at the begin)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
Shader "Outline/Outline" {
    Properties {
      _OutlineColor ("Outline Color", Color) = (0,0,0,1)
      _Outline ("Outline width", Range (.002, 0.03)) = .005
    }

    CGINCLUDE
    #include "UnityCG.cginc"

    struct appdata {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    };

    struct v2f {
    float4 pos : POSITION;
    float4 color : COLOR;
    };

    uniform float _Outline;
    uniform float4 _OutlineColor;

    v2f vert(appdata v) {
      // just make a copy of incoming vertex data but scaled according to normal direction
      v2f o;
      o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

      float3 norm = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
      float2 offset = TransformViewToProjection(norm.xy);

      o.pos.xy += offset * o.pos.z * _Outline;
      o.color = _OutlineColor;
      return o;
    }
    ENDCG

    SubShader {
    Tags {"Queue" = "Overlay"}

      Pass {
        Name "OUTLINE"
        Tags { "LightMode" = "Always" }
        Cull Front
        ZWrite On
        ZTest Less
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask RGB
        Offset 15,15

        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
          half4 frag(v2f i) :COLOR { return i.color; }
        ENDCG
      }
    }

    SubShader {
      Tags {"Queue" = "Overlay" }
      CGPROGRAM
      #pragma surface surf Lambert

      sampler2D _MainTex;
      fixed4 _Color;

      struct Input {
        float2 uv_MainTex;
      };

      void surf (Input IN, inout SurfaceOutput o) {
        fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
        o.Albedo = c.rgb;
        o.Alpha = c.a;
      }
      ENDCG

      Pass {
        Name "OUTLINE"
        Tags { "LightMode" = "Always" }
        Cull Front
        ZWrite On
        ColorMask RGB
        Blend SrcAlpha OneMinusSrcAlpha

        CGPROGRAM
        #pragma vertex vert
        #pragma exclude_renderers gles xbox360 ps3
        ENDCG
        SetTexture [_MainTex] { combine primary }
      }
    }

    Fallback "Diffuse"
}

The result is pretty good:

shader

Finally, here it is a Unity script that automatically creates the outline effect when applied to an object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using UnityEngine;
using System.Collections;

public class Outliner : MonoBehaviour {

  public Color meshColor = new Color(1f,1f,1f,0.5f);
  public Color outlineColor = new Color(1f,1f,0f,1f);

  // Use this for initialization
  public void Start () {

    // Set the transparent material to this object
    MeshRenderer meshRenderer = GetComponent<meshrenderer>();
    Material[] materials = meshRenderer.materials;
    int materialsNum = materials.Length;
    for(int i = 0; i < materialsNum; i++) {
      materials[i].shader = Shader.Find("Outline/Transparent");
      materials[i].SetColor("_color", meshColor);
    }

    // Create copy of this object, this will have the shader that makes the real outline
    GameObject outlineObj = new GameObject();
    outlineObj.transform.position = transform.position;
    outlineObj.transform.rotation = transform.rotation;
    outlineObj.AddComponent<meshfilter>();
    outlineObj.AddComponent<meshrenderer>();
    Mesh mesh;
    mesh = (Mesh) Instantiate(GetComponent<meshfilter>().mesh);
    outlineObj.GetComponent<meshfilter>().mesh = mesh;

    outlineObj.transform.parent = this.transform;
    materials = new Material[materialsNum];
    for(int i = 0; i < materialsNum; i++) {
      materials[i] = new Material(Shader.Find("Outline/Outline"));
      materials[i].SetColor("_OutlineColor", outlineColor);
    }
    outlineObj.GetComponent<meshrenderer>().materials = materials;

  }

}

Voxelization in Unity


This post was originally published on my previous blog


A few words on Voxelization and SAT

In this post we will create a script for voxelize any kind of mesh in unity. Voxelization could be useful in physical simulations, terrain representation and every situation where we need to manipulate the hollow inside of a mesh. A great post about Voxelization can be found “here”, on Wolfire blog. The post explains how the voxelization of a triangle mesh is done in Overgrowth, we will use the same method in unity.

The creation of a voxel model that reproduces the mesh is achived trough a 3d grid of cubes and an intersection test for each triangle against each cube. The author states that he uses a AABB-AABB intersection test to check if a cube and a triangle are intersected. This is very fast and appropriate for most situations, but we want the general solution.

A slower but precise way to test the intersection is to use the Separating Axis Theorem. This paper explains the use of the SAT for Triangle-AABB intersection.

An implementation in C++ of this algorithm was written by Mike Vandelay and can be found planet-source-code.com. I rewrote the same code in unityscript.

Basically the SAT works like this

Take 13 axes: 3 axes are the cube face normals, 1 axis is the triangle face normal, 9 are the dot product between the first 3 axes and the triangles edges.

Project the AABB and the triangle on each axis. If the projections intersects on an axis, then the AABB and the triangle are intersected, otherwise they aren’t. here a much more detailed explanation of the SAT.

Now, let’s see how implement all this in unity.


Mesh Voxelization

voxelized sphere

The complete script for voxelization can be found here on my github.

We are going to use a grid for creating a voxel model. Each Grid is formed by cubes of the same size, these are the grid properties:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class AABCGrid {

private var side : float;
private var width : short;
private var height : short;
private var depth : short;
private var origin : Vector3;
private var cubeSet : boolean[,,];
private var cubeNormalSum : short[,,];
private var debug = false;

...
}

/*
AABC stands for Axis Aligned Bounding Cube.
For performance purpose, I didn't add a 3 dimension array of AABCs, otherwise each cube had to store the side length, the set value etc...
However an AABC class is defined, but only for external use, while inside the AABCGrid class everything is evaluated starting from the class properties.
e.g. to obtain the vertices of a cube is it possible to use the method
AABCGrid.GetAABCCorners(x : short, y : short, z : short) : Vector3[]
or the method AABC.GetCorners().
The AABC.GetCorners() is actually defined like this:
*/
public function GetCorners(x : short, y : short, z : short) : Vector3[] {
    // grid is a reference to an AABCGrid
    return grid.GetAABCCorners(x, y, z);
}

All the public method call a CheckBound() function that check if the cube specified by the x, y, z variable is inside the grid, then the real implementation of the method is called. e.g.

1
2
3
4
5
6
7
8
public function IsAABCSet(x : short, y : short, z : short) : boolean {
   CheckBounds(x, y, z);
   return IsAABCSetUnchecked(x, y, z);
}

protected function IsAABCSetUnchecked(x : short, y : short, z : short) : boolean {
   return cubeSet[x, y, z];
}

Off course, inside the AABCGrid class and in the possible inheritors, only the unchecked method should be called for faster code.

Creating the voxel shell

Once the grid is defined, we need to ‘set’ all the cubes that are intersected by a triangle of the mesh. This is done in the AABCGrid.FillGridWithGameObjectMeshShell() method.

The result will be a voxel shell, an empty shape that reproduces the mesh.

Ignore the part relative to the normals of the triangles, I’m going to explain that later.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public function FillGridWithGameObjectMeshShell(gameObj : GameObject, storeNormalSum : boolean) {
    var gameObjMesh = gameObj.GetComponent(MeshFilter).mesh;
    var gameObjTransf = gameObj.transform;
    var triangle = new Vector3[3];
    var startTime = Time.realtimeSinceStartup;
    var meshVertices = gameObjMesh.vertices;
    var meshTriangles = gameObjMesh.triangles;
    var meshTrianglesCount = meshTriangles.length / 3;
    var x : short;
    var y : short;
    var z : short;
    var ignoreNormalRange = 0;
    // In this method we can also evaluate stores the normals of the triangles 
    // that intersect the cube.
    if (storeNormalSum) {
        cubeNormalSum = new short [width, height, depth];
    }
    if(debug) {
        Debug.Log("Start:");
        Debug.Log("Time: " + startTime);
        Debug.Log("     Mesh Description: ");
        Debug.Log("Name: " + gameObjMesh.name);
        Debug.Log("Triangles: " + meshTrianglesCount);
        Debug.Log("Local AABB size: " + gameObjMesh.bounds.size);
        Debug.Log("     AABCGrid Description:");
        Debug.Log("Size: " + width + ',' + height + ',' + depth);
    }

    // For each triangle, perform SAT intersection check with the AABCs within the triangle AABB.
    for (var i = 0; i &lt; meshTrianglesCount; ++i) {
        triangle[0] = gameObjTransf.TransformPoint(meshVertices[meshTriangles[i * 3]]);
        triangle[1] = gameObjTransf.TransformPoint(meshVertices[meshTriangles[i * 3 + 1]]);
        triangle[2] = gameObjTransf.TransformPoint(meshVertices[meshTriangles[i * 3 + 2]]);
        // Find the triangle AABB, select a sub grid.
        var startX = Mathf.Floor((Mathf.Min([triangle[0].x, triangle[1].x, triangle[2].x]) - origin.x) / side);
        var startY = Mathf.Floor((Mathf.Min([triangle[0].y, triangle[1].y, triangle[2].y]) - origin.y) / side);
        var startZ = Mathf.Floor((Mathf.Min([triangle[0].z, triangle[1].z, triangle[2].z]) - origin.z) / side);
        var endX = Mathf.Ceil((Mathf.Max([triangle[0].x, triangle[1].x, triangle[2].x]) - origin.x) / side);
        var endY = Mathf.Ceil((Mathf.Max([triangle[0].y, triangle[1].y, triangle[2].y]) - origin.y) / side);
        var endZ = Mathf.Ceil((Mathf.Max([triangle[0].z, triangle[1].z, triangle[2].z]) - origin.z) / side);
        if (storeNormalSum) {
            for (x = startX; x &lt;= endX; ++x) {
                for (y = startY; y &lt;= endY; ++y) {
                    for (z = startZ; z &lt;= endZ; ++z) {
                        if (TriangleIntersectAABC(triangle, x, y, z)) {
                            var triangleNormal = GetTriangleNormal(triangle);
                            cubeSet[x, y, z] = true;
                            if (triangleNormal.z &lt; 0 - ignoreNormalRange) {
                                cubeNormalSum[x, y, z]++;
                            } else if (triangleNormal.z &gt; 0 + ignoreNormalRange){
                                cubeNormalSum[x, y, z]--;
                            }
                        }
                    }
                }
            }
        } else {
            for (x = startX; x &lt; endX; ++x) {
                for (y = startY; y &lt; endY; ++y) {
                    for (z = startZ; z &lt; endZ; ++z) {
                        if (!IsAABCSet(x, y, z) &amp;&amp; TriangleIntersectAABC(triangle, x, y, z)) {
                            cubeSet[x, y, z] = true;
                        }
                    }
                }
            }
        }
    }
    if(debug) {
        Debug.Log("Grid Evaluation Ended!");
        Debug.Log("Time spent: " + (Time.realtimeSinceStartup - startTime) + "s");
        Debug.Log("End: ");
    }
}

The code finds the AABB of each triangle (2), then performs the SAT intersection test on each cube intersected by AABB (3).

triangles (1) the triangle in the grid.  (2) the triangle with its AABB and the AABCs intersected by the AABB. (3) the AABCs intersected by the triangle

Filling the hollow inside

When this method is finished we will have a voxel model that reproduce the mesh. But we have not finished yet, we may need also to know which voxel (AABC) is inside the mesh and which is out. In order to do that we use the scan fill algorithm like the post on overgrowth blog explains, except for a little thing: we don’t start to fill the cube when the normal of the last triangle faces to the left, instead we mark ‘Begin’ and ‘End’ cubes in FillGridWithGameObjectMeshShell(). If the z component of the triangle is positive, we decrease cubeNormalSum[x, y, z] by one, else we increase it. When all the triangles have been processed, a  positive cubeNormalSum means that the cube is a ‘Begin’ cube, if it is negative then the cube is an ‘End’ cube.

We can’t just check the normal of the last triangle because we don’t know the order of the triangles, we neither traverse the entire grid during the creation of the voxel shell.

The method FillGridWithGameObjectMesh() does the real scan lining once that FillGridWithGameObjectMeshShell() ends. It traverses all the grid, starting from the cube at 0, 0, 0. If a ‘Begin’ cube is found, an ‘End’ cube is searched. If an ‘End’ cube is found, all the cubes between the last ‘Begin’ and ‘End’ are set.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public function FillGridWithGameObjectMesh(gameObj : GameObject) {
   FillGridWithGameObjectMeshShell(gameObj, true);

   for (var x = 0; x &lt; width; ++x) {
      for (var y = 0; y &lt; height; ++y) {
         var fill = false;
         var cubeToFill = 0;
         for (var z = 0; z &lt; depth; ++z) {
            if (cubeSet[x, y, z]) {
               var normalSum = cubeNormalSum[x, y, z];
               if (normalSum) {
                  if (normalSum &gt; 0) {
                     // 'Begin' cube
                     fill = true;
                  } else {
                     // 'End' cube
                     fill = false;
                     while (cubeToFill &gt; 1) {
                        cubeToFill--;
                        cubeSet[x, y, z - cubeToFill] = true;
                     }
                  }
               cubeToFill = 0;
             }
             continue;
         }
         if (fill) {
            cubeToFill++;
         }
      }
   }
}
 cubeNormalSum = null;
}

Performance

Performance are mainly determined by the number of triangles in the mesh and the side length of the AABCs. Here they are some of the tests made on my laptop:

Laptop specs: HP g6-1359el Intel Core i5-2450M - 2,5 GHz AMD Radeon HD 7450M

First Test

first

Mesh: construction_worke Time spent: 0.4051636s Triangles: 4020 Cube side: 0.05

Second Test

second

Mesh: construction_worker Time spent: 1.088864s Triangles: 4020 Cube side: 0.02

Third Test

third

Mesh: sphere

Time spent: 1.926165s Triangles:760 Cube side: 0.03

Memory could be saved storing cubeSet using a 3D bitarray class and cubeNormalSum using a 3D array of bytes

Try it yourself

For testing purpose there is also a VoxelizationTest.js script on my github. Attach it to an object with a mesh to try this voxelization script. Remember to enable Gizmos in the game window, otherwise the AABCs will not appear!