[英]Detecting contours of multiple faces via Firebase ML Kit face detection
[英]Why is Google's ML Face Detection Kit crashing on .process()
我正在创建一个面部检测器应用程序,它将实时检测面部并识别面部上的地标。 人脸的地标工作得很好,但是我的实时人脸检测根本不起作用。
我按照 Google 的 ML 套件( https://developers.google.com/ml-kit/vision/face-detection/android )中的说明进行操作,但我真的很难获得实时人脸检测的功能。
在我的调试器中,代码在facedetector.process(image).addOnSuccessListener()
崩溃,而是进入onFailure()
这是我的实时人脸检测部分的代码(我已经评论了一些部分+减少了冗余)。
@Override
//process method to detect frame by frame in real time face detection
public void process(@NonNull Frame frame) {
int width = frame.getSize().getWidth();
int height = frame.getSize().getHeight();
InputImage image = InputImage.fromByteArray(
frame.getData(),
/* image width */width,
/* image height */height,
//if camera is facing front rotate image 90, else 270 degrees
(cameraFacing != Facing.FRONT) ? 90 : 270,
InputImage.IMAGE_FORMAT_YUV_420_888 // or IMAGE_FORMAT_YV12
);
FaceDetectorOptions faceDetectorOptions = new FaceDetectorOptions.Builder()
.setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
//setting contour mode to detect all facial contours in real time
.build();
FaceDetector faceDetector = FaceDetection.getClient(faceDetectorOptions);
faceDetector.process(image).addOnSuccessListener(new OnSuccessListener<List<Face>>() {
@Override
public void onSuccess(@NonNull List<Face> faces) {
imageView.setImageBitmap(null);
Bitmap bitmap = Bitmap.createBitmap(height, width, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint dotPaint = new Paint();
dotPaint.setColor(Color.YELLOW);
dotPaint.setStyle(Paint.Style.FILL);
dotPaint.setStrokeWidth(6f);
Paint linePaint = new Paint();
linePaint.setColor(Color.GREEN);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setStrokeWidth(4f);
//looping through each face to detect each contour
for (Face face : faces) {
List<PointF> faceContours = face.getContour(
FaceContour.FACE
).getPoints();
for (int i = 0; i < faceContours.size(); i++) {
PointF faceContour = null;
if (i != (faceContours.size() - 1)) {
faceContour = faceContours.get(i);
canvas.drawLine(
faceContour.x, faceContour.y, faceContours.get(i + 1).x, faceContours.get(i + 1).y, linePaint
);
} else {//if at the last point, draw to the first point
canvas.drawLine(faceContour.x, faceContour.y, faceContours.get(0).x, faceContours.get(0).y, linePaint);
}
canvas.drawCircle(faceContour.x, faceContour.y, 4f, dotPaint);
}//end inner loop
List<PointF> leftEyebrowTopCountours = face.getContour(
FaceContour.LEFT_EYEBROW_TOP).getPoints();
for (int i = 0; i < leftEyebrowTopCountours.size(); i++) {
PointF leftEyebrowTopContour = leftEyebrowTopCountours.get(i);
if (i != (leftEyebrowTopCountours.size() - 1))
canvas.drawLine(leftEyebrowTopContour.x, leftEyebrowTopContour.y, leftEyebrowTopCountours.get(i + 1).x, leftEyebrowTopCountours.get(i + 1).y, linePaint);
canvas.drawCircle(leftEyebrowTopContour.x, leftEyebrowTopContour.y, 4f, dotPaint);
}
}
}
旁注:我在模拟器中使用 Pixel 2 API 29。 我省略了重复的代码,因为我只是通过轮廓
完整代码供参考:
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.face.Face;
import com.google.mlkit.vision.face.FaceContour;
import com.google.mlkit.vision.face.FaceDetection;
import com.google.mlkit.vision.face.FaceDetector;
import com.google.mlkit.vision.face.FaceDetectorOptions;
import com.google.mlkit.vision.face.FaceLandmark;
import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameProcessor;
import com.theartofdev.edmodo.cropper.CropImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class MainActivity extends AppCompatActivity implements FrameProcessor {
private Facing cameraFacing = Facing.FRONT;
private ImageView imageView;
private CameraView faceDetectionCameraView;
private RecyclerView bottomSheetRecyclerView;
private BottomSheetBehavior bottomSheetBehavior;
private ArrayList<FaceDetectionModel> faceDetectionModels;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
faceDetectionModels = new ArrayList<>();
bottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.bottom_sheet));
imageView = findViewById(R.id.face_detection_image_view);
faceDetectionCameraView = findViewById(R.id.face_detection_camera_view);
Button toggle = findViewById(R.id.face_detection_cam_toggle_button);
FrameLayout bottomSheetButton = findViewById(R.id.bottom_sheet_button);
bottomSheetRecyclerView = findViewById(R.id.bottom_sheet_recycler_view);
faceDetectionCameraView.setFacing(cameraFacing);
faceDetectionCameraView.setLifecycleOwner(MainActivity.this);
faceDetectionCameraView.addFrameProcessor(MainActivity.this);
toggle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cameraFacing = (cameraFacing == Facing.FRONT) ? Facing.BACK : Facing.FRONT;
faceDetectionCameraView.setFacing(cameraFacing);
}
});
bottomSheetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CropImage.activity().start(MainActivity.this);
}
});
bottomSheetRecyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
bottomSheetRecyclerView.setAdapter(new FaceDetectionAdapter(faceDetectionModels, MainActivity.this));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE){
CropImage.ActivityResult result = CropImage.getActivityResult(data);
if(resultCode == RESULT_OK){
Uri imageUri = result.getUri();
try {
analyseImage(MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void analyseImage(Bitmap bitmap) {
if(bitmap == null){
Toast.makeText(this, "There was an error", Toast.LENGTH_SHORT).show();
}
//imageView.setImageBitmap(null);
faceDetectionModels.clear();
Objects.requireNonNull(bottomSheetRecyclerView.getAdapter()).notifyDataSetChanged();
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
showProgress();
InputImage firebaseInputImage = InputImage.fromBitmap(bitmap, 0);
FaceDetectorOptions options =
new FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
.build();
FaceDetector faceDetector = FaceDetection.getClient(options);
faceDetector.process(firebaseInputImage)
.addOnSuccessListener(new OnSuccessListener<List<Face>>() {
@Override
public void onSuccess(@NonNull List<Face> faces) {
Bitmap mutableImage = bitmap.copy(Bitmap.Config.ARGB_8888, true);
detectFaces(faces, mutableImage);
imageView.setImageBitmap(mutableImage);
hideProgress();
bottomSheetRecyclerView.getAdapter().notifyDataSetChanged();
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Toast.makeText(MainActivity.this, "There was an error", Toast.LENGTH_SHORT).show();
hideProgress();
}
});
}
private void detectFaces(List<Face> faces, Bitmap bitmap) {
if(faces == null || bitmap == null) {
Toast.makeText(this, "There was an error", Toast.LENGTH_SHORT).show();
return;
}
Canvas canvas = new Canvas(bitmap);
Paint facePaint = new Paint();
facePaint.setColor(Color.GREEN);
facePaint.setStyle(Paint.Style.STROKE);
facePaint.setStrokeWidth(5f);
Paint faceTextPaint = new Paint();
faceTextPaint.setColor(Color.BLUE);
faceTextPaint.setTextSize(30f);
faceTextPaint.setTypeface(Typeface.SANS_SERIF);
Paint landmarkPaint = new Paint();
landmarkPaint.setColor(Color.YELLOW);
landmarkPaint.setStyle(Paint.Style.FILL);
landmarkPaint.setStrokeWidth(8f);
for(int i = 0; i < faces.size(); i++){
canvas.drawRect(faces.get(i).getBoundingBox(), facePaint);
canvas.drawText("Face" + i,
(faces.get(i).getBoundingBox().centerX()
-(faces.get(i).getBoundingBox().width() >> 1) + 8f),
(faces.get(i).getBoundingBox().centerY() + (faces.get(i).getBoundingBox().height() >> 1) - 8f), facePaint);
Face face = faces.get(i); //get one face
if(face.getLandmark(FaceLandmark.LEFT_EYE) != null){
FaceLandmark leftEye = face.getLandmark(FaceLandmark.LEFT_EYE);
//Now we have our left eye, we draw a little circle
canvas.drawCircle(leftEye.getPosition().x, leftEye.getPosition().y, 8f, landmarkPaint);
}
if(face.getLandmark(FaceLandmark.RIGHT_EYE) != null){
FaceLandmark rightEye = face.getLandmark(FaceLandmark.RIGHT_EYE);
//Now we have our left eye, we draw a little circle
canvas.drawCircle(rightEye.getPosition().x, rightEye.getPosition().y, 8f, landmarkPaint);
}
if(face.getLandmark(FaceLandmark.NOSE_BASE) != null){
FaceLandmark noseBase = face.getLandmark(FaceLandmark.NOSE_BASE);
//Now we have our left eye, we draw a little circle
canvas.drawCircle(noseBase.getPosition().x, noseBase.getPosition().y, 8f, landmarkPaint);
}
if(face.getLandmark(FaceLandmark.LEFT_EAR) != null){
FaceLandmark leftEar = face.getLandmark(FaceLandmark.LEFT_EAR);
//Now we have our left eye, we draw a little circle
canvas.drawCircle(leftEar.getPosition().x, leftEar.getPosition().y, 8f, landmarkPaint);
}
if(face.getLandmark(FaceLandmark.RIGHT_EAR) != null){
FaceLandmark rightEar = face.getLandmark(FaceLandmark.RIGHT_EAR);
//Now we have our left eye, we draw a little circle
canvas.drawCircle(rightEar.getPosition().x, rightEar.getPosition().y, 8f, landmarkPaint);
}
if(face.getLandmark(FaceLandmark.MOUTH_LEFT) != null && face.getLandmark(FaceLandmark.MOUTH_BOTTOM) != null && face.getLandmark(FaceLandmark.MOUTH_RIGHT) != null){
FaceLandmark mouthLeft = face.getLandmark(FaceLandmark.MOUTH_LEFT);
FaceLandmark mouthRight = face.getLandmark(FaceLandmark.MOUTH_RIGHT);
FaceLandmark mouthBottom = face.getLandmark(FaceLandmark.MOUTH_BOTTOM);
//Now we have our left eye, we draw a little circle
canvas.drawLine(mouthLeft.getPosition().x, mouthLeft.getPosition().y, mouthBottom.getPosition().x, mouthBottom.getPosition().y, landmarkPaint);
canvas.drawLine(mouthBottom.getPosition().x, mouthBottom.getPosition().y, mouthRight.getPosition().x, mouthRight.getPosition().y, landmarkPaint);
}
faceDetectionModels.add(new FaceDetectionModel(i, "Smiling probability"
+ face.getSmilingProbability()));
faceDetectionModels.add(new FaceDetectionModel(i, "Left eye open probability"
+ face.getLeftEyeOpenProbability()));
faceDetectionModels.add(new FaceDetectionModel(i, "Right eye open probability"
+ face.getRightEyeOpenProbability()));
}
}
private void showProgress() {
findViewById(R.id.bottom_sheet_button_img).setVisibility(View.GONE);
findViewById(R.id.bottom_sheet_butotn_progress_bar).setVisibility(View.VISIBLE);
}
private void hideProgress() {
findViewById(R.id.bottom_sheet_button_img).setVisibility(View.VISIBLE);
findViewById(R.id.bottom_sheet_butotn_progress_bar).setVisibility(View.GONE);
}
//real-time detection starts HERE
@Override
public void process(@NonNull Frame frame) {
//setting up width and frame height
int width = frame.getSize().getWidth();
int height = frame.getSize().getHeight();
byte[] byteArray = frame.getData();
InputImage image = InputImage.fromByteArray(
//frame.getData()
byteArray,
width,
height,
//rotation
(cameraFacing == Facing.FRONT) ? 90 : 270,
//image format
InputImage.IMAGE_FORMAT_YV12 // or IMAGE_FORMAT_YV12
);
//Contour mode all is real time contour detection
FaceDetectorOptions realTimeOpts = new FaceDetectorOptions.Builder()
.setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
.build();
FaceDetector faceDetector = FaceDetection.getClient(realTimeOpts);
faceDetector.process(image).addOnSuccessListener(new OnSuccessListener<List<Face>>() {
@Override
public void onSuccess(@NonNull List<Face> faces) {
//don't have image yet set to null first
imageView.setImageBitmap(null);
//bitmap stores pixels of image
Bitmap bitmap = Bitmap.createBitmap(height, width, Bitmap.Config.ARGB_8888);
//canvas hold the draw calls -- write into the bitmap
Canvas canvas = new Canvas(bitmap);
//paint specifies what the canvas should draw
Paint dotPaint = new Paint();
dotPaint.setColor(Color.YELLOW);
dotPaint.setStyle(Paint.Style.FILL);
dotPaint.setStrokeWidth(6f);
Paint linePaint = new Paint();
linePaint.setColor(Color.GREEN);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setStrokeWidth(4f);
for (Face face : faces) {
//fetching contours
List<PointF> faceContours = face.getContour(
FaceContour.FACE
).getPoints();
for (int i = 0; i < faceContours.size(); i++) {
PointF faceContour = faceContours.get(i);
if (i != (faceContours.size() - 1)) {
canvas.drawLine(
//if not at last index, continue drawing to next index
faceContour.x, faceContour.y, faceContours.get(i + 1).x, faceContours.get(i + 1).y, linePaint
);
} else {
return;
}
//always draw circle
canvas.drawCircle(faceContour.x, faceContour.y, 4f, dotPaint);
}//end inner loop
List<PointF> leftEyebrowTopCountours = face.getContour(
FaceContour.LEFT_EYEBROW_TOP).getPoints();
for (int i = 0; i < leftEyebrowTopCountours.size(); i++) {
PointF leftEyebrowTopContour = leftEyebrowTopCountours.get(i);
if (i != (leftEyebrowTopCountours.size() - 1)) {
canvas.drawLine(leftEyebrowTopContour.x, leftEyebrowTopContour.y, leftEyebrowTopCountours.get(i + 1).x, leftEyebrowTopCountours.get(i + 1).y, linePaint);
}else{
return;
}
canvas.drawCircle(leftEyebrowTopContour.x, leftEyebrowTopContour.y, 4f, dotPaint);
}
List<PointF> rightEyebrowTopCountours = face.getContour(
FaceContour.RIGHT_EYEBROW_TOP).getPoints();
for (int i = 0; i < rightEyebrowTopCountours.size(); i++) {
PointF rightEyebrowContour = rightEyebrowTopCountours.get(i);
if (i != (rightEyebrowTopCountours.size() - 1)) {
canvas.drawLine(rightEyebrowContour.x, rightEyebrowContour.y, rightEyebrowTopCountours.get(i + 1).x, rightEyebrowTopCountours.get(i + 1).y, linePaint);
}else{
return;
}
canvas.drawCircle(rightEyebrowContour.x, rightEyebrowContour.y, 4f, dotPaint);
}
List<PointF> rightEyebrowBottomCountours = face.getContour(
FaceContour.RIGHT_EYEBROW_BOTTOM).getPoints();
for (int i = 0; i < rightEyebrowBottomCountours.size(); i++) {
PointF rightEyebrowBottomContour = rightEyebrowBottomCountours.get(i);
if (i != (rightEyebrowBottomCountours.size() - 1)) {
canvas.drawLine(rightEyebrowBottomContour.x, rightEyebrowBottomContour.y, rightEyebrowBottomCountours.get(i + 1).x, rightEyebrowBottomCountours.get(i + 1).y, linePaint);
}else{
return;
}
canvas.drawCircle(rightEyebrowBottomContour.x, rightEyebrowBottomContour.y, 4f, dotPaint);
}
List<PointF> leftEyeContours = face.getContour(
FaceContour.LEFT_EYE).getPoints();
for (int i = 0; i < leftEyeContours.size(); i++) {
PointF leftEyeContour = leftEyeContours.get(i);
if (i != (leftEyeContours.size() - 1)) {
canvas.drawLine(leftEyeContour.x, leftEyeContour.y, leftEyeContours.get(i + 1).x, leftEyeContours.get(i + 1).y, linePaint);
} else {
return;
}
canvas.drawCircle(leftEyeContour.x, leftEyeContour.y, 4f, dotPaint);
}
List<PointF> rightEyeContours = face.getContour(
FaceContour.RIGHT_EYE).getPoints();
for (int i = 0; i < rightEyeContours.size(); i++) {
PointF rightEyeContour = rightEyeContours.get(i);
if (i != (rightEyeContours.size() - 1)) {
canvas.drawLine(rightEyeContour.x, rightEyeContour.y, rightEyeContours.get(i + 1).x, rightEyeContours.get(i + 1).y, linePaint);
} else {
return;
}
canvas.drawCircle(rightEyeContour.x, rightEyeContour.y, 4f, dotPaint);
}
List<PointF> upperLipTopContour = face.getContour(
FaceContour.UPPER_LIP_TOP).getPoints();
for (int i = 0; i < upperLipTopContour.size(); i++) {
PointF upperLipContour = upperLipTopContour.get(i);
if (i != (upperLipTopContour.size() - 1)) {
canvas.drawLine(upperLipContour.x, upperLipContour.y,
upperLipTopContour.get(i + 1).x,
upperLipTopContour.get(i + 1).y, linePaint);
}else{
return;
}
canvas.drawCircle(upperLipContour.x, upperLipContour.y, 4f, dotPaint);
}
List<PointF> upperLipBottomContour = face.getContour(
FaceContour.UPPER_LIP_BOTTOM).getPoints();
for (int i = 0; i < upperLipBottomContour.size(); i++) {
PointF upBottom = upperLipBottomContour.get(i);
if (i != (upperLipBottomContour.size() - 1)) {
canvas.drawLine(upBottom.x, upBottom.y, upperLipBottomContour.get(i + 1).x, upperLipBottomContour.get(i + 1).y, linePaint);
}else{
return;
}
canvas.drawCircle(upBottom.x, upBottom.y, 4f, dotPaint);
}
List<PointF> lowerLipTopContour = face.getContour(
FaceContour.LOWER_LIP_TOP).getPoints();
for (int i = 0; i < lowerLipTopContour.size(); i++) {
PointF lowerTop = lowerLipTopContour.get(i);
if (i != (lowerLipTopContour.size() - 1)) {
canvas.drawLine(lowerTop.x, lowerTop.y, lowerLipTopContour.get(i + 1).x, lowerLipTopContour.get(i + 1).y, linePaint);
}
else{
return;
}
canvas.drawCircle(lowerTop.x, lowerTop.y, 4f, dotPaint);
}
List<PointF> lowerLipBottomContour = face.getContour(
FaceContour.LOWER_LIP_BOTTOM).getPoints();
for (int i = 0; i < lowerLipBottomContour.size(); i++) {
PointF lowerBottom = lowerLipBottomContour.get(i);
if (i != (lowerLipBottomContour.size() - 1)) {
canvas.drawLine(lowerBottom.x, lowerBottom.y, lowerLipBottomContour.get(i + 1).x, lowerLipBottomContour.get(i + 1).y, linePaint);
}else{
return;
}
canvas.drawCircle(lowerBottom.x, lowerBottom.y, 4f, dotPaint);
}
List<PointF> noseBridgeContours = face.getContour(
FaceContour.NOSE_BRIDGE).getPoints();
for (int i = 0; i < noseBridgeContours.size(); i++) {
PointF noseBridge = noseBridgeContours.get(i);
if (i != (noseBridgeContours.size() - 1)) {
canvas.drawLine(noseBridge.x, noseBridge.y, noseBridgeContours.get(i + 1).x, noseBridgeContours.get(i + 1).y, linePaint);
}else{
return;
}
canvas.drawCircle(noseBridge.x, noseBridge.y, 4f, dotPaint);
}
List<PointF> noseBottomContours = face.getContour(
FaceContour.NOSE_BOTTOM).getPoints();
for (int i = 0; i < noseBottomContours.size(); i++) {
PointF noseBottom = noseBottomContours.get(i);
if (i != (noseBottomContours.size() - 1)) {
canvas.drawLine(noseBottom.x, noseBottom.y, noseBottomContours.get(i + 1).x, noseBottomContours.get(i + 1).y, linePaint);
}else{
return;
}
canvas.drawCircle(noseBottom.x, noseBottom.y, 4f, dotPaint);
//facing front flip image
if (cameraFacing == Facing.FRONT) {
//Flip image!
Matrix matrix = new Matrix();
matrix.preScale(-1f, 1f);
Bitmap flippedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(),
matrix, true);
imageView.setImageBitmap(flippedBitmap);
} else {
imageView.setImageBitmap(bitmap);
}
}//end outer loop
canvas.save();
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure (@NonNull Exception e){
imageView.setImageBitmap(null);
}
});
}
}
编辑:我收到此错误
Getting this error now: 2021-04-27 19:12:05.335 538-1065/system_process E/JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.)
java.lang.RuntimeException: android.os.RemoteException: Couldn't get ApplicationInfo for package android.frameworks.sensorservice@1.0::ISensorManager
at android.os.Parcel.writeException(Parcel.java:2158)
at android.os.Binder.execTransactInternal(Binder.java:1178)
at android.os.Binder.execTransact(Binder.java:1123)
Caused by: android.os.RemoteException: Couldn't get ApplicationInfo for package android.frameworks.sensorservice@1.0::ISensorManager
at com.android.server.pm.PackageManagerService$PackageManagerNative.getTargetSdkVersionForPackage(PackageManagerService.java:23957)
at android.content.pm.IPackageManagerNative$Stub.onTransact(IPackageManagerNative.java:255)
at android.os.Binder.execTransactInternal(Binder.java:1159)
at android.os.Binder.execTransact(Binder.java:1123)
太感谢了!!
如果您想将相机流 output 与 MLKit 一起使用,您可以使用 CameraX ImageAnalysis 用例。 它生成 YUV_420_888 格式的 android.media.Image,可以直接转换为 mlkit InputImage。
或者,您也可以使用 ML Kit 刚刚发布的 CameraXSource 库。 示例代码在这里。 这消除了您设置 camerax 用例的样板代码,并从 cameraX output 在内部为您创建 MLKit 输入。 请注意,这仍然是一个测试版 SDK。 我们期待您的反馈。
为了使用 API,您需要将以下依赖项添加到您的应用程序中:
实施 'com.google.mlkit:camera:16.0.0-beta1'
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.