首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >高效实现Java原生接口Webcam Feed

高效实现Java原生接口Webcam Feed
EN

Stack Overflow用户
提问于 2010-11-29 06:18:19
回答 4查看 2.6K关注 0票数 10

我正在做一个项目,该项目从网络摄像头获取视频输入,并向用户显示运动区域。我在这个项目中的"beta“尝试是使用Java Media Framework来检索网络摄像头提要。通过一些实用函数,JMF可以方便地将摄像头帧作为BufferedImages返回,我围绕它构建了大量的框架进行处理。然而,我很快意识到,Sun/Oracle不再很好地支持JMF,并且一些更高的网络摄像头分辨率(720p)无法通过JMF接口访问。

我想继续以BufferedImages的形式处理帧,并使用OpenCV (C++)来获取视频提要。单独使用OpenCV的框架,我发现OpenCV在高效地返回高清摄像头帧并将它们绘制到屏幕上方面做得很好。

我认为将这些数据输入到Java中并达到同样的效率是非常简单的。我刚刚完成了JNI的编写,以便将这些数据复制到BufferedImage中并将其返回给Java。然而,我发现我正在做的大量数据复制确实阻碍了性能。我的目标是30FPS,但仅将OpenCV返回的字符数组中的数据复制到Java中就需要大约100毫秒。取而代之的是,我看到了大约2-5 FPS。

返回帧捕获时,OpenCV提供指向一维字符数组的指针。这些数据需要提供给Java,显然我没有时间复制这些数据。

我需要一个更好的解决方案来让这些帧捕获到一个BufferedImage。我正在考虑的一些解决方案,我认为没有一个是非常好的(相当肯定它们的性能也会很差):

(1)重写BufferedImage,并通过本机调用DLL从各种BufferedImage方法返回像素数据。(我不是一次复制数组,而是按照调用代码的要求返回单个像素)。注意,调用代码通常需要图像中的所有像素来绘制图像或处理图像,因此这种单独的像素获取操作将在2D for循环中实现。

(2)指示BufferedImage使用java.nio.ByteBuffer以某种方式直接访问OpenCV返回的字符数组中的数据。将感谢任何关于如何做到这一点的提示。

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

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

顺便说一下..。这里的问题是:将图像数据的1Dchar数组复制到BufferedImage中最有效的方法是什么?

下面是我用来从OpenCV中源图像并复制到BufferedImage中的(低效)代码:

代码语言:javascript
复制
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
}
EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2010-12-01 07:13:21

已使用NIO ByteBuffer加速此过程。

在C++ JNI端...

代码语言:javascript
复制
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方面...

代码语言:javascript
复制
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代码返回的字符数组,而无需(显然)执行太多可怕的数组复制。

票数 0
EN

Stack Overflow用户

发布于 2011-11-10 06:39:24

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

如果你想进一步研究这个问题,我可以从herehere开始。

祝您编程愉快!

票数 3
EN

Stack Overflow用户

发布于 2010-11-29 06:55:38

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

代码语言:javascript
复制
 void setRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) 

一次设置整个图像数据数组。或者至少是很大一部分。

在没有计时的情况下,我会怀疑是对每个像素的调用

代码语言:javascript
复制
env->SetIntArrayRegion(rgbArray,0,3,rgb);
env->CallVoidMethod(writableRaster,setPixelID,x,y,rgbArray);

它们占据了大部分的时间。

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

注意:您仍然必须至少复制数据一次,但是通过一个函数调用在一次命中中复制所有像素将比通过2 x N个函数调用单独执行它们效率高得多。

编辑2:

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

代码语言:javascript
复制
NewIntArray

创建一个int数组,并

代码语言:javascript
复制
GetIntArrayElements

来固定它并获得一个指针,当你完成时,

代码语言:javascript
复制
ReleaseIntArrayElements

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

然后,您应该能够使用Java int数组句柄来调用setRGB函数。

还要记住,这实际上是设置RGBA像素,因此4个通道,包括alpha,而不仅仅是3个( Java中的RGB名称似乎早于alpha通道,但大多数所谓的方法都与32位值兼容)。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4299449

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档