2010-11-28 10 views
10

मैं ऐसे प्रोजेक्ट पर काम कर रहा हूं जो वेबकैम से वीडियो इनपुट लेता है और उपयोगकर्ता को गति के क्षेत्र प्रदर्शित करता है। इस परियोजना में मेरा "बीटा" प्रयास वेब कैमरा फ़ीड पुनर्प्राप्त करने के लिए जावा मीडिया फ्रेमवर्क का उपयोग करना था। कुछ उपयोगिता कार्यों के माध्यम से, जेएमएफ आसानी से वेबकैम फ्रेम को BufferedImages के रूप में लौटाता है, जिसे मैंने प्रक्रिया के लिए चारों ओर ढांचे का एक महत्वपूर्ण मात्रा बनाया है। हालांकि, मुझे जल्द ही एहसास हुआ कि जेएमएफ अब सूर्य/ओरेकल द्वारा समर्थित नहीं है, और कुछ उच्च वेबकैम संकल्प (720 पी) जेएमएफ इंटरफ़ेस के माध्यम से उपलब्ध नहीं हैं।जावा मूल इंटरफ़ेस वेबकैम फ़ीड कुशलतापूर्वक कार्यान्वित करना

मैं BufferedImages के रूप में फ़्रेम को प्रोसेस करना जारी रखना चाहता हूं, और वीडियो फ़ीड स्रोत के लिए ओपनसीवी (सी ++) का उपयोग करना चाहता हूं। ओपनसीवी के ढांचे का अकेले उपयोग करके, मैंने पाया है कि ओपनसीवी कुशलतापूर्वक हाई-डेफ वेबकैम फ्रेम लौटने और उन्हें स्क्रीन पर पेंट करने का अच्छा काम करता है।

मुझे लगा कि यह जावा में इस डेटा को खिलाने और समान दक्षता प्राप्त करने के लिए बहुत सरल होगा। मैंने इस डेटा को BufferedImage में कॉपी करने और इसे जावा पर वापस करने के लिए जेएनआई डीएलएल लिखना समाप्त कर दिया। हालांकि, मुझे लगता है कि मैं जो डेटा कॉपी कर रहा हूं वह वास्तव में प्रदर्शन में बाधा डाल रहा है। मैं 30 एफपीएस को लक्षित कर रहा हूं, लेकिन जावा बुफर्ड इमेज में ओपनसीवी द्वारा लौटाए गए चार सरणी से डेटा कॉपी करने के लिए लगभग 100 एमएससी अकेले ही लेता है। इसके बजाय, मैं लगभग 2-5 एफपीएस देख रहा हूं।

फ्रेम कैप्चर लौटने पर, ओपनसीवी एक 1 डी चार सरणी के लिए एक सूचक प्रदान करता है। यह डेटा जावा को प्रदान किया जाना चाहिए, और जाहिर है कि मेरे पास इसमें से किसी एक को कॉपी करने का समय नहीं है।

मुझे इन फ्रेम कैप्चर को BufferedImage में प्राप्त करने के लिए एक बेहतर समाधान की आवश्यकता है। कुछ समाधान मैं विचार कर रहा हूँ, जिनमें से कोई भी मुझे लगता है कि बहुत अच्छा (काफी हद तक निश्चित वे भी खराब प्रदर्शन होता है) हैं:

(1) BufferedImage ओवरराइड, और के मूल निवासी कॉल करके विभिन्न BufferedImage तरीकों से पिक्सेल डेटा वापसी DLL। (एक बार में सरणी की प्रतिलिपि बनाने के बजाय, मैं कॉलिंग कोड द्वारा अनुरोध किए गए व्यक्तिगत पिक्सल लौटाता हूं)। ध्यान दें कि कॉलिंग कोड को छवि को पेंट करने या इसे संसाधित करने के लिए आम तौर पर छवि में सभी पिक्सेल की आवश्यकता होती है, इसलिए यह व्यक्तिगत पिक्सेल-ग्रैप ऑपरेशन 2 डी फॉर-लूप में लागू किया जाएगा।

