标签:
博主QQ:1356438802
本文实验平台:Eclipse + Opencv 2.4.10 + MTK Android 4.4平板(这一直是我的Android实验平台)
可能各位看官,看到前面的文章会觉得很凌乱,一会儿这个平台,一会儿那个平台。
其实我的主要思路就是:opencv中的任何一个功能,首先在windows上验证成功,再到Ubuntu,然后到Android上验证!
在windows上,由于其系统通用性,各方面支持肯定更好,所以我一定能验证成功,然后我再去Android上面做这些功能。这样前面的经验可以给我做参考,在Android上调试时可以敏锐滴知道问题出在哪里,并且这个过程下来三个平台都熟悉了,对于以后我的应用程序跨平台移植也是有帮助的!
回顾这段时间的工作,主要是三个阶段:
1. 在各个平台能够正常调用opencv的函数。例如yanzi_OpenCV4Android app的JNI里面有个灰度处理的函数Java_luo_uvc_jni_ImageProc_grayProc,就只是为了验证opencv可以调用。
2. 在各个平台能够正常获取图像,实现实时预览。这个阶段前面在windows、Ubuntu和Android基本都实现了,只是效果有差别而已。
3. 对2中的函数调用升级——在各个平台使用opencv2的C++类实现预览和录像。实际上opencv2的C++类只是对opencv1的C函数做了一次封装,但是看起来代码更干净简洁,也更方便和MAT结合。windows和Ubuntu已经验证完了,可以参考前面系列文章。
接下来我们验证VideoCapture和VideoWriter在Android上的使用!
把文章《我的Opencv4Android添加V4L2支持的移植记录(2)》中的例程yanzi_OpenCV4Android拷贝过来做如下修改
yanzi_OpenCV4Android6
ImageProc.cpp
#include "ImageProc.h" #include "cv.h" #include "highgui.h" #include "opencv2/core/core.hpp" #include "opencv2/highgui/highgui.hpp" #include <string> #include <vector> using namespace cv; using namespace std; #ifdef __cplusplus extern "C" { #pragma message("------------------------ ImageProc.cpp") #endif #define NAMELEN (64) Mat frame; VideoCapture capture; char *prefix = NULL; char fileName[NAMELEN] = {0}; VideoWriter writer; int id; JNIEXPORT jintArray JNICALL Java_luo_uvc_jni_ImageProc_grayProc(JNIEnv* env, jclass obj, jintArray buf, jint w, jint h) { jint *cbuf; cbuf = env->GetIntArrayElements(buf, false); if (cbuf == NULL) { return 0; } Mat imgData(h, w, CV_8UC4, (unsigned char*) cbuf); uchar* ptr = imgData.ptr(0); for (int i = 0; i < w * h; i++) { //计算公式:Y(亮度) = 0.299*R + 0.587*G + 0.114*B //对于一个int四字节,其彩色值存储方式为:BGRA int grayScale = (int) (ptr[4 * i + 2] * 0.299 + ptr[4 * i + 1] * 0.587 + ptr[4 * i + 0] * 0.114); ptr[4 * i + 1] = grayScale; ptr[4 * i + 2] = grayScale; ptr[4 * i + 0] = grayScale; } int size = w * h; jintArray result = env->NewIntArray(size); env->SetIntArrayRegion(result, 0, size, cbuf); env->ReleaseIntArrayElements(buf, cbuf, 0); return result; } JNIEXPORT jint JNICALL Java_luo_uvc_jni_ImageProc_connectCamera(JNIEnv* env, jclass obj, jint device) { id = device; if (capture.isOpened()) { LOGE("camera is already opened!"); return -2; } else { //打开一个默认的摄像头 capture.open(-1); if (capture.isOpened()) { LOGE("camera open success!"); return 0; } } // LOGI("usb camera Java_luo_uvc_jni_ImageProc_connectCamera end ....\n"); return -1; } JNIEXPORT jint JNICALL Java_luo_uvc_jni_ImageProc_releaseCamera(JNIEnv* env, jclass obj) { // LOGI("usb camera Java_luo_uvc_jni_ImageProc_releaseCamera start ....\n"); if (capture.isOpened()) { capture.release(); LOGE("camera release success!"); return 0; } // LOGI("usb camera Java_luo_uvc_jni_ImageProc_releaseCamera end ....\n"); return -1; } struct FrameInfoClass { jfieldID width; jfieldID heigth; jfieldID imageSize; jfieldID pixels; }; JNIEXPORT jobject JNICALL Java_luo_uvc_jni_ImageProc_getFrame(JNIEnv* _env, jclass obj) { // LOGI("------------Java_luo_uvc_jni_ImageProc_getFrame\n"); if (capture.isOpened()) { LOGI("------------start capture frame\n"); //取图片帧 capture >> frame; if (frame.empty()) { LOGE("capture frame empty!"); return NULL; } //将图片写入视频文件 if(writer.isOpened()) { //录制视频用的frame不需要转格式 writer.write(frame); } struct FrameInfoClass frameInfoClass; //内部类用$ //luo/uvc/jni/ImageProc$FrameInfo jclass class2 = _env->FindClass("luo/uvc/jni/ImageProc$FrameInfo"); frameInfoClass.width = _env->GetFieldID(class2, "mWidth", "I"); frameInfoClass.heigth = _env->GetFieldID(class2, "mHeight", "I"); frameInfoClass.imageSize = _env->GetFieldID(class2, "mImageSize", "I"); frameInfoClass.pixels = _env->GetFieldID(class2, "mPixels", "[I"); // jobject joFrame = _env->AllocObject(class2); LOGI("frame->cols = %d\n", frame.cols); LOGI("frame->rows = %d\n", frame.rows); // LOGI("frame->imageSize = %d\n", frame->imageSize); _env->SetIntField(joFrame, frameInfoClass.width, frame.cols); _env->SetIntField(joFrame, frameInfoClass.heigth, frame.rows); // _env->SetIntField(joFrame, frameInfoClass.imageSize, frame->imageSize); int size = frame.cols * frame.rows; //创建一个新的java数组(jarray),但是jarray不是C数组类型,不能直接访问jarray jintArray jiArr = _env->NewIntArray(size); jint *ji; #if 1 //可用 //RGB --> ARGB8888 Mat frameARGB; cvtColor(frame, frameARGB, CV_RGB2RGBA); //JNI支持一系列的Get/Release<Type>ArrayElement 函数,允许本地代码获取一个指向基本C类型数组的元素的指针。 ji = _env->GetIntArrayElements(jiArr, 0); memcpy((jbyte *) ji, frameARGB.data, 4 * size); _env->ReleaseIntArrayElements(jiArr, ji, 0); //可加,可不加 _env->SetObjectField(joFrame, frameInfoClass.pixels, jiArr); #else //可用 //可以使用GetIntArrayRegion函数来把一个 int数组中的所有元素复制到一个C缓冲区中 //SetIntArrayRegion则是逆过程 _env->SetIntArrayRegion(jiArr, 0, 2, abc); _env->SetObjectField(joFrame, company_class.money, jiArr); #endif // LOGI("Java_luo_uvc_jni_ImageProc_getFrame end\n"); return joFrame; } // LOGI("=================Java_luo_uvc_jni_ImageProc_getFrame failed\n"); return 0; } JNIEXPORT jint JNICALL Java_luo_uvc_jni_ImageProc_startRecord (JNIEnv *env, jclass, jstring jstr) { if(writer.isOpened() == false) { prefix = jstringToChar(env, jstr); memset(fileName, 0, NAMELEN); sprintf(fileName, "/storage/sdcard0/Movies/%s.avi", prefix); LOGI("fileName: %s", fileName); FREE(prefix); writer.open(fileName, CV_FOURCC('F', 'L', 'V', '1')/*有效*/, 30, cv::Size(640, 480),true); // writer.open(fileName, CV_FOURCC('M', 'J', 'P', 'G')/*有效*/, 30, cv::Size(640, 480),true); // writer.open(fileName, CV_FOURCC('D', 'I', 'V', 'X')/*有效*/, 30, cv::Size(640, 480),true); // writer.open(fileName, CV_FOURCC('X', 'V', 'I', 'D')/*有效*/, 30, cv::Size(640, 480),true); if(writer.isOpened()) { LOGE("writer open successful!"); return 0; } else { LOGE("writer open failed!"); return -2; } } else { //实际上已经在录像 return -1; } } JNIEXPORT jint JNICALL Java_luo_uvc_jni_ImageProc_stopRecord (JNIEnv *, jclass) { if (writer.isOpened()) { //如果正在录像,则停止录像 writer.release(); LOGI("%s end record!", fileName); return 0; } return -1; } JNIEXPORT jint JNICALL Java_luo_uvc_jni_ImageProc_getWidth(JNIEnv* env, jclass obj) { return 0; } JNIEXPORT jint JNICALL Java_luo_uvc_jni_ImageProc_getHeight(JNIEnv* env, jclass obj) { return 0; } //jstring to char* char* jstringToChar(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = env->FindClass("java/lang/String"); jstring strencode = env->NewStringUTF("utf-8"); jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode); jsize alen = env->GetArrayLength(barr); jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE); if (alen > 0) { rtn = (char*)calloc(1, alen + 1); memcpy(rtn, ba, alen); rtn[alen] = 0; } env->ReleaseByteArrayElements(barr, ba, 0); return rtn; } #ifdef __cplusplus } #endif /* end of extern */
package luo.uvc.jni; import java.text.SimpleDateFormat; import java.util.Date; import luo.uvc.jni.ImageProc.FrameInfo; import android.R.bool; import android.R.string; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.Bitmap.Config; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.widget.Toast; public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback, Runnable { public static final String TAG = "UVCCameraPreview"; protected Context context; private SurfaceHolder holder; Thread mainLoop = null; public static final int SET_PREVIEW_TEXT = 0; public static final int SET_RECORD_TEXT = 1; private boolean mIsOpened = false; private boolean mIsRecording = false; private boolean shouldStop = false; public callback textCallback; // The following variables are used to draw camera images. private int winWidth = 0; private int winHeight = 0; private Rect rect; private int dw, dh; private float rate; public CameraPreview(Context context) { super(context); // TODO Auto-generated constructor stub this.context = context; Log.d(TAG, "CameraPreview constructed"); setFocusable(true); holder = getHolder(); holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL); } //注意:使用findViewById获取CameraPreview,会调用这个构造函数 public CameraPreview(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; Log.d(TAG, "CameraPreview constructed"); setFocusable(true); holder = getHolder(); holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL); } public void initPreview() { int index = -1; if (mIsOpened == false) { if (0 == ImageProc.connectCamera(index)) { Log.i(TAG, "open uvc success!!!"); mIsOpened = true; textCallback.setViewText(SET_PREVIEW_TEXT, "关闭"); if (null != mainLoop) { shouldStop = false; Log.i(TAG, "preview mainloop starting..."); mainLoop.start(); } Toast.makeText(context.getApplicationContext(), "成功打开摄像头", Toast.LENGTH_SHORT).show(); } else { Log.i(TAG, "open uvc fail!!!"); mIsOpened = false; Toast.makeText(context.getApplicationContext(), "摄像头打开失败", Toast.LENGTH_SHORT).show(); } } else { uninitPreview(); } } public void uninitPreview() { //结束录制 uninitRecord(); //停止预览线程 if (null != mainLoop) { Log.i(TAG, mainLoop.isAlive() ? "mainloop is alive!" : "mainloop is not alive!"); if (mainLoop.isAlive()) { shouldStop = true; while (shouldStop) { try { Thread.sleep(100); // wait for thread stopping } catch (Exception e) { } } } } //关闭camera if (mIsOpened) { mIsOpened = false; ImageProc.releaseCamera(); textCallback.setViewText(SET_PREVIEW_TEXT, "打开"); Log.i(TAG, "release camera..."); } } public void initRecord() { if(mIsOpened) { if(mIsRecording == false) { Log.i(TAG, "init camera record!"); Date date = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); String dateString = simpleDateFormat.format(date); if(null == dateString) { dateString = "luoyouren"; } Log.i(TAG, dateString); if(0 == ImageProc.startRecord(dateString)) { mIsRecording = true; textCallback.setViewText(SET_RECORD_TEXT, "停止"); Toast.makeText(context.getApplicationContext(), "开始录制...", Toast.LENGTH_SHORT).show(); } else { mIsRecording = false; Log.e(TAG, "init camera record failed!"); Toast.makeText(context.getApplicationContext(), "录制启动失败!", Toast.LENGTH_SHORT).show(); } return; } else { uninitRecord(); return; } } else { Log.e(TAG, "camera has not been opened!"); return; } } public void uninitRecord() { if(mIsRecording) { Log.i(TAG, "camera is already recording! So we stop it."); ImageProc.stopRecord(); mIsRecording = false; textCallback.setViewText(SET_RECORD_TEXT, "录制"); return; } } public boolean isOpen() { return mIsOpened; } public boolean isRecording() { return mIsRecording; } @Override public void run() { // TODO Auto-generated method stub while (true && mIsOpened) { // get camera frame FrameInfo frame = ImageProc.getFrame(); if (null == frame) { continue; } int w = frame.getWidth(); int h = frame.getHeigth(); Log.i(TAG, "frame.width = " + w + " frame.height = " + h); // 根据图像大小更新显示区域大小 // 一般来说图像大小不会变化: 640x480 updateRect(w, h); Bitmap resultImg = Bitmap.createBitmap(w, h, Config.ARGB_8888); resultImg.setPixels(frame.getPixels(), 0, w, 0, 0, w, h); // 刷surfaceview显示 Canvas canvas = getHolder().lockCanvas(); if (canvas != null) { // draw camera bmp on canvas canvas.drawBitmap(resultImg, null, rect, null); getHolder().unlockCanvasAndPost(canvas); } if (shouldStop) { shouldStop = false; Log.i(TAG, "mainloop will stop!"); break; } } Log.i(TAG, "mainloop break while!"); } public void updateRect(int frame_w, int frame_h) { // obtaining display area to draw a large image if (winWidth == 0) { winWidth = this.getWidth(); winHeight = this.getHeight(); if (winWidth * 3 / 4 <= winHeight) { dw = 0; dh = (winHeight - winWidth * 3 / 4) / 2; rate = ((float) winWidth) / frame_w; rect = new Rect(dw, dh, dw + winWidth - 1, dh + winWidth * 3 / 4 - 1); } else { dw = (winWidth - winHeight * 4 / 3) / 2; dh = 0; rate = ((float) winHeight) / frame_h; rect = new Rect(dw, dh, dw + winHeight * 4 / 3 - 1, dh + winHeight - 1); } } } @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { // TODO Auto-generated method stub } @Override public void surfaceCreated(SurfaceHolder arg0) { // TODO Auto-generated method stub mainLoop = new Thread(this); updateRect(512, 512); // 将lena图像加载程序中并进行显示 Bitmap resultImg = BitmapFactory.decodeResource(getResources(), R.drawable.lena); // 刷surfaceview显示 Canvas canvas = getHolder().lockCanvas(); if (canvas != null) { // draw camera bmp on canvas canvas.drawBitmap(resultImg, null, rect, null); getHolder().unlockCanvasAndPost(canvas); } } @Override public void surfaceDestroyed(SurfaceHolder arg0) { // TODO Auto-generated method stub } }
编译、安装、运行后,Camera实时预览正常,和《我的Opencv4Android添加V4L2支持的移植记录(2)》中一样,但是录像失败!
我们可以来看看opencv里面,对于各个平台VideoWriter功能的分发情况
/** * Videowriter dispatching method: it tries to find the first * API that can write a given stream. */ CV_IMPL CvVideoWriter* cvCreateVideoWriter( const char* filename, int fourcc, double fps, CvSize frameSize, int is_color ) { //CV_FUNCNAME( "cvCreateVideoWriter" ); CvVideoWriter *result = 0; #pragma message("------------------------ cvCreateVideoWriter") if(!fourcc || !fps) result = cvCreateVideoWriter_Images(filename); #ifdef HAVE_FFMPEG #pragma message("------------------------ HAVE_FFMPEG") if(!result) result = cvCreateVideoWriter_FFMPEG_proxy (filename, fourcc, fps, frameSize, is_color); #endif #ifdef HAVE_VFW #pragma message("------------------------ HAVE_VFW") if(!result) result = cvCreateVideoWriter_VFW(filename, fourcc, fps, frameSize, is_color); #endif #ifdef HAVE_MSMF #pragma message("------------------------ HAVE_MSMF") if (!result) result = cvCreateVideoWriter_MSMF(filename, fourcc, fps, frameSize, is_color); #endif /* #ifdef HAVE_XINE if(!result) result = cvCreateVideoWriter_XINE(filename, fourcc, fps, frameSize, is_color); #endif */ #ifdef HAVE_AVFOUNDATION #pragma message("------------------------ HAVE_AVFOUNDATION") if (! result) result = cvCreateVideoWriter_AVFoundation(filename, fourcc, fps, frameSize, is_color); #endif #if defined(HAVE_QUICKTIME) || defined(HAVE_QTKIT) #pragma message("------- defined(HAVE_QUICKTIME) || defined(HAVE_QTKIT)") if(!result) result = cvCreateVideoWriter_QT(filename, fourcc, fps, frameSize, is_color); #endif #ifdef HAVE_GSTREAMER #pragma message("------------------------ HAVE_GSTREAMER") if (! result) result = cvCreateVideoWriter_GStreamer(filename, fourcc, fps, frameSize, is_color); #endif #if !defined(HAVE_FFMPEG) && !defined(HAVE_VFW) && !defined(HAVE_MSMF) && !defined(HAVE_AVFOUNDATION) && !defined(HAVE_QUICKTIME) && !defined(HAVE_QTKIT) && !defined(HAVE_GSTREAMER) // If none of the writers is used // these statements suppress 'unused parameter' warnings. (void)frameSize; (void)is_color; #endif #pragma message("------------------------ cvCreateVideoWriter_Images") if(!result) result = cvCreateVideoWriter_Images(filename); return result; }
enum { CV_CAP_ANY =0, // autodetect CV_CAP_MIL =100, // MIL proprietary drivers CV_CAP_VFW =200, // platform native CV_CAP_V4L =200, CV_CAP_V4L2 =200, CV_CAP_FIREWARE =300, // IEEE 1394 drivers CV_CAP_FIREWIRE =300, CV_CAP_IEEE1394 =300, CV_CAP_DC1394 =300, CV_CAP_CMU1394 =300, CV_CAP_STEREO =400, // TYZX proprietary drivers CV_CAP_TYZX =400, CV_TYZX_LEFT =400, CV_TYZX_RIGHT =401, CV_TYZX_COLOR =402, CV_TYZX_Z =403, CV_CAP_QT =500, // QuickTime CV_CAP_UNICAP =600, // Unicap drivers CV_CAP_DSHOW =700, // DirectShow (via videoInput) CV_CAP_MSMF =1400, // Microsoft Media Foundation (via videoInput) CV_CAP_PVAPI =800, // PvAPI, Prosilica GigE SDK CV_CAP_OPENNI =900, // OpenNI (for Kinect) CV_CAP_OPENNI_ASUS =910, // OpenNI (for Asus Xtion) CV_CAP_ANDROID =1000, // Android CV_CAP_ANDROID_BACK =CV_CAP_ANDROID+99, // Android back camera CV_CAP_ANDROID_FRONT =CV_CAP_ANDROID+98, // Android front camera CV_CAP_XIAPI =1100, // XIMEA Camera API CV_CAP_AVFOUNDATION = 1200, // AVFoundation framework for iOS (OS X Lion will have the same API) CV_CAP_GIGANETIX = 1300, // Smartek Giganetix GigEVisionSDK CV_CAP_INTELPERC = 1500 // Intel Perceptual Computing SDK };
看看之前的Cmake的结果对比就知道:
Opencv在我增加V4L2后,也支持NativeCamera和V4L2两种Video I/O,而Ubuntu有FFMPEG的支持(不懂FFMPEG的去问度娘)。并且在cvCreateVideoWriter函数中也没有对NativeCamera的支持,所以我的APP录像失败。
所以如果要让yanzi_OpenCV4Android 能够录像,就要添加FFMPEG的支持!!!!
PS:
昨天我一直在纠结一个问题,VFW和DirectShow都是微软视频处理框架,DirectShow是VFW的升级版。但是在Opencv中,
cvCreateCameraCapture 读摄像头
cvCreateFileCapture 读视频文件
cvCreateVideoWriter 写视频文件
和摄像头相关的cvCreateCameraCapture 里面对DirectShow和VFW都有支持,但是和文件相关的后面两个函数只对VFW有支持。
我猜测大概是DirectShow不善于处理视频文件。
我的Opencv4Android添加V4L2支持的移植记录(3)
标签:
原文地址:http://blog.csdn.net/luoyouren/article/details/51895475