能否裁剪相机预览?

16

我还没有找到任何一种方法来裁剪摄像头预览并在 SurfaceView 上显示它。

Android - 是否可能裁剪摄像头预览?


你可以更改或裁剪surfaceView的大小,而不是全屏视图,这是你想要的东西,否则你需要裁剪视频本身的预览。 - Karthi
@Anderson 你是在谈论图片还是视频? - Nikunj Patel
2
你可以拍摄图片然后进行裁剪。 - Richa
2
可能是如何裁剪相机预览?的重复问题。 - Sergey Glotov
对于任何发现这个问题的人,使用TextureView有一种方法可以实现此操作。请参见Ruslan在此处的答案:https://dev59.com/i2Qn5IYBdhLWcg3wNkw4 - Sam
显示剩余2条评论
9个回答

19

您可以不使用覆盖视图(这在某些情况下无法工作)来完成此操作。

子类化ViewGroup,将SurfaceView添加为唯一的子项,然后:

  1. 在onMeasure中提供所需的裁剪尺寸。
  2. 在onLayout中,使用未裁剪的尺寸对SurfaceView进行布局。

基本上,

public class CroppedCameraPreview extends ViewGroup {
  private SurfaceView cameraPreview;
  public CroppedCameraPreview( Context context ) {
    super( context );
    // i'd probably create and add the SurfaceView here, but it doesn't matter
  }
  @Override
  protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec ) {
    setMeasuredDimension( croppedWidth, croppedHeight );
  }
  @Override
  protected void onLayout( boolean changed, int l, int t, int r, int b ) {
    if ( cameraPreview != null ) {
      cameraPreview.layout( 0, 0, actualPreviewWidth, actualPreviewHeight );
    }
  }
}

如何获取 actualPreviewWidthactualPreviewHeightgetWidth()getHeight() 返回 0。我的 CroppedCameraPreview 实例从 XML 文件中膨胀,并使用 match_parent 作为宽度和高度。 - lordmegamax
@lordmegamax 这些是由您(作者)提供的。通常,您会发现一组尺寸中最接近可用空间的最佳匹配项,使用Camera.getParameters().getSupportedPreviewSizes() - momo
谢谢 @momo。我也使用了布局调用的左右参数来获取相机预览屏幕的中心而不是左侧,即 cameraPreview.layout(offset, 0, actualPreviewWidth+offset, actualPreviewHeight)。 - jacob
@momo 有空看一下这个链接:http://stackoverflow.com/questions/36943772/camera-draws-outside-the-view? - AndreKR

9
您可以将摄像头预览(SurfaceView)放置在一个LinearLayout中,该LinearLayout位于ScrollView内部。当相机输出大于您设置的LinearLayout时,您可以编程滚动它并禁用用户滚动。这样,您可以轻松地模拟相机裁剪:
<ScrollView 
                     android:id="@+id/scrollView"
                     android:layout_width="640dip"
                     android:layout_height="282dip"
                     android:scrollbars="none"
                     android:fillViewport="true">

                        <LinearLayout
                                android:id="@+id/linearLayoutBeautyContent"
                                android:layout_width="fill_parent"
                                android:layout_height="fill_parent"
                                android:orientation="vertical">

                                <SurfaceView
                                            android:layout_width="match_parent"
                                            android:layout_height="match_parent"
                                            android:id="@+id/surfaceViewBeautyCamera"/>
                      </LinearLayout>
</ScrollView>

