android - Vista previa de la cámara de cultivo para TextureView
opengl-es android-camera (6)
La respuesta dada por @SatteliteSD es la más adecuada. Cada cámara solo admite ciertos tamaños de vista previa que se establece en HAL. Por lo tanto, si los tamaños de vista previa disponibles no son suficientes, entonces necesita extraer los datos de onPreview
Tengo un TextureView con ancho y alto fijo y quiero mostrar una vista previa de la cámara dentro de él. Necesito recortar la vista previa de la cámara para que no se vea estirada dentro de mi TextureView. Cómo hacer el cultivo? Si necesito usar OpenGL, ¿cómo relacionar la textura de la superficie con OpenGL y cómo hacer el recorte con OpenGL?
public class MyActivity extends Activity implements TextureView.SurfaceTextureListener
{
private Camera mCamera;
private TextureView mTextureView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_options);
mTextureView = (TextureView) findViewById(R.id.camera_preview);
mTextureView.setSurfaceTextureListener(this);
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mCamera = Camera.open();
try
{
mCamera.setPreviewTexture(surface);
mCamera.startPreview();
} catch (IOException ioe) {
// Something bad happened
}
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
mCamera.stopPreview();
mCamera.release();
return true;
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface)
{
// Invoked every time there''s a new Camera preview frame
}
}
Además, después de hacer la vista previa correctamente, necesito poder leer en tiempo real los píxeles que se encuentran en el centro de la imagen recortada.
podría manipular los byte[] data
de byte[] data
de onPreview()
.
Creo que deberás:
- ponerlo en un mapa de bits
- hacer el recorte en el
Bitmap
- hacer un poco de estiramiento / cambio de tamaño
- y pasa el
Bitmap
deBitmap
a tuSurfaceView
Esta no es una forma muy efectiva. Quizás puedas manipular el byte[]
directamente, pero tendrás que lidiar con formatos de imagen como NV21.
Para manipulaciones en tiempo real de imágenes de vista previa de la cámara, OpenCV para Android está perfectamente adaptado. Encontrará todas las muestras necesarias aquí: http://opencv.org/platforms/android/opencv4android-samples.html , y como verá, funciona muy bien en tiempo real.
Descargo de responsabilidad: configurar una librería opencv en Android puede ser un gran dolor de cabeza dependiendo de cómo se experimente con c ++ / opencv / the ndk. En todos los casos, escribir código opencv nunca es simple, pero, por otro lado, es una biblioteca muy poderosa.
Acabo de crear una aplicación en funcionamiento donde necesitaba mostrar una vista previa con x2 de la entrada real sin obtener un aspecto pixelado. Es decir. Necesitaba mostrar la parte central de una vista previa en vivo de 1280x720 en un TextureView de 640x360.
Aquí esta lo que hice.
Establezca la vista previa de la cámara en una resolución x2 de lo que necesitaba:
params.setPreviewSize(1280, 720);
Y luego escala la vista de textura en consecuencia:
this.captureView.setScaleX(2f);
this.captureView.setScaleY(2f);
Esto funciona sin inconvenientes en dispositivos pequeños.
Simplemente calcule la relación de aspecto, genere una matriz de escala y aplíquela al TextureView. Según la relación de aspecto de la superficie y la relación de aspecto de la imagen de vista previa, la imagen de vista previa se recorta en la parte superior e inferior o izquierda y derecha. Otra solución que descubrí es que si abres la cámara antes de que SurfaceTexture esté disponible, la vista previa ya está escalada automáticamente. Intente mover mCamera = Camera.open (); a su función onCreate después de configurar SurfaceTextureListener. Esto funcionó para mí en el N4. Con esta solución, es probable que tengas problemas cuando giras de vertical a horizontal. Si necesita soporte vertical y horizontal, ¡tome la solución con la matriz de escala!
private void initPreview(SurfaceTexture surface, int width, int height) {
try {
camera.setPreviewTexture(surface);
} catch (Throwable t) {
Log.e("CameraManager", "Exception in setPreviewTexture()", t);
}
Camera.Parameters parameters = camera.getParameters();
previewSize = parameters.getSupportedPreviewSizes().get(0);
float ratioSurface = width > height ? (float) width / height : (float) height / width;
float ratioPreview = (float) previewSize.width / previewSize.height;
int scaledHeight = 0;
int scaledWidth = 0;
float scaleX = 1f;
float scaleY = 1f;
boolean isPortrait = false;
if (previewSize != null) {
parameters.setPreviewSize(previewSize.width, previewSize.height);
if (display.getRotation() == Surface.ROTATION_0 || display.getRotation() == Surface.ROTATION_180) {
camera.setDisplayOrientation(display.getRotation() == Surface.ROTATION_0 ? 90 : 270);
isPortrait = true;
} else if (display.getRotation() == Surface.ROTATION_90 || display.getRotation() == Surface.ROTATION_270) {
camera.setDisplayOrientation(display.getRotation() == Surface.ROTATION_90 ? 0 : 180);
isPortrait = false;
}
if (isPortrait && ratioPreview > ratioSurface) {
scaledWidth = width;
scaledHeight = (int) (((float) previewSize.width / previewSize.height) * width);
scaleX = 1f;
scaleY = (float) scaledHeight / height;
} else if (isPortrait && ratioPreview < ratioSurface) {
scaledWidth = (int) (height / ((float) previewSize.width / previewSize.height));
scaledHeight = height;
scaleX = (float) scaledWidth / width;
scaleY = 1f;
} else if (!isPortrait && ratioPreview < ratioSurface) {
scaledWidth = width;
scaledHeight = (int) (width / ((float) previewSize.width / previewSize.height));
scaleX = 1f;
scaleY = (float) scaledHeight / height;
} else if (!isPortrait && ratioPreview > ratioSurface) {
scaledWidth = (int) (((float) previewSize.width / previewSize.height) * width);
scaledHeight = height;
scaleX = (float) scaledWidth / width;
scaleY = 1f;
}
camera.setParameters(parameters);
}
// calculate transformation matrix
Matrix matrix = new Matrix();
matrix.setScale(scaleX, scaleY);
textureView.setTransform(matrix);
}
La solución anterior de @Romanski funciona bien pero se escala con el recorte. Si necesita escalar para adaptarse, entonces use la siguiente solución. Llame a updateTextureMatrix cada vez que se modifique la vista de superficie, es decir, en los métodos onSurfaceTextureAvailable y onSurfaceTextureSizeChanged. También tenga en cuenta que esta solución se basa en que la actividad ignora los cambios de configuración (es decir, android: configChanges = "orientation | screenSize | keyboardHidden" o algo así):
private void updateTextureMatrix(int width, int height)
{
boolean isPortrait = false;
Display display = getWindowManager().getDefaultDisplay();
if (display.getRotation() == Surface.ROTATION_0 || display.getRotation() == Surface.ROTATION_180) isPortrait = true;
else if (display.getRotation() == Surface.ROTATION_90 || display.getRotation() == Surface.ROTATION_270) isPortrait = false;
int previewWidth = orgPreviewWidth;
int previewHeight = orgPreviewHeight;
if (isPortrait)
{
previewWidth = orgPreviewHeight;
previewHeight = orgPreviewWidth;
}
float ratioSurface = (float) width / height;
float ratioPreview = (float) previewWidth / previewHeight;
float scaleX;
float scaleY;
if (ratioSurface > ratioPreview)
{
scaleX = (float) height / previewHeight;
scaleY = 1;
}
else
{
scaleX = 1;
scaleY = (float) width / previewWidth;
}
Matrix matrix = new Matrix();
matrix.setScale(scaleX, scaleY);
textureView.setTransform(matrix);
float scaledWidth = width * scaleX;
float scaledHeight = height * scaleY;
float dx = (width - scaledWidth) / 2;
float dy = (height - scaledHeight) / 2;
textureView.setTranslationX(dx);
textureView.setTranslationY(dy);
}
También necesita los siguientes campos:
private int orgPreviewWidth;
private int orgPreviewHeight;
Inicialícelo en onSurfaceTextureMaddy disponible antes de llamar a updateTextureMatrix:
Camera.Parameters parameters = camera.getParameters();
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
Pair<Integer, Integer> size = getMaxSize(parameters.getSupportedPreviewSizes());
parameters.setPreviewSize(size.first, size.second);
orgPreviewWidth = size.first;
orgPreviewHeight = size.second;
camera.setParameters(parameters);
Método getMaxSize:
private static Pair<Integer, Integer> getMaxSize(List<Camera.Size> list)
{
int width = 0;
int height = 0;
for (Camera.Size size : list) {
if (size.width * size.height > width * height)
{
width = size.width;
height = size.height;
}
}
return new Pair<Integer, Integer>(width, height);
}
Y lo último: necesitas corregir la rotación de la cámara. Así que llame al método setCameraDisplayOrientation en el método Activity onConfigurationChanged (y también realice la llamada inicial en el método onSurfaceTextureAvailable):
public static void setCameraDisplayOrientation(Activity activity, int cameraId, Camera camera)
{
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation)
{
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
{
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
}
else
{ // back-facing
result = (info.orientation - degrees + 360) % 360;
}
camera.setDisplayOrientation(result);
Camera.Parameters params = camera.getParameters();
params.setRotation(result);
camera.setParameters(params);
}