(2) BufferedImage को java.nio.ByteBuffer का उपयोग करने के लिए किसी भी तरह से ओपनसीवी द्वारा लौटाए गए चार सरणी में सीधे डेटा तक पहुंचने के लिए निर्देश दें। यह कैसे किया जाता है इस बारे में किसी भी सुझाव की सराहना करेंगे।

(3) सी ++ में सबकुछ करें और जावा भूल जाएं। खैर, हाँ, यह सबसे तार्किक समाधान की तरह लगता है, हालांकि मेरे पास इस कई महीने की परियोजना शुरू करने के लिए समय नहीं होगा।

अभी तक, मेरा जेएनआई कोड बुफर्ड इमेज को वापस करने के लिए लिखा गया है, हालांकि इस बिंदु पर मैं 1 डी चार सरणी की वापसी स्वीकार करने के लिए तैयार हूं और फिर इसे बुफर्ड इमेज में डाल दूंगा।

वैसे ... सवाल यह है कि: BufferedImage में छवि डेटा के 1 डी चार सरणी को कॉपी करने का सबसे प्रभावी तरीका क्या है?

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

हालांकि ऐसा लगता है कि सीवी कैप्चर डिवाइस का पता प्राप्त करने और बुफर्ड इमेज बनाने में महत्वपूर्ण ओवरहेड है, कोड निष्पादन के एक लाइन-बाय-लाइन समय से साबित होता है कि फॉर-लूप लगभग 100 एमसीसी लेता है, जबकि शेष कोड <5 एमसीसी में निष्पादित करता है। – Jason

उत्तर

0

एक एनआईओ बाइटबफर का उपयोग कर प्रक्रिया को तेज करने के लिए प्रबंधित किया गया।

सी ++ 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; 
} 

और जावा तरफ ...

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 का उपयोग करना, मैं जल्दी से चार सरणी लौटे पता कर सकते हैं ओपनसीवी कोड द्वारा (स्पष्ट रूप से) बहुत भयानक सरणी प्रतिलिपि कर रहा है।

+0

मदद के लिए धन्यवाद! 30 एफपीएस प्राप्त करने के लिए प्रबंधित। – Jason

+0

क्या आप सुनिश्चित हैं कि 'IplImage * frame', जिसे स्पष्ट रूप से cvQueryFrame द्वारा आवंटित किया गया है, लीक नहीं हो रहा है ... या यह एक स्थिर संरचना है - लेकिन फिर थ्रेडिंग समस्याओं से सावधान रहें। –

+0

cvQueryFrame() के लिए प्रलेखन निर्दिष्ट करता है "लौटाई गई छवि को उपयोगकर्ता द्वारा जारी या संशोधित नहीं किया जाना चाहिए।" क्या इसका मतलब है कि मैं अच्छा हूँ? – Jason

2

मैं BufferedImage द्वारा आरजीबी पूर्णांक सरणी आवश्यक बनाएगी और फिर

लिए एक कॉल का उपयोग करें:

परंतु (अक्षम) कोड है कि मैं OpenCV से स्रोत छवि के लिए इस्तेमाल करते हैं और 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); 

के प्रति पिक्सेल कॉल है।

संपादित करें: यह समय ले रहा है, स्मृति के हेरफेर के बजाय विधि invocations की संभावना होगी। तो अपने जेएनआई कोड में डेटा बनाएं और इसे जावा छवि में ब्लॉक या एक हिट में कॉपी करें। एक बार जब आप जावा int बनाते हैं और पिन करते हैं [] आप देशी पॉइंटर्स के माध्यम से इसे एक्सेस कर सकते हैं। फिर सेटआरबीबी के लिए एक कॉल सरणी को आपकी छवि में कॉपी करेगा।

