Skip to content

Commit

Permalink
List of included changes:
Browse files Browse the repository at this point in the history
  - Draw face mesh z value using different colors
  - Update kotlin-gradle-plugin version

PiperOrigin-RevId: 473150898
Change-Id: I693d4665871154c316442f823725c5afdd9572f7
  • Loading branch information
Google ML Kit authored and Lei Yu committed Sep 9, 2022
1 parent 36254d3 commit bea1576
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@

package com.google.mlkit.vision.demo;

import static java.lang.Math.max;
import static java.lang.Math.min;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Ints;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -32,15 +36,15 @@
* them, triggering the appropriate drawing and invalidation within the view.
*
* <p>Supports scaling and mirroring of the graphics relative the camera's preview properties. The
* idea is that detection items are expressed in terms of an image size, but need to be scaled up
* to the full view size, and also mirrored in the case of the front-facing camera.
* idea is that detection items are expressed in terms of an image size, but need to be scaled up to
* the full view size, and also mirrored in the case of the front-facing camera.
*
* <p>Associated {@link Graphic} items should use the following methods to convert to view
* coordinates for the graphics that are drawn:
*
* <ol>
* <li>{@link Graphic#scale(float)} adjusts the size of the supplied value from the image scale
* to the view scale.
* <li>{@link Graphic#scale(float)} adjusts the size of the supplied value from the image scale to
* the view scale.
* <li>{@link Graphic#translateX(float)} and {@link Graphic#translateY(float)} adjust the
* coordinate from the image's coordinate system to the view coordinate system.
* </ol>
Expand Down Expand Up @@ -143,6 +147,66 @@ public Matrix getTransformationMatrix() {
public void postInvalidate() {
overlay.postInvalidate();
}

/**
* Given the {@code zInImagePixel}, update the color for the passed in {@code paint}. The color will be
* more red if the {@code zInImagePixel} is smaller, or more blue ish vice versa. This is
* useful to visualize the z value of landmarks via color for features like Pose and Face Mesh.
*
* @param paint the paint to update color with
* @param canvas the canvas used to draw with paint
* @param visualizeZ if true, paint color will be changed.
* @param rescaleZForVisualization if true, re-scale the z value with zMin and zMax to make
* color more distinguishable
* @param zInImagePixel the z value used to update the paint color
* @param zMin min value of all z values going to be passed in
* @param zMax max value of all z values going to be passed in
*/
public void updatePaintColorByZValue(
Paint paint,
Canvas canvas,
boolean visualizeZ,
boolean rescaleZForVisualization,
float zInImagePixel,
float zMin,
float zMax) {
if (!visualizeZ) {
return;
}

// When visualizeZ is true, sets up the paint to different colors based on z values.
// Gets the range of z value.
float zLowerBoundInScreenPixel;
float zUpperBoundInScreenPixel;

if (rescaleZForVisualization) {
zLowerBoundInScreenPixel = min(-0.001f, scale(zMin));
zUpperBoundInScreenPixel = max(0.001f, scale(zMax));
} else {
// By default, assume the range of z value in screen pixel is [-canvasWidth, canvasWidth].
float defaultRangeFactor = 1f;
zLowerBoundInScreenPixel = -defaultRangeFactor * canvas.getWidth();
zUpperBoundInScreenPixel = defaultRangeFactor * canvas.getWidth();
}

float zInScreenPixel = scale(zInImagePixel);

if (zInScreenPixel < 0) {
// Sets up the paint to be red if the item is in front of the z origin.
// Maps values within [zLowerBoundInScreenPixel, 0) to [255, 0) and use it to control the
// color. The larger the value is, the more red it will be.
int v = (int) (zInScreenPixel / zLowerBoundInScreenPixel * 255);
v = Ints.constrainToRange(v, 0, 255);
paint.setARGB(255, 255, 255 - v, 255 - v);
} else {
// Sets up the paint to be blue if the item is behind the z origin.
// Maps values within [0, zUpperBoundInScreenPixel] to [0, 255] and use it to control the
// color. The larger the value is, the more blue it will be.
int v = (int) (zInScreenPixel / zUpperBoundInScreenPixel * 255);
v = Ints.constrainToRange(v, 0, 255);
paint.setARGB(255, 255 - v, 255 - v, 255);
}
}
}

public GraphicOverlay(Context context, AttributeSet attrs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public class FaceMeshGraphic extends Graphic {
private final Paint boxPaint;
private volatile FaceMesh faceMesh;
private final int useCase;
private float zMin;
private float zMax;

@ContourType
private static final int[] DISPLAY_CONTOURS = {
Expand Down Expand Up @@ -107,8 +109,23 @@ public void draw(Canvas canvas) {
useCase == USE_CASE_CONTOUR_ONLY ? getContourPoints(faceMesh) : faceMesh.getAllPoints();
List<Triangle<FaceMeshPoint>> triangles = faceMesh.getAllTriangles();

zMin = Float.MAX_VALUE;
zMax = Float.MIN_VALUE;
for (FaceMeshPoint point : points) {
zMin = min(zMin, point.getPosition().getZ());
zMax = max(zMax, point.getPosition().getZ());
}

// Draw face mesh points
for (FaceMeshPoint point : points) {
updatePaintColorByZValue(
positionPaint,
canvas,
/* visualizeZ= */ true,
/* rescaleZForVisualization= */ true,
point.getPosition().getZ(),
zMin,
zMax);
canvas.drawCircle(
translateX(point.getPosition().getX()),
translateY(point.getPosition().getY()),
Expand Down Expand Up @@ -140,6 +157,14 @@ private List<FaceMeshPoint> getContourPoints(FaceMesh faceMesh) {
}

private void drawLine(Canvas canvas, PointF3D point1, PointF3D point2) {
updatePaintColorByZValue(
positionPaint,
canvas,
/* visualizeZ= */ true,
/* rescaleZForVisualization= */ true,
(point1.getZ() + point2.getZ()) / 2,
zMin,
zMax);
canvas.drawLine(
translateX(point1.getX()),
translateY(point1.getY()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import com.google.common.primitives.Ints;
import com.google.mlkit.vision.common.PointF3D;
import com.google.mlkit.vision.demo.GraphicOverlay;
import com.google.mlkit.vision.demo.GraphicOverlay.Graphic;
Expand Down Expand Up @@ -93,13 +92,11 @@ public void draw(Canvas canvas) {
// Draw pose classification text.
float classificationX = POSE_CLASSIFICATION_TEXT_SIZE * 0.5f;
for (int i = 0; i < poseClassification.size(); i++) {
float classificationY = (canvas.getHeight() - POSE_CLASSIFICATION_TEXT_SIZE * 1.5f
* (poseClassification.size() - i));
float classificationY =
(canvas.getHeight()
- POSE_CLASSIFICATION_TEXT_SIZE * 1.5f * (poseClassification.size() - i));
canvas.drawText(
poseClassification.get(i),
classificationX,
classificationY,
classificationTextPaint);
poseClassification.get(i), classificationX, classificationY, classificationTextPaint);
}

// Draw all the points
Expand Down Expand Up @@ -199,9 +196,10 @@ public void draw(Canvas canvas) {
}
}

void drawPoint(Canvas canvas, PoseLandmark landmark, Paint paint) {
void drawPoint(Canvas canvas, PoseLandmark landmark, Paint paint) {
PointF3D point = landmark.getPosition3D();
maybeUpdatePaintColor(paint, canvas, point.getZ());
updatePaintColorByZValue(
paint, canvas, visualizeZ, rescaleZForVisualization, point.getZ(), zMin, zMax);
canvas.drawCircle(translateX(point.getX()), translateY(point.getY()), DOT_RADIUS, paint);
}

Expand All @@ -211,52 +209,14 @@ void drawLine(Canvas canvas, PoseLandmark startLandmark, PoseLandmark endLandmar

// Gets average z for the current body line
float avgZInImagePixel = (start.getZ() + end.getZ()) / 2;
maybeUpdatePaintColor(paint, canvas, avgZInImagePixel);
updatePaintColorByZValue(
paint, canvas, visualizeZ, rescaleZForVisualization, avgZInImagePixel, zMin, zMax);

canvas.drawLine(
translateX(start.getX()),
translateY(start.getY()),
translateX(end.getX()),
translateY(end.getY()),
paint);
}

private void maybeUpdatePaintColor(Paint paint, Canvas canvas, float zInImagePixel) {
if (!visualizeZ) {
return;
}

// When visualizeZ is true, sets up the paint to different colors based on z values.
// Gets the range of z value.
float zLowerBoundInScreenPixel;
float zUpperBoundInScreenPixel;

if (rescaleZForVisualization) {
zLowerBoundInScreenPixel = min(-0.001f, scale(zMin));
zUpperBoundInScreenPixel = max(0.001f, scale(zMax));
} else {
// By default, assume the range of z value in screen pixel is [-canvasWidth, canvasWidth].
float defaultRangeFactor = 1f;
zLowerBoundInScreenPixel = -defaultRangeFactor * canvas.getWidth();
zUpperBoundInScreenPixel = defaultRangeFactor * canvas.getWidth();
}

float zInScreenPixel = scale(zInImagePixel);

if (zInScreenPixel < 0) {
// Sets up the paint to draw the body line in red if it is in front of the z origin.
// Maps values within [zLowerBoundInScreenPixel, 0) to [255, 0) and use it to control the
// color. The larger the value is, the more red it will be.
int v = (int) (zInScreenPixel / zLowerBoundInScreenPixel * 255);
v = Ints.constrainToRange(v, 0, 255);
paint.setARGB(255, 255, 255 - v, 255 - v);
} else {
// Sets up the paint to draw the body line in blue if it is behind the z origin.
// Maps values within [0, zUpperBoundInScreenPixel] to [0, 255] and use it to control the
// color. The larger the value is, the more blue it will be.
int v = (int) (zInScreenPixel / zUpperBoundInScreenPixel * 255);
v = Ints.constrainToRange(v, 0, 255);
paint.setARGB(255, 255 - v, 255 - v, 255);
}
translateX(start.getX()),
translateY(start.getY()),
translateX(end.getX()),
translateY(end.getY()),
paint);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class FaceMeshGraphic(overlay: GraphicOverlay, private val faceMesh: FaceMesh) :
private val positionPaint: Paint
private val boxPaint: Paint
private val useCase: Int
private var zMin: Float
private var zMax: Float

@FaceMesh.ContourType
private val DISPLAY_CONTOURS =
Expand Down Expand Up @@ -68,15 +70,28 @@ class FaceMeshGraphic(overlay: GraphicOverlay, private val faceMesh: FaceMesh) :
rect.bottom = translateY(rect.bottom)
canvas.drawRect(rect, boxPaint)

// Draw face mesh

// Draw face mesh
val points =
if (useCase == USE_CASE_CONTOUR_ONLY) getContourPoints(faceMesh) else faceMesh.allPoints
val triangles = faceMesh.allTriangles

zMin = Float.MAX_VALUE
zMax = Float.MIN_VALUE
for (point in points) {
zMin = Math.min(zMin, point.position.z)
zMax = Math.max(zMax, point.position.z)
}

// Draw face mesh points
for (point in points) {
updatePaintColorByZValue(
positionPaint,
canvas,
/* visualizeZ= */true,
/* rescaleZForVisualization= */true,
point.position.z,
zMin,
zMax)
canvas.drawCircle(
translateX(point.position.x),
translateY(point.position.y),
Expand All @@ -99,6 +114,14 @@ class FaceMeshGraphic(overlay: GraphicOverlay, private val faceMesh: FaceMesh) :
}

private fun drawLine(canvas: Canvas, point1: PointF3D, point2: PointF3D) {
updatePaintColorByZValue(
positionPaint,
canvas,
/* visualizeZ= */true,
/* rescaleZForVisualization= */true,
(point1.z + point2.z) / 2,
zMin,
zMax)
canvas.drawLine(
translateX(point1.x),
translateY(point1.y),
Expand Down Expand Up @@ -133,5 +156,7 @@ class FaceMeshGraphic(overlay: GraphicOverlay, private val faceMesh: FaceMesh) :
boxPaint.strokeWidth = BOX_STROKE_WIDTH

useCase = PreferenceUtils.getFaceMeshUseCase(applicationContext)
zMin = java.lang.Float.MAX_VALUE
zMax = java.lang.Float.MIN_VALUE
}
}
Loading

0 comments on commit bea1576

Please sign in to comment.