2010-11-28 43 views
10

我正在从一个摄像头获取视频输入并向用户显示运动区域的项目。我在这个项目上的“测试”尝试是使用Java媒体框架来检索网络摄像头源。通过一些实用功能,JMF可以方便地将网络摄像头帧作为BufferedImages返回,从而构建了大量的框架进行处理。但是,我很快意识到JMF不再受Sun/Oracle的支持,并且一些较高的网络摄像头分辨率(720p)无法通过JMF接口访问。高效地实现Java本地接口摄像头馈送

我想继续处理帧为BufferedImages,并使用OpenCV(C++)来源视频饲料。单独使用OpenCV的框架,我发现OpenCV在高效率地返回高清摄像头帧并将它们绘制到屏幕上做得很好。

我认为将这些数据提供给Java并达到相同的效率相当简单。我刚写完JNI DLL将这些数据复制到BufferedImage并将其返回给Java。但是,我发现我正在执行的数据复制量实际上阻碍了性能。我的目标是30 FPS,但单独需要大约100毫秒才能将OpenCV返回的字符数组中的数据复制到Java BufferedImage中。相反,我看到2-5 FPS。

当返回帧捕获时,OpenCV提供了一个指向1D char数组的指针。这些数据需要提供给Java,显然我没有时间复制它。

我需要一个更好的解决方案来将这些帧捕获到一个BufferedImage中。我正在考虑一些解决方案,我认为这些解决方案都不是很好(相当肯定它们的性能还不错):

(1)重写BufferedImage,并通过调用本地调用来从各种BufferedImage方法返回像素数据DLL。 (而不是立即进行数组复制,我根据调用代码的要求返回单个像素)。请注意,调用代码通常需要图像中的所有像素来绘制图像或对其进行处理,因此这个单独的像素抓取操作将在2D for-loop中实现。

(2)指示BufferedImage使用java.nio.ByteBuffer以某种方式直接访问由OpenCV返回的char数组中的数据。希望有关如何完成这些任何提示。

(3)用C++做所有事情,忘记Java。好吧,是的,这听起来像是最合乎逻辑的解决方案,但我没有时间从头开始这个为期多月的项目。

截至目前,我的JNI代码已被写入以返回BufferedImage,但此时我愿意接受1D char数组的返回,然后将其放入BufferedImage中。

顺便说一句......这里的问题是:什么是最有效的方法复制一个图像数据的一维字符数组到一个BufferedImage?

提供的是,我使用源图像从OpenCV中并复制到的BufferedImage的(低效的)代码:

JNIEXPORT jobject JNICALL Java_graphicanalyzer_ImageFeedOpenCV_getFrame 
    (JNIEnv * env, jobject jThis, jobject camera) 
{ 
//get the memory address of the CvCapture device, the value of which is encapsulated in the camera jobject 
jclass cameraClass = env->FindClass("graphicanalyzer/Camera"); 
jfieldID fid = env->GetFieldID(cameraClass,"pCvCapture","I"); 

//get the address of the CvCapture device 
int a_pCvCapture = (int)env->GetIntField(camera, fid); 

//get a pointer to the CvCapture device 
    CvCapture *capture = (CvCapture*)a_pCvCapture; 

//get a frame from the CvCapture device 
IplImage *frame = cvQueryFrame(capture); 

//get a handle on the BufferedImage class 
jclass bufferedImageClass = env->FindClass("java/awt/image/BufferedImage"); 
if (bufferedImageClass == NULL) 
{ 
    return NULL; 
} 

//get a handle on the BufferedImage(int width, int height, int imageType) constructor 
jmethodID bufferedImageConstructor = env->GetMethodID(bufferedImageClass,"<init>","(III)V"); 

//get the field ID of BufferedImage.TYPE_INT_RGB 
jfieldID imageTypeFieldID = env->GetStaticFieldID(bufferedImageClass,"TYPE_INT_RGB","I"); 

//get the int value from the BufferedImage.TYPE_INT_RGB field 
jint imageTypeIntRGB = env->GetStaticIntField(bufferedImageClass,imageTypeFieldID); 

//create a new BufferedImage 
jobject ret = env->NewObject(bufferedImageClass, bufferedImageConstructor, (jint)frame->width, (jint)frame->height, imageTypeIntRGB); 

//get a handle on the method BufferedImage.getRaster() 
jmethodID getWritableRasterID = env->GetMethodID(bufferedImageClass, "getRaster", "()Ljava/awt/image/WritableRaster;"); 

//call the BufferedImage.getRaster() method 
jobject writableRaster = env->CallObjectMethod(ret,getWritableRasterID); 

//get a handle on the WritableRaster class 
jclass writableRasterClass = env->FindClass("java/awt/image/WritableRaster"); 

//get a handle on the WritableRaster.setPixel(int x, int y, int[] rgb) method 
jmethodID setPixelID = env->GetMethodID(writableRasterClass, "setPixel", "(II[I)V"); //void setPixel(int, int, int[]) 

//iterate through the frame we got above and set each pixel within the WritableRaster 
jintArray rgbArray = env->NewIntArray(3); 
jint rgb[3]; 
char *px; 
for (jint x=0; x < frame->width; x++) 
{ 
    for (jint y=0; y < frame->height; y++) 
    { 
    px = frame->imageData+(frame->widthStep*y+x*frame->nChannels); 
    rgb[0] = abs(px[2]); // OpenCV returns BGR bit order 
    rgb[1] = abs(px[1]); // OpenCV returns BGR bit order 
    rgb[2] = abs(px[0]); // OpenCV returns BGR bit order 
    //copy jint array into jintArray 
    env->SetIntArrayRegion(rgbArray,0,3,rgb); //take values in rgb and move to rgbArray 
    //call setPixel() this is a copy operation 
    env->CallVoidMethod(writableRaster,setPixelID,x,y,rgbArray); 
    } 
} 

return ret; //return the BufferedImage 
} 
+0

