有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

高效实现Java本机接口网络摄像头提要的性能

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

我想继续将帧作为缓冲图像进行处理,并使用OpenCV(C++)作为视频源。仅使用OpenCV的框架,我发现OpenCV在高效返回高清网络摄像头帧并将其绘制到屏幕上方面做得很好

我认为将这些数据输入Java并获得同样的效率是非常简单的。我刚刚完成了JNIDLL的编写,以便将这些数据复制到BuffereImage并将其返回到Java。然而,我发现我正在进行的数据复制量确实影响了性能。我的目标是30 FPS,但要将OpenCV返回的字符数组中的数据复制到Java BuffereImage中,仅此一步就需要大约100毫秒。相反,我看到的是2-5帧

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

我需要一个更好的解决方案来将这些帧捕获放到BuffereImage中。我正在考虑的几个解决方案,我认为没有一个是非常好的(相当肯定它们也会表现不佳):

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

(2)指示BuffereImage使用java。尼奥。ByteBuffer以某种方式直接访问OpenCV返回的字符数组中的数据。如果您有任何关于如何做到这一点的建议,我们将不胜感激

(3)以C++为对象,并忘记java。嗯,是的,这听起来像是最合乎逻辑的解决方案,但是我没有时间从头开始这个多月的项目

到目前为止,我的JNI代码已经编写好返回BuffereImage,但是现在我愿意接受1D字符数组的返回,然后将其放入BuffereImage中

顺便说一下。。。这里的问题是:将图像数据的1D字符数组复制到BuffereImage中最有效的方法是什么

提供了(低效)代码,用于从OpenCV获取图像并复制到BuffereImage:

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
}

共 (4) 个答案

  1. # 1 楼答案

    如果您希望使代码真正快速并仍然使用Java,还有另一种选择。AWT窗口工具包有一个直接的本地接口,可以使用C或C++绘制到AWT表面。因此,不需要复制任何东西到java,因为你可以直接从C++或C++中的缓冲区中直接呈现。我不确定如何做到这一点的具体细节,因为我已经有一段时间没有看它了,但我知道它包含在标准JRE发行版中。使用此方法,如果愿意,您可能会接近相机的FPS限制,而不是勉强达到30 FPS

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

    快乐编程

  2. # 2 楼答案

    我将构造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将数组复制到图像中

    注意:您仍然需要至少复制一次数据,但是通过一个函数调用一次完成所有像素的操作要比通过2 x N函数调用单独完成操作效率高得多

    编辑2:

    回顾我的JNI代码,我只使用过字节数组,但对int数组的原理是相同的。使用:

    NewIntArray
    

    要创建int数组,请执行以下操作:

    GetIntArrayElements
    

    锁定它并获取指针,完成后

    ReleaseIntArrayElements
    

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

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

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

  3. # 3 楼答案

    设法使用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。尼奥。通过使用ByteBuffer,我可以快速处理OpenCV代码返回的char数组,而不用(显然)进行太多可怕的数组复制

  4. # 4 楼答案

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