नोट: आपको अभी भी कम से कम एक बार डेटा कॉपी करना है, लेकिन 1 पिक्चर कॉल के माध्यम से एक हिट में सभी पिक्सल करना 2 एक्स एन फ़ंक्शन कॉल के माध्यम से उन्हें अलग-अलग करने से कहीं अधिक कुशल होगा।

संपादित करें 2:

मेरी JNI कोड की समीक्षा, मैं सिर्फ कभी बाइट सरणियों का इस्तेमाल किया है, लेकिन सिद्धांतों पूर्णांक सरणियों के लिए ही हैं। का उपयोग करें:

NewIntArray 

किसी पूर्णांक सरणी बनाने के लिए, और

GetIntArrayElements 

पिन और एक सूचक मिलता है, और जब आप पूरा कर लिए,

ReleaseIntArrayElements 

, इसे जारी करने की याद करने के लिए जावा की मेमोरी ढेर में डेटा की प्रतिलिपि बनाने के लिए ध्वज का उपयोग करें।

फिर, आप setRGB फ़ंक्शन को आमंत्रित करने के लिए अपने जावा int सर हैंडल का उपयोग करने में सक्षम होना चाहिए।

यह भी याद रखें कि यह वास्तव में आरजीबीए पिक्सेल सेट कर रहा है, इसलिए अल्फा समेत 4 चैनल, केवल तीन नहीं (जावा में आरजीबी नाम अल्फा चैनल की भविष्यवाणी करते हैं, लेकिन अधिकांश नामित विधियां 32 बिट के साथ संगत हैं मूल्य)।

+0

मैंने उन प्रति-पिक्सेल कॉल का समय दिया और उन्हें यह मामला पाया। मैं डेटा कॉपी किए बिना आरजीबी int सरणी को कुशलता से कैसे बना सकता हूं? – Jason

+0

ऑनलाइन जेएनआई प्रशिक्षण पाठ्यक्रम में सरणी में हेरफेर करने के लिए उपयोगी जानकारी है: http://java.sun.com/developer/onlineTraining/Programming/JDCBook/jnistring.html –

+0

धन्यवाद मैं इसे एक शॉट दूंगा। – Jason

1

एक माध्यमिक विचार के रूप में, यदि OpenCV द्वारा दिया छवि डेटा सरणी और क्या के बीच फर्क सिर्फ इतना है जावा के लिए आवश्यक है, बीजीआर बनाम आरजीबी है तो

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); 

(ध्यान दें मेरी सी कोड जंग लगी है, इसलिए यह पूरी तरह मान्य नहीं हो सकता है, लेकिन यह पता चलता है कि जरूरत है): इसके बजाय आप की तरह कुछ कर सकता है।

3

यदि आप अपना कोड वास्तव में तेज़ बनाना चाहते हैं और अभी भी जावा का उपयोग करना चाहते हैं तो एक और विकल्प है।एडब्ल्यूटी विंडोिंग टूलकिट में प्रत्यक्ष देशी इंटरफ़ेस है जिसका उपयोग आप सी या सी ++ का उपयोग करके एडब्ल्यूटी सतह पर आकर्षित करने के लिए कर सकते हैं। इस प्रकार, जावा पर कुछ भी कॉपी करने की आवश्यकता नहीं होगी, क्योंकि आप सीधे सी या सी ++ में बफर से प्रस्तुत कर सकते हैं। मुझे इस बारे में निश्चित जानकारी नहीं है कि इसे कैसे किया जाए क्योंकि मैंने इसे थोड़ी देर में नहीं देखा है, लेकिन मुझे पता है कि यह मानक जेआरई वितरण में शामिल है। इस विधि का उपयोग करते हुए, यदि आप चाहें तो कैमरे की एफपीएस सीमा तक पहुंच सकते हैं, बजाय 30 एफपीएस तक पहुंचने के लिए संघर्ष करना।

यदि आप इसे और अधिक शोध करना चाहते हैं तो मैं here और here शुरू करूंगा।

हैप्पी प्रोग्रामिंग!

संबंधित मुद्दे