虽然看起来像是在获取CvCapture设备地址和创建BufferedImage方面存在重大开销,但逐行执行的代码执行时间证明for循环需要将近100毫秒,而其余的代码在<5毫秒内执行。 – Jason 2010-11-28 22:20:18

回答

0

管理加快使用NIO的ByteBuffer的过程。

在C++ JNI侧...

JNIEXPORT jobject JNICALL Java_graphicanalyzer_ImageFeedOpenCV_getFrame 
    (JNIEnv * env, jobject jThis, jobject camera) 
{ 
    //... 

    IplImage *frame = cvQueryFrame(pCaptureDevice); 

    jobject byteBuf = env->NewDirectByteBuffer(frame->imageData, frame->imageSize); 

    return byteBuf; 
} 

,并在Java端...

void getFrame(Camera cam) 
{ 
    ByteBuffer frameData = cam.getFrame(); //NATIVE call 

    byte[] imgArray = new byte[frame.data.capacity()]; 
    frameData.get(imgArray); //although it seems like an array copy, this call returns very quickly 
    DataBufferByte frameDataBuf = new DataBufferByte(imgArray,imgArray.length); 

    //determine image sample model characteristics 
    int dataType = DataBuffer.TYPE_BYTE; 
    int width = cam.getFrameWidth(); 
    int height = cam.getFrameHeight(); 
    int pixelStride = cam.getPixelStride(); 
    int scanlineStride = cam.getScanlineStride(); 
    int bandOffsets = new int[] {2,1,0}; //BGR 

    //create a WritableRaster with the DataBufferByte 
    PixelInterleavedSampleModel pism = new PixelInterleavedSampleModel 
    (
     dataType, 
     width, 
     height, 
     pixelStride, 
     scanlineStride, 
     bandOffsets 
    ); 
    WritableRaster raster = new ImgFeedWritableRaster(pism, frameDataBuf, new Point(0,0)); 

    //create the BufferedImage 
    ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); 
    ComponentColorModel cm = new ComponentColorModel(cs, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); 
    BufferedImage newImg = new BufferedImage(cm,raster,false,null); 

    handleNewImage(newImg); 
} 

使用java.nio.ByteBuffer中,我能很快解决返回的字符数组由OpenCV代码没有(显然)做了很多可怕的阵列复制。

2

我将构建所需的RGB int数组由BufferedImage然后用一个调用

void setRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) 

立即设置整个图像数据数组。或者至少,它的很大一部分。

无需定时它,我会怀疑它的每个像素调用

env->SetIntArrayRegion(rgbArray,0,3,rgb); 
env->CallVoidMethod(writableRaster,setPixelID,x,y,rgbArray); 

这些都占用的时间的大部分份额。

编辑:它可能是方法的调用,而不是操纵内存,本身,花费时间。因此,在您的JNI代码中构建数据并将其以块或单个命中复制到Java映像。一旦你创建并固定了一个Java int [],你可以通过本机指针来访问它。然后调用setRGB将复制数组到您的图像。

注意:你还是要至少一次复制数据,但这样做的所有像素在通过1所函数调用的一击将远远大于通过2×N个函数调用单独做他们更有效率。

编辑2:

回顾我的JNI代码,我只使用过的字节数组,但原则是INT阵列相同。用途:

NewIntArray 

创建一个int数组,并

GetIntArrayElements 

引脚,并得到一个指针,而当你完成后,

ReleaseIntArrayElements 

释放它,记住使用该标志将数据复制回Java的内存堆。

然后,你应该能够使用您的Java int数组句柄调用setRGB功能。请记住,这实际上是设置RGBA像素,所以包括alpha在内的4个通道不仅仅是3个(Java中的RGB名称似乎早于alpha通道,但大多数所谓的方法都与32位值)。

+0

我确实记下了这些每个像素的调用,并发现这些情况。如何在不复制数据的情况下有效构造RGB int数组? – Jason 2010-11-28 23:08:21

+0

在线JNI培训课程提供了有关操作数组的有用信息:http://java.sun.com/developer/onlineTraining/Programming/JDCBook/jnistring.html – 2010-11-28 23:50:06

1

作为次要的考虑,如果是由Java的要求的OpenCV返回的图像数据阵列和什么之间的唯一区别是BGR VS RGB,然后

px = frame->imageData+(frame->widthStep*y+x*frame->nChannels); 
rgb[0] = abs(px[2]); // OpenCV returns BGR bit order 
rgb[1] = abs(px[1]); // OpenCV returns BGR bit order 
rgb[2] = abs(px[0]); // OpenCV returns BGR bit order 

是将它们转换相对低效的方式。相反,你可以这样做:

uint32 px = frame->imageData+(frame->widthStep*y+x*frame->nChannels); 
javaArray[ofs]=((px&0x00FF0000)>>16)|(px&0x0000FF00)|((px&0x000000FF)<<16); 

(注意我的C代码是生锈,所以这可能不是完全有效的,但它显示了所需要的)。

3

还有另一种选择,如果你想使你的代码非常快,并且仍然使用Java。AWT窗口工具包有一个直接的本地接口,您可以使用它来使用C或C++绘制到AWT曲面。因此,不需要将任何内容复制到Java,因为您可以直接从C或C++的缓冲区中进行渲染。我不确定如何做到这一点的细节,因为我没有看过它,但我知道它包含在标准的JRE发行版中。使用这种方法,如果您愿意,您可能会接近相机的FPS极限,而不是奋力达到30 FPS。

如果你想进一步研究,我会开始herehere

快乐编程!