使用视觉api在脸上拍摄可绘画/油漆

时间:2017-07-31 05:18:56

标签: android google-vision vision-api

我在尝试什么?

我试图在脸上画上可绘画/油漆,但是我无法在同一张照片上拍摄。

enter image description here

我尝试了什么?

我尝试使用CameraSource.takePicture,但我只是脸上没有任何可绘画/油漆。

mCameraSource.takePicture(shutterCallback, new CameraSource.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] bytes) {
                try {
                    String mainpath = getExternalStorageDirectory() + separator + "TestXyz" + separator + "images" + separator;
                    File basePath = new File(mainpath);
                    if (!basePath.exists())
                        Log.d("CAPTURE_BASE_PATH", basePath.mkdirs() ? "Success": "Failed");
                    String path = mainpath + "photo_" + getPhotoTime() + ".jpg";
                    File captureFile = new File(path);
                    captureFile.createNewFile();
                    if (!captureFile.exists())
                        Log.d("CAPTURE_FILE_PATH", captureFile.createNewFile() ? "Success": "Failed");
                    FileOutputStream stream = new FileOutputStream(captureFile);
                    stream.write(bytes);
                    stream.flush();
                    stream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

我也尝试过使用:

mPreview.setDrawingCacheEnabled(true);
        Bitmap drawingCache = mPreview.getDrawingCache();
        try {
            String mainpath = getExternalStorageDirectory() + separator + "TestXyz" + separator + "images" + separator;
            File basePath = new File(mainpath);
            if (!basePath.exists())
                Log.d("CAPTURE_BASE_PATH", basePath.mkdirs() ? "Success": "Failed");
            String path = mainpath + "photo_" + getPhotoTime() + ".jpg";
            File captureFile = new File(path);
            captureFile.createNewFile();
            if (!captureFile.exists())
                Log.d("CAPTURE_FILE_PATH", captureFile.createNewFile() ? "Success": "Failed");
            FileOutputStream stream = new FileOutputStream(captureFile);
            drawingCache.compress(Bitmap.CompressFormat.PNG, 100, stream);
            stream.flush();
            stream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

在这种情况下,我只能得到我在脸上画的东西。这里,mPreview是CameraSourcePreview

刚刚添加了捕获按钮,并在this google示例中添加了上述代码。

3 个答案:

答案 0 :(得分:11)

你非常接近你所需要的东西:)

你有:

  1. 脸部相机的图像(第一个代码段)
  2. 来自眼睛画布的图像叠加(第二个代码段)
  3. 你需要什么:

    • 将眼睛覆盖在脸上的图像 - 合并的图像。

    如何合并?

    要合并2个图像,只需使用画布,如下所示:

    public Bitmap mergeBitmaps(Bitmap face, Bitmap overlay) {
        // Create a new image with target size
        int width = face.getWidth();
        int height = face.getHeight();
        Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    
        Rect faceRect = new Rect(0,0,width,height);
        Rect overlayRect = new Rect(0,0,overlay.getWidth(),overlay.getHeight());
    
        // Draw face and then overlay (Make sure rects are as needed)
        Canvas canvas = new Canvas(newBitmap); 
        canvas.drawBitmap(face, faceRect, faceRect, null);
        canvas.drawBitmap(overlay, overlayRect, faceRect, null);
        return newBitmap
    }
    

    然后你可以像现在一样保存新图像。

    完整代码如下所示:

    mCameraSource.takePicture(shutterCallback, new 
    CameraSource.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] bytes) {
                // Generate the Face Bitmap
                BitmapFactory.Options options = new BitmapFactory.Options();
                Bitmap face = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
    
                // Generate the Eyes Overlay Bitmap
                mPreview.setDrawingCacheEnabled(true);
                Bitmap overlay = mPreview.getDrawingCache();
    
                // Generate the final merged image
                Bitmap result = mergeBitmaps(face, overlay);
    
                // Save result image to file
                try {
                    String mainpath = getExternalStorageDirectory() + separator + "TestXyz" + separator + "images" + separator;
                    File basePath = new File(mainpath);
                    if (!basePath.exists())
                        Log.d("CAPTURE_BASE_PATH", basePath.mkdirs() ? "Success": "Failed");
                    String path = mainpath + "photo_" + getPhotoTime() + ".jpg";
                    File captureFile = new File(path);
                    captureFile.createNewFile();
                    if (!captureFile.exists())
                        Log.d("CAPTURE_FILE_PATH", captureFile.createNewFile() ? "Success": "Failed");
                    FileOutputStream stream = new FileOutputStream(captureFile);
                    result.compress(Bitmap.CompressFormat.PNG, 100, stream);
                    stream.flush();
                    stream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    

    请注意,上面只是一个示例代码。 您应该将合并并保存到文件到后台线程。

答案 1 :(得分:5)

我可以通过以下解决方案捕获带有drawable / paint的图像:

private void captureImage() {
        mPreview.setDrawingCacheEnabled(true);
        Bitmap drawingCache = mPreview.getDrawingCache();

        mCameraSource.takePicture(shutterCallback, new CameraSource.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] bytes) {
                int orientation = Exif.getOrientation(bytes);
                Bitmap temp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                Bitmap picture = rotateImage(temp,orientation);
                Bitmap overlay = Bitmap.createBitmap(mGraphicOverlay.getWidth(),mGraphicOverlay.getHeight(),picture.getConfig());
                Canvas canvas = new Canvas(overlay);

                Matrix matrix = new Matrix();

                matrix.setScale((float)overlay.getWidth()/(float)picture.getWidth(),(float)overlay.getHeight()/(float)picture.getHeight());

                // mirror by inverting scale and translating
                matrix.preScale(-1, 1);
                matrix.postTranslate(canvas.getWidth(), 0);

                Paint paint = new Paint();
                canvas.drawBitmap(picture,matrix,paint);
                canvas.drawBitmap(drawingCache,0,0,paint);

                try {
                    String mainpath = getExternalStorageDirectory() + separator + "MaskIt" + separator + "images" + separator;
                    File basePath = new File(mainpath);
                    if (!basePath.exists())
                        Log.d("CAPTURE_BASE_PATH", basePath.mkdirs() ? "Success": "Failed");
                    String path = mainpath + "photo_" + getPhotoTime() + ".jpg";
                    File captureFile = new File(path);
                    captureFile.createNewFile();
                    if (!captureFile.exists())
                        Log.d("CAPTURE_FILE_PATH", captureFile.createNewFile() ? "Success": "Failed");
                    FileOutputStream stream = new FileOutputStream(captureFile);
                    overlay.compress(Bitmap.CompressFormat.PNG, 100, stream);
                    stream.flush();
                    stream.close();
                    picture.recycle();
                    drawingCache.recycle();
                    mPreview.setDrawingCacheEnabled(false);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

有时在某些设备上也会出现方向问题。为此,我使用了Exif类和rotateImage()函数。

Exif类(来自here的参考):

public class Exif {
    private static final String TAG = "CameraExif";

    // Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
    public static int getOrientation(byte[] jpeg) {
        if (jpeg == null) {
            return 0;
        }

        int offset = 0;
        int length = 0;

        // ISO/IEC 10918-1:1993(E)
        while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
            int marker = jpeg[offset] & 0xFF;

            // Check if the marker is a padding.
            if (marker == 0xFF) {
                continue;
            }
            offset++;

            // Check if the marker is SOI or TEM.
            if (marker == 0xD8 || marker == 0x01) {
                continue;
            }
            // Check if the marker is EOI or SOS.
            if (marker == 0xD9 || marker == 0xDA) {
                break;
            }

            // Get the length and check if it is reasonable.
            length = pack(jpeg, offset, 2, false);
            if (length < 2 || offset + length > jpeg.length) {
                Log.e(TAG, "Invalid length");
                return 0;
            }

            // Break if the marker is EXIF in APP1.
            if (marker == 0xE1 && length >= 8 &&
                    pack(jpeg, offset + 2, 4, false) == 0x45786966 &&
                    pack(jpeg, offset + 6, 2, false) == 0) {
                offset += 8;
                length -= 8;
                break;
            }

            // Skip other markers.
            offset += length;
            length = 0;
        }

        // JEITA CP-3451 Exif Version 2.2
        if (length > 8) {
            // Identify the byte order.
            int tag = pack(jpeg, offset, 4, false);
            if (tag != 0x49492A00 && tag != 0x4D4D002A) {
                Log.e(TAG, "Invalid byte order");
                return 0;
            }
            boolean littleEndian = (tag == 0x49492A00);

            // Get the offset and check if it is reasonable.
            int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
            if (count < 10 || count > length) {
                Log.e(TAG, "Invalid offset");
                return 0;
            }
            offset += count;
            length -= count;

            // Get the count and go through all the elements.
            count = pack(jpeg, offset - 2, 2, littleEndian);
            while (count-- > 0 && length >= 12) {
                // Get the tag and check if it is orientation.
                tag = pack(jpeg, offset, 2, littleEndian);
                if (tag == 0x0112) {
                    // We do not really care about type and count, do we?
                    int orientation = pack(jpeg, offset + 8, 2, littleEndian);
                    switch (orientation) {
                        case 1:
                            return 0;
                        case 3:
                            return 3;
                        case 6:
                            return 6;
                        case 8:
                            return 8;
                    }
                    Log.i(TAG, "Unsupported orientation");
                    return 0;
                }
                offset += 12;
                length -= 12;
            }
        }

        Log.i(TAG, "Orientation not found");
        return 0;
    }

    private static int pack(byte[] bytes, int offset, int length,
            boolean littleEndian) {
        int step = 1;
        if (littleEndian) {
            offset += length - 1;
            step = -1;
        }

        int value = 0;
        while (length-- > 0) {
            value = (value << 8) | (bytes[offset] & 0xFF);
            offset += step;
        }
        return value;
    }
}

rotateImage功能:

    private Bitmap rotateImage(Bitmap bm, int i) {
        Matrix matrix = new Matrix();
        switch (i) {
            case ExifInterface.ORIENTATION_NORMAL:
                return bm;
            case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
                matrix.setScale(-1, 1);
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                matrix.setRotate(180);
                break;
            case ExifInterface.ORIENTATION_FLIP_VERTICAL:
                matrix.setRotate(180);
                matrix.postScale(-1, 1);
                break;
            case ExifInterface.ORIENTATION_TRANSPOSE:
                matrix.setRotate(90);
                matrix.postScale(-1, 1);
                break;
            case ExifInterface.ORIENTATION_ROTATE_90:
                matrix.setRotate(90);
                break;
            case ExifInterface.ORIENTATION_TRANSVERSE:
                matrix.setRotate(-90);
                matrix.postScale(-1, 1);
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
                matrix.setRotate(-90);
                break;
            default:
                return bm;
        }
        try {
            Bitmap bmRotated = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
            bm.recycle();
            return bmRotated;
        } catch (OutOfMemoryError e) {
            e.printStackTrace();
            return null;
        }
    }

答案 2 :(得分:4)

您可以通过将其分成更小的步骤来达到您想要的效果。

  1. 拍照
  2. 将位图发送到Google Mobile Vision以检测&#34;地标&#34;在脸上和每只眼睛打开的可能性
  3. 画出适当的眼睛&#34;到你的形象
  4. 使用Google移动视觉的FaceDetector时,您将获得一个SparseArray的Face对象(可能包含多个面,或者可能为空)。因此,您需要处理这些案件。但是你可以循环遍历SparseArray并找到你想要玩的Face对象。

    static Bitmap processFaces(Context context, Bitmap picture) {
        // Create a "face detector" object, using the builder pattern
        FaceDetector detector = new FaceDetector.Builder(context)
                .setTrackingEnabled(false) // disable tracking to improve performance
                .setClassificationType(FaceDetector.ALL_CLASSIFICATIONS)
                .build();
    
        // create a "Frame" object, again using a builder pattern (and passing in our picture)
        Frame frame = new Frame.Builder().setBitmap(picture).build(); // build frame
    
        // get a sparse array of face objects
        SparseArray<Face> faces = detector.detect(frame); // detect the faces
    
        // This example just deals with a single face for the sake of simplicity,
        // but you can change this to deal with multiple faces.
        if (faces.size() != 1) return picture;
    
        // make a mutable copy of the background image that we can modify
        Bitmap bmOverlay = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(), picture.getConfig());
        Canvas canvas = new Canvas(bmOverlay);
        canvas.drawBitmap(picture, 0, 0, null);
        // get the Face object that we want to manipulate, and process it
        Face face = faces.valueAt(0);
        processFace(face, canvas);
        detector.release();
        return bmOverlay;
    }
    

    一旦你有了Face对象,就可以找到你感兴趣的功能

    private static void processFace(Face face, Canvas canvas) {
        // The Face object can tell you the probability that each eye is open.
        // I'm comparing this probability to an arbitrary threshold of 0.6 here,
        // but you can vary it between 0 and 1 as you please.
        boolean leftEyeClosed = face.getIsLeftEyeOpenProbability() < .6;
        boolean rightEyeClosed = face.getIsRightEyeOpenProbability() < .6;
        // Loop through the face's "landmarks" (eyes, nose, etc) to find the eyes.
        // landmark.getPosition() gives you the (x,y) coordinates of each feature.
        for (Landmark landmark : face.getLandmarks()) {
            if (landmark.getType() == Landmark.LEFT_EYE)
                overlayEyeBitmap(canvas, leftEyeClosed, landmark.getPosition().x, landmark.getPosition().y);
            if (landmark.getType() == Landmark.RIGHT_EYE)
                overlayEyeBitmap(canvas, rightEyeClosed, landmark.getPosition().x, landmark.getPosition().y);
        }
    }
    

    然后你可以添加你的油漆!

    private static void overlayEyeBitmap(Canvas canvas, boolean eyeClosed, float cx, float cy) {
        float radius = 40;
    
        // draw the eye's background circle with appropriate color
        Paint paintFill = new Paint();
        paintFill.setStyle(Paint.Style.FILL);
        if (eyeClosed)
            paintFill.setColor(Color.YELLOW);
        else
            paintFill.setColor(Color.WHITE);
        canvas.drawCircle(cx, cy, radius, paintFill);
    
        // draw a black border around the eye
        Paint paintStroke = new Paint();
        paintStroke.setColor(Color.BLACK);
        paintStroke.setStyle(Paint.Style.STROKE);
        paintStroke.setStrokeWidth(5);
        canvas.drawCircle(cx, cy, radius, paintStroke);
    
        if (eyeClosed)
            // draw horizontal line across closed eye
            canvas.drawLine(cx - radius, cy, cx + radius, cy, paintStroke);
        else {
            // draw big off-center pupil on open eye
            paintFill.setColor(Color.BLACK);
            float cxPupil = cx - 10;
            float cyPupil = cy + 10;
            canvas.drawCircle(cxPupil, cyPupil, 25, paintFill);
        }
    }
    

    在上面的片段中,我只是对眼睛半径进行了硬编码,以显示概念证明。您可能希望使用face.getWidth()的一定百分比来进行更灵活的缩放,以确定适当的值。但是这里的图像处理可以做什么:

    big-eyed evil picture

    有关Mobile Vision API的更多详细信息是hereUdacity's current Advanced Android course对这些内容进行了很好的演练(拍摄照片,将其发送到Mobile Vision,并在其上添加位图)。该课程是免费的,或者你可以看看他们做了什么on Github