2
这是一个不错的方法。但是SurfaceView不应该有一个匹配相机纵横比并且比周围布局更大的固定高度吗?使用match_parent会使其填充ScrollView的固定高度,导致预览被压缩。 - Father Stack
1
是的,我正在以编程方式实现这个功能,因为SurfaceView应该与预览图像具有相同的比例,为此我必须询问相机。 - steve_patrick
我无法让它工作,几行代码会很好。 在我的情况下,水平滚动是不可能的。 而且,如果带有surfaceview的活动是对话框主题,滚动surfaceview会导致相机图像出现在对话框边框之外。(因此,对于非全屏活动,这会导致相当严重的图形故障) - John
@steve_patrick 我正在尝试使用TextureView做同样的事情,但它不起作用,相机预览仍然被压缩了,你能帮我吗?我使用TextureView是因为我还需要设置alpha值。 - Bilal Rabbani

7

不直接支持。相机API现在不允许偏移,并会将图像压缩到表面持有者中。但是您可以通过放置覆盖层(其他视图)来解决问题。


一个人也可以强制预览表面的大小和位置,以达到裁剪的效果。 - Alex Cohn
完全替代的方法是使用setPreviewTexture()而不是setPreviewDisplay(),并通过OpenGL控制裁剪。 - Alex Cohn
@AlexCohn,你能给我指一些示例代码,展示你在之前评论中提到的方法之一吗?我想裁剪相机预览的中心并在固定大小的SurfaceView中显示它。 - Catalin Morosan
https://dev59.com/TWoy5IYBdhLWcg3wYM2B#17702981 - steve_patrick
间接地,您正在处理从setPreviewCallback获取的图像,然后使用位图裁剪或任何其他算法对该图像进行裁剪。但问题是,我们能否裁剪相机预览本身以适应任何表面。比如我只想要相机视图中心的512x512部分? - Rohit Patil
显示剩余2条评论

0

这里提供了一个针对横屏方向的解决方案,以补充@drees的优秀答案。

只需在res文件夹中添加layout-land文件夹,复制整个布局xml并更改@drees代码中的布局部分,如下所示:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/background_dark" >

    <FrameLayout
        android:id="@+id/frameSurface"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:background="@android:color/background_light"/>

    <View
        android:id="@+id/transparent_window"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_centerInParent="true"
        android:background="@android:color/transparent" />

    <View
        android:id="@+id/black_top_box"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_toLeftOf="@id/transparent_window"
        android:background="@android:color/background_dark"/>

    <View
        android:id="@+id/black_bottom_box"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_toRightOf="@id/transparent_window"
        android:background="@android:color/background_dark"/>
</RelativeLayout>

0

编程自动滚动图片的代码可能如下所示:

public void setCameraOrientationOnOpen() 
    {   
        mCamera.stopPreview();
        int rotation = getRotation();
        Camera.Parameters currentCameraParameters = mCamera.getParameters();
        List<Camera.Size> previewSizes = currentCameraParameters.getSupportedPreviewSizes();

        mOptimalCameraSize = getOptimaPreviewCameraSize(previewSizes, (double)9/16);            
        currentCameraParameters.setPreviewSize(mOptimalCameraSize.width, mOptimalCameraSize.height);
        mCamera.setParameters(currentCameraParameters);
        float ratio = 100;
        int ratio1 = (mSurfaceView.getLayoutParams().height * 100) / mOptimalCameraSize.width; //height
        int ratio2 = (mSurfaceView.getLayoutParams().width * 100) / mOptimalCameraSize.height; //width 
        ratio = Math.max(ratio1, ratio2);
        mSurfaceView.getLayoutParams().height = (int) ((mOptimalCameraSize.width * ratio) / 100);
        mSurfaceView.getLayoutParams().width = (int) ((mOptimalCameraSize.height * ratio) / 100);
        if(ratio > 100)
        {
            int offset = (mSurfaceView.getLayoutParams().height - mBoxHeight)/2;
            mScrollView.scrollTo(0, offset); //center the image
        }
        mCamera.setDisplayOrientation(rotation);
        mOptimalCameraSize = mCamera.getParameters().getPreviewSize();
    }

我会从相机中计算出最佳的预览尺寸,以适应我的相机内容框(比例为16:9),然后将计算出的比例应用于图像,以保持相同的比例,并最终计算所需的滚动量(图像将位于中间)。

