録画したビデオとライブビデオを分析するビデオコンテンツ分析アプリケーションを作成しています。
私はopenglを使用してqtインターフェースにビデオを表示します(qglwidgetsを使用)。グラフィックカードがサポートしている場合は、画像バッファオブジェクトでテクスチャマッピングを使用しています(参照: http: //www.songho.ca/opengl/gl_pbo.html)。ビデオを表示します(OpenCVのIPLImageからロードされます)。
問題は、アプリケーションのメモリが時間の経過とともに増加し続けることです。約 1秒あたり4〜8KB。これを確認するためにタスクマネージャーを使用しています。
テクスチャが解放されず、メモリ使用量につながるという投稿をたくさん見たので、ビデオのレンダリングに関する問題を絞り込みましたが、問題の解決策を見つけることができませんでした。
initializeGL()でglGenTexturesのみを使用しているため、テクスチャは1回だけ生成され、再利用されます。
問題が存在するコードは次のとおりです。
void paintGL(){
static int index = 0;
int nextIndex = 0; // pbo index used for next frame
if(paintFlag){
if(pboMode > 0) {
// "index" is used to copy pixels from a PBO to a texture object "nextIndex" is used to update pixels in a PBO
if(pboMode == 1){
// In single PBO mode, the index and nextIndex are set to 0
index = nextIndex = 0;
}
else if(pboMode == 2)
{
// In dual PBO mode, increment current index first then get the next index
index = (index + 1) % 2;
nextIndex = (index + 1) % 2;
}
// start to copy from PBO to texture object ///////
// bind the texture and PBO
glBindTexture(GL_TEXTURE_2D, texture);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[index]);
// copy pixels from PBO to texture object
// Use offset instead of ponter.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT, GL_BGR, GL_UNSIGNED_BYTE, 0);
// measure the time copying data from PBO to texture object
//t1.stop();
//copyTime = t1.getElapsedTimeInMilliSec();
///////////////////////////////////////////////////
// start to modify pixel values ///////////////////
// t1.start();
// bind PBO to update pixel values
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[nextIndex]);
// map the buffer object into client's memory
// Note that glMapBufferARB() causes sync issue.
// If GPU is working with this buffer, glMapBufferARB() will wait(stall)
// for GPU to finish its job. To avoid waiting (stall), you can call
// first glBufferDataARB() with NULL pointer before glMapBufferARB().
// If you do that, the previous data in PBO will be discarded and
// glMapBufferARB() returns a new allocated pointer immediately
// even if GPU is still working with the previous data.
glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, DATA_SIZE, 0, GL_STREAM_DRAW_ARB);
GLubyte* ptr = (GLubyte*)glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB);
if(ptr)
{
// update data directly on the mapped buffer
//updatePixels(ptr, DATA_SIZE);
memcpy(ptr,original->imageData,DATA_SIZE);
glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB); // release pointer to mapping buffer
}
// measure the time modifying the mapped buffer
//t1.stop();
//updateTime = t1.getElapsedTimeInMilliSec();
///////////////////////////////////////////////////
// it is good idea to release PBOs with ID 0 after use.
// Once bound with 0, all pixel operations behave normal ways.
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
}
else
{
///////////////////////////////////////////////////
// start to copy pixels from system memory to textrure object
//t1.start();
glBindTexture(GL_TEXTURE_2D, texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT, GL_BGR, GL_UNSIGNED_BYTE, (GLvoid*)original->imageData);
//t1.stop();
//copyTime = t1.getElapsedTimeInMilliSec();
}
paintFlag=false;
}
// clear buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glBegin(GL_QUADS);
glTexCoord2i(0,1); glVertex2i(0,HEIGHT);
glTexCoord2i(0,0); glVertex2i(0,0);
glTexCoord2i(1,0); glVertex2i(WIDTH,0);
glTexCoord2i(1,1); glVertex2i(WIDTH,HEIGHT);
glEnd();
glFlush();
glBindTexture(GL_TEXTURE_2D, 0);
swapBuffers();
glDeleteBuffers(1,&texture);
updateGL();
}
コードはチュートリアルとほとんど同じです。ただし、私のテクスチャデータは、別のスレッドによって継続的に更新されるIplImage構造から取得されます。また、同期の目的でBoostのlock_guardを使用しています。
私がここでやっていることは何か問題がありますか?
編集:残りのコードを追加しています:
//Constructor, this is where all the allocation happens
const int DATA_SIZE = WIDTH * HEIGHT * 3;
QGLCanvas::QGLCanvas(QWidget* parent,QString caption)
: QGLWidget(parent)
{
imageFormat=QImage::Format_RGB888;
this->name=caption;
original=cvCreateImage(cvSize(WIDTH,HEIGHT),IPL_DEPTH_8U,3);
if(this->name=="Background")
bgFrameBackup=cvCreateImage(cvSize(WIDTH,HEIGHT),IPL_DEPTH_8U,3);
cvZero(original);
//cvShowImage("w",original);
//cvWaitKey(0);
switch(original->nChannels) {
case 1:
format = GL_LUMINANCE;
break;
case 2:
format = GL_LUMINANCE_ALPHA;
break;
case 3:
format = GL_BGR;
break;
default:
return;
}
drawing=false;
setMouseTracking(true);
mouseX=0;mouseY=0;
startX=0; endX=0;
startY=0; endY=0;
dialog=new EntryExitRuleDialog();
makeCurrent();
GLenum result=glewInit();
if(result){
qDebug()<<(const char*)(glewGetErrorString(result));
}
//qDebug()<<"Open GL Version: "<<(const char*)glGetString(GL_VERSION);
bgColor=QColor::fromRgb(100,100,100);
initializeGL();
qglClearColor(bgColor);
glInfo glInfo;
glInfo.getInfo();
#ifdef _WIN32
// check PBO is supported by your video card
if(glInfo.isExtensionSupported("GL_ARB_pixel_buffer_object"))
{
// get pointers to GL functions
glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)wglGetProcAddress("glGenBuffersARB");
glBindBufferARB = (PFNGLBINDBUFFERARBPROC)wglGetProcAddress("glBindBufferARB");
glBufferDataARB = (PFNGLBUFFERDATAARBPROC)wglGetProcAddress("glBufferDataARB");
glBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC)wglGetProcAddress("glBufferSubDataARB");
glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)wglGetProcAddress("glDeleteBuffersARB");
glGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC)wglGetProcAddress("glGetBufferParameterivARB");
glMapBufferARB = (PFNGLMAPBUFFERARBPROC)wglGetProcAddress("glMapBufferARB");
glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)wglGetProcAddress("glUnmapBufferARB");
// check once again PBO extension
if(glGenBuffersARB && glBindBufferARB && glBufferDataARB && glBufferSubDataARB &&
glMapBufferARB && glUnmapBufferARB && glDeleteBuffersARB && glGetBufferParameterivARB)
{
pboSupported = true;
cout << "Video card supports GL_ARB_pixel_buffer_object." << endl;
}
else
{
pboSupported = false;
cout << "Video card does NOT support GL_ARB_pixel_buffer_object." << endl;
}
}
#else // for linux, do not need to get function pointers, it is up-to-date
if(glInfo.isExtensionSupported("GL_ARB_pixel_buffer_object"))
{
pboSupported = pboUsed = true;
cout << "Video card supports GL_ARB_pixel_buffer_object." << endl;
}
else
{
pboSupported = pboUsed = false;
cout << "Video card does NOT support GL_ARB_pixel_buffer_object." << endl;
}
#endif
if(pboSupported){
glGenBuffersARB(2, pboIds);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[0]);
glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, DATA_SIZE, 0, GL_STREAM_DRAW_ARB);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[1]);
glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, DATA_SIZE, 0, GL_STREAM_DRAW_ARB);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
//Note: pboMode=2 somehow does not work while calibration. Fix this later.
pboMode=1;
}
else{
pboMode=0;
}
paintFlag=false;
}
void QGLCanvas::setImage(IplImage image){
if(QString(this->name)=="Background"){
cvCopyImage(&image,bgFrameBackup);
}
//cvShowImage(name,&image);
// Display a rectangle between startX ,startY and endX,endY if we are in calibration mode
//and drawing flag is set.(typically, by a mouse click)
if(QString(this->name)=="Calibrate" && calibrating ){
if(drawing)
cvRectangle(&image,cvPoint(startX,startY),cvPoint(endX,endY),cvScalarAll(0xee));
if(select_object) //During calibration
cvRectangle(&image,cvPoint(selection.x,selection.y),cvPoint(selection.x+selection.width,selection.y+selection.height),cvScalarAll(0xee));
//Draw existing calibration rectangles
for (list<CvRect>::iterator it=calibration_rect_list->begin(); it!=calibration_rect_list->end(); ++it)
{
cvRectangle(&image, cvPoint((*it).x, (*it).y), cvPoint((*it).x + (*it).width, (*it).y + (*it).height), CV_RGB(100,255,0), 2, 8, 0);
}
}
//Only draw on the video widget with the name "Final"
if(QString(this->name)=="Final")
{
if(calibrating && drawing)
cvRectangle(&image,cvPoint(startX,startY),cvPoint(endX,endY),cvScalarAll(0xee));
//If we are adding a rule, the corresponding rule shape must be drawn on the widget.
if(addingRule && drawing){
if(currentShape==RULE_SHAPE_RECT){
cvRectangle(&image,cvPoint(startX,startY),cvPoint(endX,endY),cvScalarAll(0xee));
}
else if(currentShape==RULE_SHAPE_POLY){
int linecolor=0xee;
if(points.count()>0){
//Draw polygon...
for(int i=1;i<points.count();i++){
cvLine(&image,cvPoint(points[i-1]->x(),points[i-1]->y()),cvPoint(points[i]->x(),points[i]->y()),cvScalarAll(linecolor));
}
cvLine(&image,cvPoint(startX,startY),cvPoint(endX,endY),cvScalarAll(0xee));
cvLine(&image,cvPoint(endX,endY),cvPoint(points[0]->x(),points[0]->y()),cvScalarAll(linecolor));
}
}
else if(currentShape==RULE_SHAPE_TRIPLINE){
for(int i=1;i<points.count();i++){
cvLine(&image,cvPoint(points[i-1]->x(),points[i-1]->y()),cvPoint(points[i]->x(),points[i]->y()),cvScalarAll(0xee));
}
cvLine(&image,cvPoint(startX,startY),cvPoint(endX,endY),cvScalarAll(0xee));
}
}
if(entryExitRuleCreated && currentZoneType==RULE_ZONE_TYPE_ENTRY_EXIT ){
//Highlight appropriate sides of the currentRule to mark them as Entry/Exit Zone
for(int i=0;i<currentRule->points.count();i++){
QPoint* P1=currentRule->points[i];
QPoint* P2;
//Implement cyclic nature of polygon
if(i<currentRule->points.count()-1)
P2=currentRule->points[i+1];
else P2=currentRule->points[0];
int deltax=mouseX-P1->x();
int deltax1=P2->x()-P1->x();
float m,m1;
if(deltax!=0)
m= (float)(mouseY-P1->y())/deltax;
if(deltax1!=0 && deltax!=0){
m1=(float)(P2->y()-P1->y())/deltax1;
if(round(m,1)==round(m1,1))//Mouse pointer lies on the line whose slope is same as the polygon edge
{
//Mouse pointer is on the edge of a polygon, highlight the edge
if(abs(P1->y()-P2->y()) >= abs(mouseY-P2->y()) && abs(P1->y()-P2->y()) >= abs(mouseY-P1->y())
&& abs(P1->x()-P2->x()) >= abs(mouseX-P2->x()) && abs(P1->x()-P2->x()) >= abs(mouseX-P1->x())
){
edgeHighlighted=true;
highlightedEdge[0]=P1;
highlightedEdge[1]=P2;
currentEdgeNumber=i;
break;
}
}
else{
edgeHighlighted=false;
}
}
else{
//Vertical edge of a polygon.
if(abs(mouseX-P1->x())<4) { //Same vertical line
if(abs(P1->y()-P2->y()) > abs(mouseY-P2->y()) && abs(P1->y()-P2->y()) > abs(mouseY-P1->y())){
//Current y lies between the two vertices of an edge
//Mouse pointer is on the edge of polygon,highlight the edge
//qDebug()<<"P1="<<P1->x()<<","<<P1->y()<<", P2="<<P2->x()<<","<<P2->y();
edgeHighlighted=true;
highlightedEdge[0]=P1;
highlightedEdge[1]=P2;
currentEdgeNumber=i;
break;
}
else
edgeHighlighted=false;
}
}
}
if(edgeHighlighted || edgeHighlightedFromButton){
cvLine(&image,cvPoint(highlightedEdge[0]->x(),highlightedEdge[0]->y()),cvPoint(highlightedEdge[1]->x(),highlightedEdge[1]->y()),cvScalar(0xff,0x00,0x00),3);
}
}
}
{
//qDebug()<<name<<":Saving original image";
ExclusiveLock xlock(globalXMutex);
this->original=ℑ
paintFlag=true;
}
updateGL();
/*if(this->name=="Final"){
cvShowImage("Final",original);
cvWaitKey(1);
}*/
}
//Texture is generated here
void QGLCanvas::initializeGL(){
glDisable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glClearColor(0, 0, 0, 0); // background color
glClearStencil(0); // clear stencil buffer
glClearDepth(1.0f); // 0 is near, 1 is far
glDepthFunc(GL_LEQUAL);
glEnable(GL_TEXTURE_2D);
glGenTextures(1,&texture);
glBindTexture(GL_TEXTURE_2D,texture);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glBindTexture(GL_TEXTURE_2D,texture);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,WIDTH,HEIGHT,0,GL_BGR,GL_UNSIGNED_BYTE,NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glClearStencil(0); // clear stencil buffer
glClearDepth(1.0f); // 0 is near, 1 is far
glDepthFunc(GL_LEQUAL);
setAutoBufferSwap(false);
}
void QGLCanvas::resizeGL(int width,int height){
if (height==0) // Prevent A Divide By Zero By
{
height=1; // Making Height Equal One
}
glViewport(0,0,WIDTH,HEIGHT); // Reset The Current Viewport
glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
glLoadIdentity(); // Reset The Projection Matrix
glOrtho(0.0f,WIDTH,HEIGHT,0.0f,0.0f,1.0f);
glEnable(GL_TEXTURE_2D);
glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
glLoadIdentity(); // Reset The Modelview Matrix
}