高效地实现Java本地接口摄像头馈送

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

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

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

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

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

顺便说一句...这里的问题是:什么是最有效的方法将一维图像数据的char数组复制到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
}

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

如果你想进一步研究,我会从这里开始。

快乐编程!


我将构造BufferedImage所需的RGB int数组,然后使用一次调用

 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位值兼容)。


作为次要考虑,如果OpenCV返回的图像数据数组与Java所要求的图像数据数组之间的唯一区别是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代码是生锈的,所以这可能不是完全有效的,但它显示了需要什么)。

链接地址: http://www.djcxy.com/p/35397.html

上一篇: Efficiently Implementing Java Native Interface Webcam Feed

下一篇: Starvation in ForkJoinPool with managed blocking