0
创建一个居中的Frame Layout,用于存储相机预览,并用视图覆盖它以进行“裁剪”。创建视图时,动态拉伸一个透明视图,也要居中。
XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000" >

<FrameLayout 
    android:id="@+id/camera_preview_frame"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_centerVertical="true" />

<View
    android:id="@+id/transparent_window"
    android:layout_width="fill_parent"
    android:layout_height="50dip"
    android:layout_centerVertical="true"
    android:background="@android:color/transparent" />

<View 
    android:id="@+id/black_top_box"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_above="@id/transparent_window"
    android:background="#000"/>

<View 
    android:id="@+id/black_bottom_box"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_below="@id/transparent_window"
    android:background="#000"/>  
</RelativeLayout>

然后在您的活动类的 OnCreate() 方法中,您可以像这样拉伸透明视图。

CameraActivity.java

final View transView = findViewById(R.id.transparent_window);

transView.post(new Runnable() {

    @Override
    public void run() {
        RelativeLayout.LayoutParams params;
        params = (RelativeLayout.LayoutParams) transView.getLayoutParams();
        params.height = transView.getWidth();
        transView.setLayoutParams(params);
        transView.postInvalidate();
    }
});

这是我手机上的截图。中间的灰色斑点是相机透过透明视图看到的地面视图。


仅支持竖屏,旋转屏幕会失效..无论如何请点赞! - Matan Dahan

0

这个解决方案是你的情况的一种变通方法。有些代码已经过时,不建议在企业项目中使用,但如果你只需要显示相机预览而不压缩它,那就足够了。
如果你需要在预览之前处理图像,那么你应该看看SurfaceTexture

