Android: как отображать предварительный просмотр камеры с помощью обратного вызова?
То, что мне нужно сделать, довольно просто, я хочу вручную отобразить предварительный просмотр с камеры с помощью обратного вызова камеры, и я хочу получить по крайней мере 15 кадров в секунду на реальном устройстве. Мне даже не нужны цвета, мне просто нужно просмотреть изображение в оттенках серого. Изображения с камеры находятся в формате YUV, и вам нужно как-то обработать их, что является основной проблемой производительности. Я использую API 8.
Во всех случаях я использую camera.setPreviewCallbackWithBuffer (), что быстрее, чем camera.setPreviewCallback (). Кажется, я не могу получить около 24 кадров в секунду, если я не показываю предварительный просмотр. Так что нет проблемы.
Я пробовал эти решения:
- Сортировка TreeMap по значению
- статический импорт в Java
- NoClassDefFoundError: org / w3c / dom / ElementTraversal
- Как правильно переопределить toString () на Java?
- Как я могу разобрать этот JSON в Android?
1. Отобразить предварительный просмотр камеры на SurfaceView в виде растрового изображения. Он работает, но производительность составляет около 6 кадров в секунду.
baos = new ByteOutputStream(); yuvimage=new YuvImage(cameraFrame, ImageFormat.NV21, prevX, prevY, null); yuvimage.compressToJpeg(new Rect(0, 0, prevX, prevY), 80, baos); jdata = baos.toByteArray(); bmp = BitmapFactory.decodeByteArray(jdata, 0, jdata.length); // Convert to Bitmap, this is the main issue, it takes a lot of time canvas.drawBitmap(bmp , 0, 0, paint);
2. Покажите предварительный просмотр камеры на GLSurfaceView как текстуру. Здесь я показывал только данные яркости (изображение в оттенках серого), что довольно просто, для каждого кадра требуется только один arraycopy (). Я могу получить около 12 кадров в секунду, но мне нужно применить некоторые фильтры к просмотру, и кажется, что это не может быть сделано быстро в OpenGL ES 1. Поэтому я не могу использовать это решение. Некоторые подробности этого в другом вопросе .
3. Отобразить предварительный просмотр камеры на SurfaceView (GL) с помощью NDK для обработки данных YUV. Я нахожу здесь решение, которое использует некоторую функцию C и NDK. Но мне это не удалось, вот еще несколько деталей . Но в любом случае это решение выполняется, чтобы вернуть ByteBuffer, чтобы отобразить его как текстуру в OpenGL, и это будет не быстрее, чем предыдущая попытка. Поэтому мне пришлось бы изменить его, чтобы возвратить массив int [], который можно нарисовать с помощью canvas.drawBitmap (), но я не понимаю C достаточно для этого.
Итак, есть ли другой способ, который я потерял или какое-то улучшение в попытках, которые я пробовал?
- Как изменить fragmentы с помощью Android-навигатора
- Удалить журнал вызовов в android для определенного номера
- Обнаружение долгой печати с помощью Android
- Android, Java: HTTP POST Request
- Integer.toString (int i) vs String.valueOf (int i)
- Вызов метода подclassа в Java
- При создании службы нет пустого конструктора
- Ошибка MultiDex NoClassDefFound
Я работаю над одной и той же проблемой, но не так хорошо, как у вас.
Рассматривали ли вы прямое рисование пикселей на canvasе, не кодируя их сначала в формате JPEG? Внутри набора OpenCV http://sourceforge.net/projects/opencvlibrary/files/opencv-android/2.3.1/OpenCV-2.3.1-android-bin.tar.bz2/download (который фактически не использует opencv; не волнуйтесь), есть проект под названием tutorial-0-androidcamera, который демонстрирует преобразование пикселей YUV в RGB, а затем их прямое преобразование в bitmap.
Соответствующий код по существу:
public void onPreviewFrame(byte[] data, Camera camera, int width, int height) { int frameSize = width*height; int[] rgba = new int[frameSize+1]; // Convert YUV to RGB for (int i = 0; i < height; i++) for (int j = 0; j < width; j++) { int y = (0xff & ((int) data[i * width + j])); int u = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 0])); int v = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 1])); y = y < 16 ? 16 : y; int r = Math.round(1.164f * (y - 16) + 1.596f * (v - 128)); int g = Math.round(1.164f * (y - 16) - 0.813f * (v - 128) - 0.391f * (u - 128)); int b = Math.round(1.164f * (y - 16) + 2.018f * (u - 128)); r = r < 0 ? 0 : (r > 255 ? 255 : r); g = g < 0 ? 0 : (g > 255 ? 255 : g); b = b < 0 ? 0 : (b > 255 ? 255 : b); rgba[i * width + j] = 0xff000000 + (b << 16) + (g << 8) + r; } Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); bmp.setPixels(rgba, 0/* offset */, width /* stride */, 0, 0, width, height); Canvas canvas = mHolder.lockCanvas(); if (canvas != null) { canvas.drawBitmap(bmp, (canvas.getWidth() - width) / 2, (canvas.getHeight() - height) / 2, null); mHolder.unlockCanvasAndPost(canvas); } else { Log.w(TAG, "Canvas is null!"); } bmp.recycle(); }
Конечно, вам придется адаптировать его для удовлетворения ваших потребностей (например, не выделяя rgba для каждого кадра), но это может быть начало. Я бы с удовольствием посмотрел, работает ли это для вас или нет - я все еще борюсь с проблемами, ортогональными вам на данный момент.
Я думаю, что Майкл на правильном пути. Сначала вы можете попробовать этот метод для преобразования из RGB в оттенки серого. Ясно, что он делает почти то же самое, что и его, но немного более лаконично для того, что вы хотите.
//YUV Space to Greyscale static public void YUVtoGrayScale(int[] rgb, byte[] yuv420sp, int width, int height){ final int frameSize = width * height; for (int pix = 0; pix < frameSize; pix++){ int pixVal = (0xff & ((int) yuv420sp[pix])) - 16; if (pixVal < 0) pixVal = 0; if (pixVal > 255) pixVal = 255; rgb[pix] = 0xff000000 | (pixVal << 16) | (pixVal << 8) | pixVal; } }
}
Во-вторых, не создавайте тонны работы для сборщика мусора. Ваши растровые изображения и массивы будут фиксированными. Создайте их один раз, а не в onFramePreview.
В результате вы получите что-то похожее на это:
public PreviewCallback callback = new PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { if ( (mSelectView == null) || !inPreview ) return; if (mSelectView.mBitmap == null) { //initialize SelectView bitmaps, arrays, etc //mSelectView.mBitmap = Bitmap.createBitmap(mSelectView.mImageWidth, mSelectView.mImageHeight, Bitmap.Config.RGB_565); //etc } //Pass Image Data to SelectView System.arraycopy(data, 0, mSelectView.mYUVData, 0, data.length); mSelectView.invalidate(); } };
И тогда canvas, где вы хотите его поместить, выглядит так:
class SelectView extends View { Bitmap mBitmap; Bitmap croppedView; byte[] mYUVData; int[] mRGBData; int mImageHeight; int mImageWidth; public SelectView(Context context){ super(context); mBitmap = null; croppedView = null; } @Override protected void onDraw(Canvas canvas){ if (mBitmap != null) { int canvasWidth = canvas.getWidth(); int canvasHeight = canvas.getHeight(); // Convert from YUV to Greyscale YUVtoGrayScale(mRGBData, mYUVData, mImageWidth, mImageHeight); mBitmap.setPixels(mRGBData, 0, mImageWidth, 0, 0, mImageWidth, mImageHeight); Rect crop = new Rect(180, 220, 290, 400); Rect dst = new Rect(0, 0, canvasWidth, (int)(canvasHeight/2)); canvas.drawBitmap(mBitmap, crop, dst, null); } super.onDraw(canvas); }
Этот пример показывает обрезанный и искаженный выбор предварительного просмотра камеры в реальном времени, но вы получаете идею. Он работает при высоком FPS на Nexus S в оттенках серого и должен работать и для ваших нужд.
Разве это не то, что вы хотите? Просто используйте SurfaceView в своем макете, а затем где-то в вашем init, например onResume()
:
SurfaceView surfaceView = ... SurfaceHolder holder = surfaceView.getHolder(); ... Camera camera = ...; camera.setPreviewDisplay(holder);
Он просто посылает кадры прямо на представление так быстро, как они поступают.
Если вы хотите оттенки серого, измените параметры камеры с помощью setColorEffect("mono")
.
Для очень простых и простых эффектов
Camera.Parameters parameters = mCamera.getParameters(); parameters.setColorEffect(Parameters.EFFECT_AQUA);
Я понял, что эти эффекты различаются в зависимости от устройства. Например, на моем телефоне (galaxy s II) это выглядит как комический эффект, так как в отличие от галактики s 1 это «просто» синий оттенок.
Это pro: он работает как предварительный просмотр в прямом эфире.
Я просмотрел некоторые другие приложения для камеры, и они, очевидно, также столкнулись с этой проблемой. Так что же они сделали? Они захватывают изображение камеры по умолчанию, применяют фильтр к данным растрового изображения и показывают это изображение в простом ImageView. Это точно не так круто, как в режиме предварительного просмотра, но вы никогда не столкнетесь с проблемами производительности.
Я считаю, что прочитал в блоге, что данные в gradleациях серого находятся в первых байтах x * y. Yuv должен представлять яркость, поэтому данные есть, хотя это не идеальный оттенок серого. Он отлично подходит для относительной яркости, но не в оттенках серого, поскольку каждый цвет не такой яркий, как один из других в rgb. Зелену обычно дают более сильный вес в преобразованиях светимости. Надеюсь это поможет!
Есть ли какая-то особая причина, по которой вы вынуждены использовать GLES 1.0?
Потому что, если нет, см. Принятый ответ здесь: Android SDK: получить изображение с предварительным просмотром изображения без отображения его
Обычно он упоминает использование Camera.setPreviewTexture () в сочетании с GLES 2.0. В GLES 2.0 вы можете отображать полноэкранный квадрат по всему экрану и создавать любой необходимый эффект.
Скорее всего, это самый быстрый способ.