public class CameraPreview
        extends SurfaceView
        implements SurfaceHolder.Callback, Camera.PreviewCallback {

    public static final String TAG = CameraPreview.class.getSimpleName();

    private static final int PICTURE_SIZE_MAX_WIDTH = 1280;
    private static final int PREVIEW_SIZE_MAX_WIDTH = 640;
    private static final double ASPECT_RATIO = 3.0 / 4.0;

    private Camera mCamera;
    private SurfaceHolder mHolder;
    private boolean mIsLive;
    private boolean mIsPreviewing;

    public CameraPreview(Context context) {
        super(context);
        init(context);
    }

    public CameraPreview(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = (int) (width / ASPECT_RATIO + 0.5);
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        //L.g().d(TAG, "onVisibilityChanged: visibility=" + visibility);
        if (mIsLive) {
            if (visibility == VISIBLE && !mIsPreviewing) {
                startCameraPreview();
            } else {
                stopCameraPreview();
            }
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        startCamera();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        stopCamera();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        //L.g().d(TAG, "surfaceChanged: format=" + format + ", width=" + w + ", height=" + h);
        if (mHolder.getSurface() == null || mCamera == null) return;
        mHolder = holder;
        try {
            mCamera.stopPreview();
        } catch (Exception ignored) {}
        try {
            mCamera.setPreviewDisplay(mHolder);
            if (mIsLive && mIsPreviewing) mCamera.startPreview();
        } catch (Exception ignored) {}
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {

        //work with camera preview

        if (mIsPreviewing) camera.setOneShotPreviewCallback(this);
    }

    private Camera.Size determineBestPreviewSize(Camera.Parameters parameters) {
        return determineBestSize(parameters.getSupportedPreviewSizes(), PREVIEW_SIZE_MAX_WIDTH);
    }

    private Camera.Size determineBestPictureSize(Camera.Parameters parameters) {
        return determineBestSize(parameters.getSupportedPictureSizes(), PICTURE_SIZE_MAX_WIDTH);
    }

    /**
     * This code I found in this repository
     * https://github.com/boxme/SquareCamera/blob/master/squarecamera/src/main/java/com/desmond/squarecamera/CameraFragment.java#L368
     */
    private Camera.Size determineBestSize(List<Camera.Size> sizes, int widthThreshold) {
        Camera.Size bestSize = null;
        Camera.Size size;
        int numOfSizes = sizes.size();
        for (int i = 0; i < numOfSizes; i++) {
            size = sizes.get(i);
            boolean isDesireRatio = (size.width / 4) == (size.height / 3);
            boolean isBetterSize = (bestSize == null) || size.width > bestSize.width;

            if (isDesireRatio && isBetterSize) {
                bestSize = size;
            }
        }
        if (bestSize == null) {
            return sizes.get(sizes.size() - 1);
        }
        return bestSize;
    }

    private void init(Context context) {
        mHolder = getHolder();
        mHolder.addCallback(this);
    }

    public void startCamera() {
        if (!mIsLive) {
            //L.g().d(TAG, "startCamera");
            mIsPreviewing = false;
            mCamera = Camera.open();
            if (mCamera != null) {
                try {
                    Camera.Parameters param = mCamera.getParameters();
                    Camera.Size bestPreviewSize = determineBestPreviewSize(param);
                    Camera.Size bestPictureSize = determineBestPictureSize(param);
                    param.setPreviewSize(bestPreviewSize.width, bestPreviewSize.height);
                    param.setPictureSize(bestPictureSize.width, bestPictureSize.height);
                    mCamera.setParameters(param);
                } catch (RuntimeException ignored) {}
                try {
                    mCamera.setDisplayOrientation(90);
                    mCamera.setPreviewCallback(this);
                    mCamera.setPreviewDisplay(mHolder);
                    mIsLive = true;
                } catch (Exception ignored) {}
            }
            //else L.g().d(TAG, "startCamera: error launching the camera");
        }
    }

    public void stopCamera() {
        if (mCamera != null && mIsLive) {
            //L.g().d(TAG, "stopCamera");
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
            mIsPreviewing = false;
            mIsLive = false;
        }
    }

    public void startCameraPreview() {
        if (mCamera != null && mIsLive && !mIsPreviewing) {
            //L.g().d(TAG, "startCameraPreview");
            mCamera.setPreviewCallback(this);
            mCamera.startPreview();
            mIsPreviewing = true;
        }
    }

    public void stopCameraPreview() {
        if (mCamera != null && mIsLive && mIsPreviewing) {
            //L.g().d("stopCameraPreview");
            mCamera.stopPreview();
            mIsPreviewing = false;
        }
    }
}

0

假设您有一个矩形或浮点矩形,这是您的计算:

float imageWidth = bitmap.Width;
float imageHeight = bitmap.Height;
float width = yourscreenWidth;
float heigth =  yourscreenHeight ;           

var W= width / heigth / (imageWidth / imageHeight);
var W2 = rect.Width() / widt * W;
var H = rect.Height() / heigth;
var cropImageWidth = imageWidth * W2 ;
var cropImageHeight = imageHeight * H ;
var cropImageX = (imageWidth - cropImageWidth) / 2;
var cropImageY = (imageHeight - cropImageHeight) / 2;
Bitmap imageCropped = Bitmap.CreateBitmap(bitmap, (int)cropImageX, (int)cropImageY, (int)cropImageWidth, (int)cropImageHeight);

0

经过最近几天的搜索,我相信我需要在这里发布我的解决方案。

我唯一成功做到并且效果良好的是添加了一个比例尺。

我想创建一个带有相机输出部分的TextureView,但如果不让预览缩放,我无法完成它。

所以在我决定相机/屏幕比例的最佳分辨率之后,我获取了相机捕获高度和我想要显示的高度之间的比例尺。

mPreviewSize = chooseOptimalSize(...);

int viewHeight = getDP(this, R.dimen.videoCaptureHeight);
float scaleY = mPreviewSize.getHeight() / viewHeight;
setScaleY(scaleY);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接