• 73013阅读
  • 29回复

QT显示图片 [复制链接]

上一主题 下一主题
离线keisuo
 

只看楼主 正序阅读 楼主  发表于: 2007-04-26
— 本帖被 XChinux 执行加亮操作(2008-07-18) —
我修改vidcat 使其可以不停地采集图片放在snap.jpeg 中
我使用QT designer作了一个图片显示程序,当用不停刷新图片时
占用很大的内存\
////////////////////
不知道有谁做过
//////
下面是我从该网站收集的所有有用的帖子/
//////////////////
希望有人做过
//////////
qt 下显示内存里的图像数据
请教大家一个问题:
    我现在有一Buffer里存放了16bit 的图像数据,我想让它在QT程序里显示出来该怎么办呢?
在之前我用Qimages 类构造一个images图,但显示出来的图像颜色不对。实现的过程如下 :
Buffer:存放了图像数据
QImage images = new QImage(Buffer,Width,Height,16,0,1,QImage::IgnoreEndian);
QPainter p(this);
    p.begin(this);
    p.drawImage(0,0,*images);
    p.end();
请大家给点建议,有没有更好的方法去实现?
///////////////////
求教QT下显示视频的方法(急用啊~~)
各位大虾:
我现在想做QT下显示经过网络传输过来的BMP图片的界面,是否可以用QImage 这个类来实现呢?
如果可以的话应该怎样做呢?(我看过QImage 这个类好像很多东西啊,应该不用全部都用上吧!)
如果QImage 这个类不能实现的话,那应该怎样实现呢?希望得到各位的帮助,先谢谢了!!

用QLabel可查看图像,由QImage或者QPixmap创建

QPixmap img;
img.loadFromData();//在参数中加载图像数据
然后
QLabel::setPixmap(img);
/////////////////
我做了一个简单的尝试:
HWWidget::HWWidget(QWidget * parent, const char *name, WFlags f)
       :    QWidget( parent, name, f)
{
   left = 0;
   top = 160;
   width =240;
   height = 160;
   mPixmap = QPixmap("./bicycle.jpg");
   //setBackground(Noground);
   show();
}

void HWWidget::paintEvent(QPaintEvent * /*e*/)
{
   QPainter p( this);
   static int times = 1;
   static int yoffset = 8;
   {
       setGeometry( 0, 320-times*yoffset, 240,times*yoffset);
       p.drawPixmap(0, 0, mPixmap, 0, 0, -1, -1);   
       times++;
       usleep(500000);
   }
}

这样的效果能出来一点,就是图片bicycle.jpg能动画的划出来,存在三个问题:
  1.是个死循环,可能是因为setGeometry也会引发paint...
  2.只有bicycle.jpg图便能有动画效果,但是widget看不到他的默认的frame...
  3.可能运算太多,arm处理器不能很好的支持
所以,请问,QT有没有好一点的接口,支持整个widget也一起想抽屉一样的划出来,
类似与QQ的主界面,从隐藏状态显示出来的时候,是一个划出来的效果..可以从左到右,从上到下,甚至从下到上的划出来...???

  非常感谢您的帮助!!!!
///////////////////
图片显示出现的错误,帮忙分析一下阿
下面的程序只显示了一下,就没了。而且没显示图片,请打下帮忙分析一下,不胜感激!
//while(1)
{
buffer=v4lsyn(dev1,i);

repaint();

v4lcapture(dev1);

}

void imagesprocess::paintEvent( QPaintEvent * )
{
paint=new QPainter(this);
paint->begin(this);
label = new QLabel(this);
QImage *img=new QImage((unsigned char*)buffer,640,480,8,0,1,QImage::IgnoreEndian);
QPixmap tmp;
tmp=*img;
label->setPixmap(tmp);
fprintf (stderr, "readjusting width to %d\n", *buffer);
paint->end();

}
下面是调试时显示的错误:
QPainter::begin: Painter is already active.
You must end() the painter before a second begin()

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1084981632 (LWP 6669)]
0x0804d70b in imagesprocess::paintEvent(QPaintEvent*) (this=0xbfffe540)
at imagesprocess.cpp:243
243 fprintf (stderr, "readjusting width to %d\n", *buffer

如果把while 前的//去掉,就完全打不开界面,本人很是疑惑,这个问题也帮忙分一下,



label->setPixmap(tmp);
应该是paint->drawImage(5,5,img)
刚才发错了

paint =new QPainter(this);
定义是在paintEvent外了,
你将paint定义移到paintEvent函数内部
///////////////////////
请问关于QImage Class 的问题
各位大虾:
  我现在想做QT下显示经过网络传输过来的BMP图片的界面,是否可以用QImage 这个类来实现呢?
如果可以的话应该怎样做呢?(我看过QImage 这个类好像很多东西啊,应该不用全部都用上吧!)
如果QImage 这个类不能实现的话,那应该怎样实现呢?希望得到各位的帮助,先谢谢了!!

loadFromData()来加载图像,并通过QLabel来显示

/

///////////////////
关于摄像头捕捉图像2
我做的不知道是不是和obrire的一样,但我想摄像头数据截取不分应该是差不多的,
源代码我是不能说的,因为像obrire说的涉及到商业.
基本流程是这样的:先调用系统函数open(dev,O_RDWR);然后开辟共享内存adrr=mmap();adrr是内存首地址;
再调用ioctl (dev, VIDIOCMCAPTURE, &vid_mmap);ioctl (dev, VIDIOCSYNC, &vid_mmap.frame);得到图像;
然后再操作adrr就行了;
注意:没用每用完一次mmap()要记得把他销毁munmap(adrr, size);不然它会从新开辟另一块内存.直到系统内存
被用完.
显示的时候建议先把图像数据拷贝到自己开辟的内存里再对数据进行处理,显示\压缩什么的随你;
显示我的思想方法是,先创建一个Image;然后把数据放到Image里;最后painter出来;

skyly2222,你能不能把最后一步images(),这个参数,我设置不好..,显示效果烂的很..
我只需要几行代码,就是qt部分,其他部分,我已经做完了。


你能用的是
QImage ( uchar * yourdata, int w, int h, int depth, QRgb * colortable, int numColors, Endian bitOrder );
这个函数吧?
这个函数我试过,不行,因为colortable设置不好会倒置象素放大或缩小造成图像混乱;
我是这样解决的.
QRgb rgb;
QPainter *paint=new QPainter;
i=0;
if(bm.create(width,height,32,0,QImage::IgnoreEndian))
{        for (x = 0; x < height ; x++) {
           for (y = 0; y <width; y++) {
          r=(int)bit[i+2];
          g=(int)bit[i+1];
          b=(int)bit;
      uint *pp = (uint *)bm.scanLine(x) + y;
        *pp = qRgb(r,g,b);
          i=i+3;
                          }
                    }

}

我想请教skly22222,我按照上面步骤写了一个数据采集程序,在QT程序中把它作为 .c文件在工程编译的时候出了问题,原因好像是这部分关于linux下的头文件找不到,但把这个程序作为.h文件时就没有这种问题了,想知道这部分程序是否可以作为.h文件调用,另外我上面问题怎么解决。路过的各位大侠也请不吝赐教,谢谢!!

skly22222,多谢了,我在用图像处理作毕业设计,也是刚用QT,你的这些代码很有用,QT资料太少了,尤其在图像处理这一方面,有没有好的网站或资料介绍一下,让我们少走些弯路!

////////////////////
还是摄像头问题,能显示但没标题栏,无法控制它
部分源码用的如下
paint=new QPainter(this);
paint->begin(this);

v4l_open("/dev/video0",&device);
unsigned char *data = GrabData();

int x,y;
int i=0;
QImage img;
QRgb *point;
int r,g,b;

if(img.create(480,320,32,0,QImage::IgnoreEndian))
{
for(x=0;x<320;x++)
{
for(y=0;y<480;y++)
{
r=(int)data[i+2];
g=(int)data[i+1];
b=(int)data;
point = (QRgb *) img.scanLine(x)+y;
*point = qRgb(r,g,b);
i+=3;
}
}
}
paint->drawImage(10,10,img);
    我现在根据上面给出的摄像头的一段程序,自己把它写全,已能在板子上显示了,但是那个图像没含最大化、最小化、关闭按钮的标题栏,请问能把它画在label或是其他qt的类上吗?怎么画呢,我还想加一个按钮来关闭端口。谢谢!


创建个widget。把这段代码写在它的paintEvent里应该就可以了吧

楼主我想请教一下,我按照上面类似的做法,图像不能刷屏,只能显示最初采集的一帧,时什么原因。

因为paintEvent只有在widget接到paint事件的时候才会调用。 你不动它是不会调到paintEvent的
还是加个timer或者在需要刷新的时候调用repaint才行


////////////////
[备份]实现从RGBA32格式的内存数据中初始化一个QImage[lsosa.BIT]
很多人都为如何将内存中某一个格式的数据如何转换成为图片数据显示到屏幕上犯愁,因为这是实现处理视频的一个关键部分,我曾经也是,所以深知其不容易,如今,我这里问题已经解决,所以将解决方法备份与此,仅供参考。
这里提供处理两种数据格式的方法,一种是处理RGB24格式,另一种是RGBA32格式;
说明:
内存中数据保存在pv_frame指针指向的内存空间中;
图象宽度为v_width, the height of it is v_height;(高度是v_height);下面展示方法调用;
RGB24:
/**
* lsosa.BIT
*/

int i;
int r, g, b;
QRgb *point;
uchar *bit;
QImage *pVideoImage = new QImage(width, height, 32);
setWFlags(getWFlags() | Qt::WRepaintNoErase);
// these codes should be optimized in future;
// high cpu taked here;
i = 0;
bit = (uchar *)(_frame);
for(int y = 0; y < v_height; y ++){
   for ( int x = 0; x < v_width; x ++ ){
       r = (int)bit;
       g = (int)bit[i+1];
       b = (int)bit[i+2];
       point = (QRgb *)pVideoImage->scanLine(y) + x;
       *point = qRgb(r, g, b);
       i += 3;
   }
}
bitBlt(this, 0, 0, pVideoImage);

the details is here:
http://www.qtcn.org/bbs/read.php?tid=3648&page=e&fpage=5
http://www.qtcn.org/bbs/read.php?tid=4990&page=e&fpage=1

RGBA32:
/**
* lsosa.BIT
*/
QImage *pv_images;
if ( pv_images == NULL ){
   QImage pv_images = new QImage((uchar *)pv_frame, \
       v_width, v_height, 32, NULL, 0, QImage::LittleEndian);
   if ( pv_images == NULL ){
       return -1;
   }
}

all is this...

这里在补充一种方法,你可以使用QImage 的loadFromData方法从一段内存数据中加载图像,例如:
QImage *pf_video_images = new QImage();
pf_video_images->loadFromData(pf_videobuffer, frame_size);
如此应该也可以的;
代码是凭感觉写出来的,为经过测试哦。。。

//////////////////////////

关于摄像头视频捕捉
各位大大:
    小弟做了一个摄相头视频捕捉程序,想连续从摄相头来的数据..现在的思路是将摄相头的数据用timer定时读到自己开辟的内存空间中,然后通过images.loadfromdata读出来,然后设置为label背景,label->setPixmap(),但是怎么也显示不出来,我想问问是不是这个思路,和具体的应该用什么控件,和api,请高手指点.....急啊..

共享内存会更快些immap

真是的,LCD驱动有三层,Overlay 1/Overlay 2和FameBuffer
为什么一定要用QT, 就相当于DShow一样,在不同层显示不同应用.还可以支持透明.
我们可以将QT的应用放在FameBuffer(Base Layer)上,而VideoCap的实时内容放
在Overlay2上,这叫回放.而在VideoCap上显示的文字,图标放在Overlay1上呀.
以上三层,包括通道透明处理和灰度处理,都测试通过.
以上便用是商业开发的真正实现.望对诸位有益.

不仅仅是LCD,所有的FameBuffer驱动,都应当支持这三层.
其实Windows也是这样实现的.

VideoCAP就是V4L提供的Linux下的用于电视(数字/模拟)的接收,存储,发布和PVR/DVR应用内核支持组件.
一般来讲,CCD/CMOS的摄像头也是使用此类接口.电视卡一般采用模拟接入,也可以像Windows下进行数据
采集的.

如果你对系统不了解,只知道库,控件什么的,这比较麻烦.

这需要对整个流程非常熟,以及对RGB/YUV的转换非常了解,才能完成实时视频流捕获/压缩和后处理.

很失望的是,这方面没有现成的控件.如果有,也只能使用驱动直接提供的接口.

至于上面有朋友提到什么timer/mmap,这只是在ioctl时,采用的一种技术.一般采用mmap,但这只能
将数据读出来,但你要知道读出来的是什么颜色,什么格式的数据呀?YUV 4:4:2/RGB 8:8:8?还是其它格式?
各种制式还要适合你的显示设备的制式,你的VGA支持哪种格式?一般是YUV 4:2:2.如果你的LCD/VGA等不
持,显示不出来.如果支持相同格式,按byte读出buffer,直接写入或cat到framebuffer上就可以显示出来了.
至于实时的preview,就是Overlay所支持的回放.打开相应的Overlay层,一个while循环就可了了.

以上便是智能设备视频开发流程的全部.

尽管我有全部的代码,但有可能涉及到商业代码,恕不能提供更多技术支持.

所能支持层:目前有一个家公司提供的产品中,能支持mjpeg/3gp播放,但不能实时codec,而以前我们所工作的
平台,能完全实时3gp实时压缩/RTP发布,支持DivX/Xvid.能让mplayer工作于arm平台,很流畅的进行播放.

首先,能对你提供帮助的是,这与QT没有任何关系.

QT只工作于fb0所支持的Base层.这是三层概念.
Base Layer fb0-->就是你的Qtopia可以显示的平面
Overlay 2     -->就是你要看到效果的平面,用于VideoCapture来的数据流实时显示
Overlay 1     -->就是你的数码相机/手机/PDA上,拍摄时,上面显示的按键/调节提示/文字/时间等
至于QTE,它只负责将GUI数据显示于第一层的内存空间
而Overlay 2是你的显示模块能支持最大的全屏图像数据大小的内存空间
Overlay 1是另一片内存区,比Overlay 2要小,主要用于存储最上层的一此提示数据/图像显示

这三层的实时,与嵌入式与否无关.
如还不清楚,建议你看看Intel中IPP关于图像/视频这部分的内容.
这比较麻烦,我只能帮你到这儿.之所以不清楚,是你对这个流程并不清楚.
就是你将数据读出来了,写到你的QT Label内存区间,是不一定显示正常的.这此数据只是颜色数据
这显相于直接写屏呀.不会引起显示的混乱??


/////////////////
加载图片的问题
////////////////////////////////////
renderarea.h
///////////////////////////////////
#include <QWidget.h>
#include <QPainter.h>
#include <QPen.h>
#include <QPixmap.h>
class renderarea:public QWidget
{
public:
    renderarea(QWidget *parent=0);
    void paintEvent(QPaintEvent *event);
private:
    QPen pen;
    QPixmap pixmap;
};
////////////////////////////////
renderarea.cpp
///////////////////////////////
#include "renderarea.h"
renderarea::renderarea(QWidget *parent):QWidget(parent)
{
 
}
void renderarea::paintEvent(QPaintEvent *)
{
  QPainter painter(this);
  painter.setPen(pen);

  painter.drawLine(10,10,60,70);
  pixmap.load(":/imagess/dog.png");
  painter.drawPixmap(0,0,pixmap);
 
}
////////////////////////////////
main.cpp
///////////////////////////////
#include <QApplication.h>
#include "renderarea.h"
int main(int argc,char *argv[])
{
  QApplication qapp(argc,argv);
  renderarea background;
  background.show();
  return qapp.exec();
}
//////////////////////////////////
做一个小程序需要加载图片,所以就弄了这个小东西试验一下,编译都通过了
画矩形,画直线都没有问题,但是图片画不出来,不知道原因是什么,看了几个例子都是这么
弄的,不明白为什么到我这就不行了?欢迎大家提出宝贵意见

由于版权原因, Qt对GIF格式支持并不好.

改换个jpg格式的就行了....

经过我逐行检查,发现是图片并没有load成功,希望大家给我分析一下原因

pixmap.load(":/imagess/dog.png");

//路径问题.你试试用绝对路径

问题解决了,我的是qt4 windows版的,后来我把图片写到qrc文件中,就ok了,谢谢你的帮忙


////////////////
一个图片显示的小程序求教
想写一个简单的在qt下显示图片的程序
可是在编译时老出现这个错误:error: multiple types in one declaration 一个声明指定了多个类型
比如在生成images.o的时候 /usr/lib/qt-3.3/include/qpen.h:88: 错误:一个声明指定了多个类型
在编译main.cpp的时候也有这种情况
/usr/lib/qt-3.3/include/qdesktopwidget.h:43: error: multiple types in one declaration
不知道是自己程序哪个地方出毛病了,反正把images.cpp置空就没这个错误了
多谢

程序如下:
imag.h

Copy code
#ifndef IMAGE_H
#define IMAGE_H

#include <qwidget.h>
#include <qimages.h>

class MyImage : public QWidget
{

public:
    MyImage( QWidget *parent=0, const char *name=0, int wFlags=0 );
    ~MyImage();
    bool loadImage( const QString& );
protected:
    void paintEvent( QPaintEvent * );
   
private:
    QString filename;
    QImage images;

}


#endif
images.cpp

Copy code
#include "images.h"

#include <qpainter.h>
#include <qapplication.h>
#include <qmessagebox.h>

MyImage::MyImage( QWidget *parent, const char *name, int wFlags ):QWidget( parent, name, wFlags )
{
   bool ok;
   filename = "/opt/program/qt/showimg/save";
   ok = loadImage( filename );
   printf( "ok = %d \n", ok );

}

MyImage::~MyImage()
{
}

bool MyImage::loadImage( const QString& filename )
{
  
   bool ok;
   ok = images.load( filename, 0 );
   update();
   return ok;

}

void MyImage::paintEvent( QPaintEvent *e )
{

   QPainter painter(this);
   painter.setClipRect(e->rect());
   painter.drawImage( images.width(), images.height(), images );

}


main.cpp

Copy code
#include "images.h"

#include <qapplication.h>
#include <qimages.h>

int main( int argc, char **argv )
{
   QApplication a( argc, argv );
   MyImage myimages;
   a.setMainWidget( &myimages );
   myimages.setCaption("images Example");
   myimages.show();
   return a.exec();
}   


大哥。你是我的偶像.

class X
{
    //....
}; 这里的';'可不能掉啊,加上就OK了

QT examples showing

这是一个图片显示的例子



QT 2.3.7 QTabel 加载图片的问题 请支招!!!
QT 2.3.7 form1.cpp中,
 
  QPixmap pixmap1( "/root/pic/tabel.png");
  table1 = new QTable( this, "table1" );
  table1->setGeometry( QRect( 135, 38, 181, 127 ) );
  table1->setBackgroundPixmap(pixmap1);  
在form1.h中,table1有申明.
问题一: 为什么设置后,显示不出来图片pixmap1,也就是setBackgroundPixmap好像不起作用.
问题二: 在QT3.1.7下,tabel有setReadOnly属性设置单元格是否只读,可是在QT2.3.7下,找不到只读属性.
如何才能显示图片以及设置单元格的只读属性呐?
急盼支招! 谢谢!

///////////////////
QPixmap::loadFromData 里面的 QByteArray & data 是个什么东西呢
bool QPixmap::loadFromData ( const QByteArray & data, const char * format = 0, Qt::ImageConversionFlags flags = Qt::AutoColor )

我在Qt程序里读入了一个Qt不支持的图片,pics格式。现在已经可以用程序读入内存。用QImage的setPixel 的时候,读入图片的速度比较慢。所以在寻求其他的方法。

看到前面大家讨论说可以直接读buffer, 我想这个方法可能可以吧。

所以想问问这个QByteArray & data是什么呢,该怎么用阿。
/////////////////////////////
请问一下,我在linux下用QT3写的一个界面的显示,出现闪屏的问题
我在linux下用QT3写的一个界面的显示,在LCD上显示时出现严重的闪屏现象,因为数据和图形要实时更新,现在想不出什么好的办法来解决,因为我也是初次接触QT。有同学跟我说用什么双缓冲可能可以,但是我还不太明白要用到哪些函数,,请高手指点一下,或可以提供些相关的资料,谢谢了

双缓冲并不需要什么特别的函数,原理就是把图画好后再放在屏幕上显示.
void QProgressBar::drawContents( QPainter *p )
{
  const QRect bar = contentsRect();

  QPixmap pm( bar.size() );
  QPainter paint( &pm );
  const QPixmap * bgPixmap = backgroundPixmap();
  QBrush fbrush = ( bgPixmap ? QBrush( backgroundColor(), *bgPixmap ) :
            QBrush( backgroundColor() ) );
  if ( bgPixmap && !isTopLevel() && (backgroundOrigin() != WidgetOrigin) )
   paint.setBrushOrigin( -x(), -y() );
  paint.fillRect( bar, fbrush );
  paint.setFont( p->font() );

  QStyle::SFlags flags = QStyle::Style_Default;
  if (isEnabled())
   flags |= QStyle::Style_Enabled;
  if (hasFocus())
   flags |= QStyle::Style_HasFocus;

  style().drawControl(QStyle::CE_ProgressBarGroove, &paint, this,
           QStyle::visualRect(style().subRect(QStyle::SR_ProgressBarGroove, this), this ),
           colorGroup(), flags);

  style().drawControl(QStyle::CE_ProgressBarContents, &paint, this,
           QStyle::visualRect(style().subRect(QStyle::SR_ProgressBarContents, this), this ),
           colorGroup(), flags);

  if (percentageVisible())
   style().drawControl(QStyle::CE_ProgressBarLabel, &paint, this,
              QStyle::visualRect(style().subRect(QStyle::SR_ProgressBarLabel, this), this ),
              colorGroup(), flags);
  paint.end();

  p->drawPixmap( bar.x(), bar.y(), pm );
}
////////////////////
【提问】实时视频采集在多线程编程中实现的问题?
我使用QT designer作了一个程序,程序要求可以视频实时采集,我用一个窗口来显示摄像头捕捉的视频,我可以使用QThread类来实现这个视频采集,可是视频显示的程序是一个无限循环,这样Qt 的GUI 主事件线程就会一直在等待视频采集的结束
,这样我的主界面mainform就不能运行了,主界面上的各个控件就会无响应。
如何才能让这个视频采集程序在后台运行,并在主程序里控制视频采集程序的开始和暂停?
请各位大侠帮忙!!!!


你看看这个程序看能否有帮助 ,我以前好像也遇到过这样的情况 ,不过比你的简单
////////////////////////////////////////////////////////
#include <qapplication.h>
#include <qhbox.h>
#include <qthread.h>
#include <qlabel.h>


class WaitThread: public QThread
{
public:
  WaitThread(QThread *thr1, QThread *thr2, QLabel *lbl);
  void run();
private:
  QThread *thread1, *thread2;
  QLabel *label;
};


WaitThread::WaitThread(QThread *thr1, QThread *thr2, QLabel *lbl)
  : thread1(thr1), thread2(thr2), label(lbl)
{
}


void WaitThread::run()
{
  thread1->wait();
  thread2->wait();

  qApp->lock();
  label->setText("<h1>Done!</h1>");
  qApp->unlock();
}

class GUIThread : public QThread
{
public:
  GUIThread( QLabel*, const QString& );

protected:
  void run();

private:
  QLabel* label;
  QString text;
};

static QMutex* mutex;

GUIThread::GUIThread( QLabel* l, const QString& t )
: label( l ), text( t )
{
}

void GUIThread::run()
{
  for ( int i = 0; i < 2; i++ ) {
     mutex->lock();
     qApp->lock();
     label->setText(text);
     qApp->unlock();
     sleep( 1 );
     mutex->unlock();
  }
}

// The program starts here

int main( int argc, char** argv )
{
  QApplication app( argc, argv );
  mutex = new QMutex;

  QHBox box( 0, 0, TRUE );
  QLabel label( &box );
  label.setAlignment(Qt::AlignCenter);
  label.setMinimumSize(400, 300);
 
  GUIThread first( &label, "<b>Ping</b>" );
  GUIThread second( &label, "<i>Pong</i>" );
 
  WaitThread third(&first, &second, &label);
  first.start();
  second.start();
 
  app.setMainWidget( &box );
  box.show();

 
  third.start();
 
  int r = app.exec();

  delete mutex;
  return r;
}

我前一段时间做的是一个线程不断的采集数据,然后在一个窗口显示.没有用你说的QWaitCondition::wait .
程序大概如下:
class Mythread::wait : public QThread {

  Mythread(Qlabel *label);
  public:

    virtual void run();

  };

MyThread::MyThread(QLabel* l ): label( l)
{
}
  void MyThread::run()
  {
    for( ; ; ) {
      sleep( 1 );
        label->setText("ping");
                qDebug( "Ping!" );
    }
  }

再参照上面的程序大概就行了

/////////////

【提问】在qt界面设计中怎么显示图像?
我想知道QT中的IconView怎么用?还有要想选择不同的图像让它在界面上显示,应该怎么办呢? 大家给我个思路吧,我的脑子很不明白,下一步怎么走?
谢谢啦:)

在Qt中有QLable类,可以用
void QLabel::setPixmap ( const QPixmap & )
就可以显示图像了

QPixmap *pixmap = new QPixmap(filePath);
QLabel *label;

label->setPixmap(*pixmap);

//////////////////////
【提问】关于在label上显示图片的问题!!【已解决】
我要设计一个能显示图片的界面,还要改变大小、对比度等,但是我在设置button和label的布局之后就不会显示图片了,example里好像没有将图片显示在label上,编译没有问题,但是总是显示
QObject::connect: No such slot Mywindow::loadImage()
QObject::connect: (sender name:   'unnamed')
QObject::connect: (receiver name: 'unnamed')

我用的是QT3.1,loadImage()是仿着一个例子写的,也不知道怎么改。
load有些图片时又显示“浮点数例外”,这个又是什么问题?难道是图片格式不对?


问题已解决了,convertFromImage()实现了,还是谢谢大家了!

我现在也在做显示图片并进行处理的工作,摆放好部件后,在ui.h中编函数时,总是有错误,您能指点一下吗?谢谢

我回答行不行?(每次我好像都是不请自到的)
ui_*.h开头的都是自动生成文件,你要继承这个类,然后在你的新类中使用。

可以参考Qt的示例:C:\Qt\4.1.4\examples\widgets\imagesviewer

也可以参考我写的一个小程序
http://www.qtcn.org/bbs/read.php?tid=6546


///////////////
下面是我的程序:
while (1)
{
buffer=v4lsyn(dev1,i);

fprintf (stderr, "readjusting width to %d\n", *buffer);
img=new QImage((unsigned char*)buffer,640,480,8,0,0,QImage::IgnoreEndian);
v4lcapture(dev1);

connect( timer, SIGNAL(timeout()), SLOT(repaint( TRUE )) );
timer->start( 100 );

}

}
void imagesprocess::paintEvent( QPaintEvent * )
{

paint->begin(this);
paint->drawImage(0,0,*img);
paint->end();

if( paint->isActive() ){
paint->end();
}

}
我的buffer中有连续的值,但是就是显示不出来,不知道为什么,大虾,帮忙分析一下
[ 此贴被XChinux在2008-07-18 16:40重新编辑 ]
离线tonkv

只看该作者 29楼 发表于: 2013-12-17
先留着
离线jay8830095
只看该作者 28楼 发表于: 2011-04-12
受教了
在不断的变化
离线梅克斯
只看该作者 27楼 发表于: 2011-03-28
字好多....支持了
离线wjlsmail

只看该作者 26楼 发表于: 2011-03-28
有些文章还是挺好的。
离线wjlsmail

只看该作者 25楼 发表于: 2011-03-28
文集啊
离线浪迹天涯
只看该作者 24楼 发表于: 2011-02-25
mark, learning
离线hy49
只看该作者 23楼 发表于: 2011-02-12
我感觉好像是弄不出来,不知道是什么情况,图片在什么地方放着呢?
离线mikedance

只看该作者 22楼 发表于: 2010-12-16
mark!
离线wywwh
只看该作者 21楼 发表于: 2009-12-03
标记之
离线cszhoujin
只看该作者 20楼 发表于: 2009-09-17
兄弟,你那QT显示图片的问题解决了没有啊?小弟现在也遇到了同样的问题,图片显示的速度太慢了!我采集的JPEG图片放在了一个数组里面,解码的时候用的事loadFromDate,速度太慢了!小弟想向大哥你请教一下,该怎么解决啊!十分感谢!
离线keisuo

只看该作者 19楼 发表于: 2007-05-28
xlinux 不知道整理了内存显示图片的信息了么
离线keisuo

只看该作者 18楼 发表于: 2007-05-27
利用内存压缩可以是刷新屏幕是更快更好
离线keisuo

只看该作者 17楼 发表于: 2007-05-11
            双缓冲技术(Double Buffering)(1、简介和源代码部分)
这一节实在是有些长,翻译完后统计了一下,快到2w字了。考虑到阅读的方便和网络的速度,打算把这节分为5个部分,第一部分为双缓冲技术的一个简介和所有的代码,如果能够看懂代码,不用看译文也就可以了。第二部分为Plotter控件的公有函数的实现,第三部分为Plotter的事件处理函数的实现,第四部分为Plotter控件的私有函数实现,第五部分为辅助类PlotSettings的实现。
这里给出一些常用的中英文对照(不一定准确,我这样用的):
Rubber band(橡皮筋线,或者橡皮线), pixmap(图像,双缓冲中用到的图像,有时也直呼pixmap),off-screen pixmap(离线图像)
Plot(plot,这一节实现的就是一个绘制曲线的控件Plotter,有时原文也叫plot,有点小名的意思,没有翻译,直接呼之)
废话少说,以下是译文:
 
双缓冲技术是GUI编程中常用的技术。所谓的双缓冲就是把需要绘制的控件保存到一个图像中,然后在把图像拷贝到需要绘制的控件上。在Qt的早期版本中,为了用户界面更加清爽,经常用这个技术来消除闪烁。
在Qt4中,QWidget能够自动处理闪烁,因此我们不用再担心这个问题。尽管如此,如果控件绘制复杂且需要经常刷新,双缓冲技术还是很有用的。我们可以把控件永久保存在一个图像中,随时准备下一次绘制事件的到来,一旦接到一个控件的绘制事件,就把图片拷贝到控件上。如果我们要做的只是小范围的修改,这个技术更是尤为有用,如要绘制一条橡皮筋线,就不必刷新整个控件了。
在本章的最后一节,我们实现的是一个叫做Plotter的自定义控件。这个控件使用了双缓冲技术,也涉及到了Qt编程的其他方面:如键盘的事件处理,布局和坐标系统。
Plotter控件用来显示一条或者多条曲线,这些曲线由一组向量坐标表示。用户可以在显示的曲线上画一个橡皮筋线,Plotter控件对橡皮筋线包围的区域进行放大。用户用鼠标左键在控件上选择一个点,然后拖动鼠标走到另一点,然后释放鼠标,就在控件上绘制一条橡皮筋线。
Figure 5.7 Zooming in on the Plotter Widget

 
用户可以多次用橡皮筋线进行放大,也可以用ZoomOut按钮缩小,然后用ZoomIn按钮再放大。ZoomOut和ZoomIn按钮只是在控件第一次放大或者缩小操作后变得可见,如果用户不缩放图形,则这两个按钮会一直不可见,这样可以使绘图区域不那么混乱。
Plotter控件可以存储任何数量的曲线的数据。同时它还维护一个PlotSettings对象的堆栈区域,每一个PlotSettings对象都是对应一个特定的放缩值。
首先看一下头文件的代码(对头文件的解析在代码中用注释的形式给出):
#ifndef PLOTTER_H
#define PLOTTER_H
#include <QMap>//包含的Qt的头文件
#include <QPixmap>
#include <QVector>
#include <QWidget>
class QToolButton; //两个前向声明
class PlotSettings; 
class Plotter : public QWidget
{
    Q_OBJECT
public:
    Plotter(QWidget *parent = 0);
    void setPlotSettings(const PlotSettings &settings);
    void setCurveData(int id, const QVector<QPointF> &data);
    void clearCurve(int id);
    QSize minimumSizeHint() const; //重写QWidget::minimumSizeHint()
    QSize sizeHint() const;        //重写QWidget::sizeHint()
public slots:
    void zoomIn();  //放大曲线
void zoomOut();  //缩小显示曲线
protected:  //重写的事件处理函数
void paintEvent(QPaintEvent *event);
void resizeEvent(QResizeEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void wheelEvent(QWheelEvent *event);
private:
    void updateRubberBandRegion();
    void refreshPixmap();
    void drawGrid(QPainter *painter);
    void drawCurves(QPainter *painter);
    enum { Margin = 50 };
    QToolButton *zoomInButton;
    QToolButton *zoomOutButton;
    QMap<int, QVector<QPointF> > curveMap;  //曲线数据
    QVector<PlotSettings> zoomStack;  //PlotSettings堆栈区域
    int curZoom;
    bool rubberBandIsShown;
    QRect rubberBandRect;
    QPixmap pixmap; //显示在屏幕的控件的一个拷贝,任何绘制总是先在pixmap进行,然//后拷贝到控件上
};
//PlotSettings确定x,y轴的范围,和刻度的个数
class PlotSettings
{
public:
    PlotSettings();

    void scroll(int dx, int dy);
    void adjust();
    double spanX() const { return maxX - minX; }
    double spanY() const { return maxY - minY; }

    double minX;
    double maxX;
    int numXTicks;
    double minY;
    double maxY;
    int numYTicks;

private:
    static void adjustAxis(double &min, double &max, int &numTicks);
};
#endif
 
图5-8表示了Plotter控件和PlotSettings的关系。
通常,numXTicks和numYTicks是有一个的误差,如果numXTicks为5,实际上Plotter会在x轴上绘制6个刻度。这样可以简化以后的计算(至于怎么样简化的,就看程序和后文吧吧)。
Figure 5-8 PlotSettings's member variables

现在来看源文件(代码有些长,先用代码格式给出完整源文件代码):
#include <QtGui>
#include <cmath>

#include "plotter.h"

Plotter::Plotter(QWidget *parent)
    : QWidget(parent)
{
    setBackgroundRole(QPalette::Dark);
    setAutoFillBackground(true);
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    setFocusPolicy(Qt::StrongFocus);
    rubberBandIsShown = false;

    zoomInButton = new QToolButton(this);
    zoomInButton->setIcon(QIcon(":/images/zoomin.png"));
    zoomInButton->adjustSize();
    connect(zoomInButton, SIGNAL(clicked()), this, SLOT(zoomIn()));

    zoomOutButton = new QToolButton(this);
    zoomOutButton->setIcon(QIcon(":/images/zoomout.png"));
    zoomOutButton->adjustSize();
    connect(zoomOutButton, SIGNAL(clicked()), this, SLOT(zoomOut()));

    setPlotSettings(PlotSettings());
}

void Plotter::setPlotSettings(const PlotSettings &settings)
{
    zoomStack.clear();
    zoomStack.append(settings);
    curZoom = 0;
    zoomInButton->hide();
    zoomOutButton->hide();
    refreshPixmap();
}

void Plotter::zoomOut()
{
    if (curZoom > 0) {
        --curZoom;
        zoomOutButton->setEnabled(curZoom > 0);
        zoomInButton->setEnabled(true);
        zoomInButton->show();
        refreshPixmap();
    }
}

void Plotter::zoomIn()
{
    if (curZoom < zoomStack.count() - 1) {
        ++curZoom;
        zoomInButton->setEnabled(curZoom < zoomStack.count() - 1);
        zoomOutButton->setEnabled(true);
        zoomOutButton->show();
        refreshPixmap();
    }
}

void Plotter::setCurveData(int id, const QVector<QPointF> &data)
{
    curveMap[id] = data;
    refreshPixmap();
}

void Plotter::clearCurve(int id)
{
    curveMap.remove(id);
    refreshPixmap();
}

QSize Plotter::minimumSizeHint() const
{
    return QSize(6 * Margin, 4 * Margin);
}

QSize Plotter::sizeHint() const
{
    return QSize(12 * Margin, 8 * Margin);
}

void Plotter::paintEvent(QPaintEvent * /* event */)
{
    QStylePainter painter(this);
    painter.drawPixmap(0, 0, pixmap);
    if (rubberBandIsShown) {
        painter.setPen(palette().light().color());
        painter.drawRect(rubberBandRect.normalized()
                                      .adjusted(0, 0, -1, -1));
    }
    if (hasFocus()) {
        QStyleOptionFocusRect option;
        option.initFrom(this);
        option.backgroundColor = palette().dark().color();
        painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
    }
}

void Plotter::resizeEvent(QResizeEvent * /* event */)
{
    int x = width() - (zoomInButton->width()
                      + zoomOutButton->width() + 10);
    zoomInButton->move(x, 5);
    zoomOutButton->move(x + zoomInButton->width() + 5, 5);
    refreshPixmap();
}
void Plotter::resizeEvent(QResizeEvent * /* event */)
{
    int x = width() - (zoomInButton->width()
                      + zoomOutButton->width() + 10);
    zoomInButton->move(x, 5);
    zoomOutButton->move(x + zoomInButton->width() + 5, 5);
    refreshPixmap();
}
void Plotter::resizeEvent(QResizeEvent * /* event */)
{
    int x = width() - (zoomInButton->width()
                      + zoomOutButton->width() + 10);
    zoomInButton->move(x, 5);
    zoomOutButton->move(x + zoomInButton->width() + 5, 5);
    refreshPixmap();
}

void Plotter::mousePressEvent(QMouseEvent *event)
{
    QRect rect(Margin, Margin,
              width() - 2 * Margin, height() - 2 * Margin);
    if (event->button() == Qt::LeftButton) {
        if (rect.contains(event->pos())) {
            rubberBandIsShown = true;
            rubberBandRect.setTopLeft(event->pos());
            rubberBandRect.setBottomRight(event->pos());
            updateRubberBandRegion();
            setCursor(Qt::CrossCursor);
        }
    }
}
void Plotter::mouseMoveEvent(QMouseEvent *event)
{
    if (rubberBandIsShown) {
        updateRubberBandRegion();
        rubberBandRect.setBottomRight(event->pos());
        updateRubberBandRegion();
    }
}
void Plotter::mouseReleaseEvent(QMouseEvent *event)
{
    if ((event->button() == Qt::LeftButton) && rubberBandIsShown) {
        rubberBandIsShown = false;
        updateRubberBandRegion();
        unsetCursor();
        QRect rect = rubberBandRect.normalized();
        if (rect.width() < 4 || rect.height() < 4)
            return;
        rect.translate(-Margin, -Margin);
        PlotSettings prevSettings = zoomStack[curZoom];
        PlotSettings settings;
        double dx = prevSettings.spanX() / (width() - 2 * Margin);
        double dy = prevSettings.spanY() / (height() - 2 * Margin);
        settings.minX = prevSettings.minX + dx * rect.left();
        settings.maxX = prevSettings.minX + dx * rect.right();
        settings.minY = prevSettings.maxY - dy * rect.bottom();
        settings.maxY = prevSettings.maxY - dy * rect.top();
        settings.adjust();
        zoomStack.resize(curZoom + 1);
        zoomStack.append(settings);
        zoomIn();
    }
}

void Plotter::keyPressEvent(QKeyEvent *event)
{
    switch (event->key()) {
    case Qt::Key_Plus:
        zoomIn();
        break;
    case Qt::Key_Minus:
        zoomOut();
        break;
    case Qt::Key_Left:
        zoomStack[curZoom].scroll(-1, 0);
        refreshPixmap();
        break;
    case Qt::Key_Right:
        zoomStack[curZoom].scroll(+1, 0);
        refreshPixmap();
        break;
    case Qt::Key_Down:
        zoomStack[curZoom].scroll(0, -1);
        refreshPixmap();
        break;
    case Qt::Key_Up:
        zoomStack[curZoom].scroll(0, +1);
        refreshPixmap();
        break;
    default:
        QWidget::keyPressEvent(event);
    }
}

void Plotter::wheelEvent(QWheelEvent *event)
{
    int numDegrees = event->delta() / 8;
    int numTicks = numDegrees / 15;
    if (event->orientation() == Qt::Horizontal) {
        zoomStack[curZoom].scroll(numTicks, 0);
    } else {
        zoomStack[curZoom].scroll(0, numTicks);
    }
    refreshPixmap();
}
void Plotter::updateRubberBandRegion()
{
    QRect rect = rubberBandRect.normalized();
    update(rect.left(), rect.top(), rect.width(), 1);
    update(rect.left(), rect.top(), 1, rect.height());
    update(rect.left(), rect.bottom(), rect.width(), 1);
    update(rect.right(), rect.top(), 1, rect.height());
}
void Plotter::refreshPixmap()
{
    pixmap = QPixmap(size());
    pixmap.fill(this, 0, 0);
    QPainter painter(&pixmap);
    painter.initFrom(this);
    drawGrid(&painter);
    drawCurves(&painter);
    update();
}

void Plotter::drawGrid(QPainter *painter)
{
    QRect rect(Margin, Margin,
              width() - 2 * Margin, height() - 2 * Margin);
    if (!rect.isValid())
        return;
    PlotSettings settings = zoomStack[curZoom];
    QPen quiteDark = palette().dark().color().light();
    QPen light = palette().light().color();
    for (int i = 0; i <= settings.numXTicks; ++i) {
        int x = rect.left() + (i * (rect.width() - 1)
                                / settings.numXTicks);
        double label = settings.minX + (i * settings.spanX()
                                          / settings.numXTicks);
        painter->setPen(quiteDark);
        painter->drawLine(x, rect.top(), x, rect.bottom());
        painter->setPen(light);
        painter->drawLine(x, rect.bottom(), x, rect.bottom() + 5);
        painter->drawText(x - 50, rect.bottom() + 5, 100, 15,
                          Qt::AlignHCenter | Qt::AlignTop,
                          QString::number(label));
    }
    for (int j = 0; j <= settings.numYTicks; ++j) {
        int y = rect.bottom() - (j * (rect.height() - 1)
                                  / settings.numYTicks);
        double label = settings.minY + (j * settings.spanY()
                                          / settings.numYTicks);
        painter->setPen(quiteDark);
        painter->drawLine(rect.left(), y, rect.right(), y);
        painter->setPen(light);
        painter->drawLine(rect.left() - 5, y, rect.left(), y);
        painter->drawText(rect.left() - Margin, y - 10, Margin - 5, 20,
                          Qt::AlignRight | Qt::AlignVCenter,
                          QString::number(label));
    }
    painter->drawRect(rect.adjusted(0, 0, -1, -1));
}

void Plotter::drawCurves(QPainter *painter)
{
    static const QColor colorForIds[6] = {
        Qt::red, Qt::green, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow
    };
    PlotSettings settings = zoomStack[curZoom];
    QRect rect(Margin, Margin,
              width() - 2 * Margin, height() - 2 * Margin);
    if (!rect.isValid())
        return;
    painter->setClipRect(rect.adjusted(+1, +1, -1, -1));
    QMapIterator<int, QVector<QPointF> > i(curveMap);
    while (i.hasNext()) {
        i.next();
        int id = i.key();
        const QVector<QPointF> &data = i.value();
        QPolygonF polyline(data.count());
        for (int j = 0; j < data.count(); ++j) {
            double dx = data[j].x() - settings.minX;
            double dy = data[j].y() - settings.minY;
            double x = rect.left() + (dx * (rect.width() - 1)
                                        / settings.spanX());
            double y = rect.bottom() - (dy * (rect.height() - 1)
                                          / settings.spanY());
            polyline[j] = QPointF(x, y);
        }
        painter->setPen(colorForIds[uint(id) % 6]);
        painter->drawPolyline(polyline);
    }
}

////////////////////////////////////////////////////////////
PlotSettings::PlotSettings()
{
    minX = 0.0;
    maxX = 10.0;
    numXTicks = 5;
    minY = 0.0;
    maxY = 10.0;
    numYTicks = 5;
}

void PlotSettings::scroll(int dx, int dy)
{
    double stepX = spanX() / numXTicks;
    minX += dx * stepX;
    maxX += dx * stepX;
    double stepY = spanY() / numYTicks;
    minY += dy * stepY;
    maxY += dy * stepY;
}

void PlotSettings::adjust()
{
    adjustAxis(minX, maxX, numXTicks);
    adjustAxis(minY, maxY, numYTicks);
}

void PlotSettings::adjustAxis(double &min, double &max,
                              int &numTicks)
{
    const int MinTicks = 4;
    double grossStep = (max - min) / MinTicks;
    double step = pow(10.0, floor(log10(grossStep)));
    if (5 * step < grossStep) {
        step *= 5;
    } else if (2 * step < grossStep) {
        step *= 2;
    }
    numTicks = int(ceil(max / step) - floor(min / step));
    if (numTicks < MinTicks)
        numTicks = MinTicks;
    min = floor(min / step) * step;
    max = ceil(max / step) * step;
}
            双缓冲技术(Double Buffering)(2、公有函数实现)
#include <QtGui>
#include <cmath>
using namespace std;
#include "plotter.h"
以上代码为文件的开头,在这里把std的名空间加入到当前的全局命名空间。这样在使用<cmath>里的函数时,就不用前缀std::了,如可以直接使用函数floor(),而不用写成std::floor()。
 
Plotter::Plotter(QWidget *parent) : QWidget(parent)
{
    setBackgroundRole(QPalette::Dark);
    setAutoFillBackground(true);
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    setFocusPolicy(Qt::StrongFocus);
    rubberBandIsShown = false;

    zoomInButton = new QToolButton(this);
    zoomInButton->setIcon(QIcon(":/images/zoomin.png"));
    zoomInButton->adjustSize();
    connect(zoomInButton, SIGNAL(clicked()), this, SLOT(zoomIn()));

    zoomOutButton = new QToolButton(this);
    zoomOutButton->setIcon(QIcon(":/images/zoomout.png"));
    zoomOutButton->adjustSize();
    connect(zoomOutButton, SIGNAL(clicked()), this, SLOT(zoomOut()));

    setPlotSettings(PlotSettings());
}
在构造函数中,调用setBackGroundRole(QPalette::Dark),当对控件进行放大需要重新绘制时,提供给Qt一个缺省的颜色填充新的区域,为了能够使用这个机制,还调用了setAutoFillBackground(true)。
函数setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)让控件在水平和垂直两个方向上都可以进行伸缩。如果控件需要占据屏幕上很大的控件,经常设置这个属性。缺省的设置是两个方向都是QSizePolicy::Preferred,意思是控件的实际尺寸和它的sizeHint一致,控件最小只能缩小到它的最小的sizeHint,并能够无限放大。
调用setFocusPolicy(Qt::StrongFocus)可以使控件通过鼠标点击或者Tab得到焦点。当Plotter控件得到焦点时,它可以接受键盘敲击事件。Plotter控件能够理解一些键盘事件,如+放大,-为缩小,可以向上下左右平移。
Figure 5.9. Scrolling the Plotter widget

 
在构造函数中,我们还创建了两个QToolButton,每一个按钮都有一个图标。点击这些图标可以放大或者缩小显示的图像。图标保存在资源文件中,为了任何程序都可以使用Plotter控件,需要在.pro添加资源条目:
RESOURCES    = plotter.qrc
资源文件是一个XML格式的文本文件,和在Spreadsheet中使用的很像:
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
    <file>images/zoomin.png</file>
    <file>images/zoomout.png</file>
</qresource>
</RCC>
调用QToolButton::adjustSize()调整按钮的大小为它们的sizeHint。在这里按钮不在布局中,在控件大小改变的时候,又程序计算它们的位置。由于没有了布局管理,因为我们必须在按钮的构造函数中确定按钮的父控件。
 
调用setPlotSettings()函数用来完成控件的初始化。函数代码如下:
void Plotter::setPlotSettings(const PlotSettings &settings)
{
    zoomStack.clear();
    zoomStack.append(settings);
    curZoom = 0;
    zoomInButton->hide();
    zoomOutButton->hide();
    refreshPixmap();
}
函数setPlotSettings()确定显示控件时的PlotSettings。它在Plotter构造函数中调用,也可以被Plotter的用户调用。开始的时候,Plotter使用的是缺省的放缩值。用户进行放大一次,就有一个新的PlotSettings对象加入到堆栈中。这个堆栈中有两个变量:
      zoomStack是保存PlotSettings对象的一个数组;
      curZoom是当前使用的PlotSettings的一个索引值。
调用setPlotSettings()后,zoomStack中只有一项,zoomIn和zoomOut按钮隐藏。如果我们调用函数zoomIn()和zoomOut(),这两个函数中调用了按钮的show()函数,它们才能显示出来。(通常,调用父控件的show()函数就显示所有的子控件。但是如果我们显式调用了子控件的hide(),必须要显示调用其show()函数显示它,否则就会一直隐藏)
调用refreshPixmap()来更新显示。通常,我们调用update()就可以,这里有些不一样,因为我们要保持QPixmap一直最新的状态。更新了图片后,refreshPixmap()再调用update()把图片显示到控件上。
void Plotter::zoomOut()
{
    if (curZoom > 0) {
        --curZoom;
        zoomOutButton->setEnabled(curZoom > 0);
        zoomInButton->setEnabled(true);
        zoomInButton->show();
        refreshPixmap();
    }
}
如果图片放大了,调用zoomOut()缩小它。它缩小比例系数,如果还能进一步缩小,zoomOut按钮一直有效。显示zoomIn按钮使之按钮有效,调用refreshPixmap()刷新控件。
void Plotter::zoomIn()
{
    if (curZoom < zoomStack.count() - 1) {
        ++curZoom;
        zoomInButton->setEnabled(curZoom < zoomStack.count() - 1);
        zoomOutButton->setEnabled(true);
        zoomOutButton->show();
        refreshPixmap();
    }
}
如果用户放大后又缩小控件,下一个放缩系数的PlotSettings就进入zoomStack。我们就可以再放大控件。
函数zoomIn增加放缩系数,zoomIn按钮显示出来,只要能够放大,按钮会一直有效。同事显示zoomOut按钮使之有效状态。
void Plotter::setCurveData(int id, const QVector<QPointF> &data)
{
    curveMap[id] = data;
  refreshPixmap();
}
函数setCurveData()设置一个指定id的曲线数据。如果曲线中有一个同样的id,那么就用新的数据替代旧数据。如果没有指定的id,则增加一个新的曲线。曲线的数据类型为QMap<int, QVector<QPointF> >
void Plotter::clearCurve(int id)
{
    curveMap.remove(id);
    refreshPixmap();
}
函数clearCurve()删除一个指定id的曲线。
QSize Plotter::minimumSizeHint() const
{
    return QSize(6 * Margin, 4 * Margin);
}
函数minimumSizeHint()和sizeHint()很像,确定控件的理想的尺寸。minimumSizeHint()确定控件的最大尺寸。布局管理器排列控件时不会超过控件的最大尺寸。
由于Margin值为50,所以我们返回的值为300×200,包括四个边界的宽度和Plot本身。如果再小,尺寸太小Plot就不能正常显示了。
QSize Plotter::sizeHint() const
{
    return QSize(12 * Margin, 8 * Margin);
}
在sizeHint()中,我们返回控件的理想尺寸,用Margin常数作为倍数,长宽的比例为3:2,与minimumSizeHint()中比例一致。
以上是Plotter的公有函数和槽函数。
            双缓冲技术(Double Buffering)(3、事件处理函数)
以下是Plotter控件的事件处理函数部分
void Plotter::paintEvent(QPaintEvent * /* event */)
{
    QStylePainter painter(this);
    painter.drawPixmap(0, 0, pixmap);
    if (rubberBandIsShown) {
        painter.setPen(palette().light().color());
        painter.drawRect(rubberBandRect.normalized()
                                      .adjusted(0, 0, -1, -1));
    }
    if (hasFocus()) {
        QStyleOptionFocusRect option;
        option.initFrom(this);
        option.backgroundColor = palette().dark().color();
        painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
    }
}
通常情况下,paintEvent()是我们处理控件的所有绘制的地方。这里Plotter控件的绘制是在refreshPixmap()中完成的,因此在paintEvent()函数中只是把图片显示在控件的(0,0)位置上。
如果能看到橡皮线,我们把它画到控件的上面。使用控件当前颜色组的“轻”的颜色橡皮线的颜色,和“黑”的背景形成对比。需要注意的是这个线是直接画在控件上的,对图片没有任何影响。使用QRect::normalized()确保橡皮线的矩形有着正数的宽和高,adjusted()减掉一个象素宽的矩形,显示它的轮廓。
如果Plotter有焦点,用控件样式的drawPrimitive()绘制一个焦点矩形,第一个参数为QStyle::PE_FrameFocusRect,第二个参数为一个QStyleOptionFocusRect对象。焦点矩形的绘制选项用initFrom()函数设置,继承自Plotter,但是背景颜色必须明确设置。
如果我们想使用当前的样式,我们可以直接调用QStyle的函数,比如:
style()->drawPrimitive(QStyle::PE_FrameFocusRect, &option, &painter, this);
或者我们使用QStylePainter,能绘制更加方便。
QWidget::Style()函数返回绘制控件使用的样式。在Qt中,一个控件的样式是QStyle的基类。Qt提供的样式有QWindowStyle,QWindowXpStyle,QMotifStyle,QCDEStyle,QMacStyle和QPlastiqueStyle。这些样式类都是重新实现了QStyle的虚函数来模拟特定平台的样式。QStylePainter::drawPrimitive()函数调用QStyle的同名函数,绘制控件的一些基本原色,如面板,按钮,焦点矩形等。在一个应用程序中,所有控件的样式都是一样的,可用通过QApplication::style()得到,也可以用QWidget::setStyle()设置某一个控件的样式。
把QStyle作为基类,可以定义一个用户样式。可以让一个应用程序看起来与众不同。通常的建议是使用和目标平台一致的样式。只要你有想法,Qt提供了很多的灵活性。
Qt提供的控件都是用QStyle绘制自己,所以在所有Qt支持的平台上,它们看起来都和平台的风格一致。
用户空间可以使用QStyle绘制自己或者使用Qt提供的控件作为子控件。对于Plotter,我们使用两个方式的组合,焦点矩形用QStyle样式绘制,zoomIn和zoomOut按钮为Qt提供的控件。
void Plotter::resizeEvent(QResizeEvent * /* event */)
{
    int x = width() - (zoomInButton->width()+ zoomOutButton->width() + 10);
    zoomInButton->move(x, 5);
    zoomOutButton->move(x + zoomInButton->width() + 5, 5);
    refreshPixmap();
}
控件大小改变时,Qt都会产生一个“resize”事件。这里,我们重写了resizeEvent()把zoomIn和zoomOut按钮放在Plotter控件的右上角。
我们把zoomIn和zoomOut按钮并排摆放,中间有5个象素的空隙,距离控件的上边距和右边距也为5个象素宽。
如果我们希望按钮放在控件的左上角(坐标点为(0,0))上,直接可以在Plotter构造函数中它们移动到左上角。如果我们想跟踪控件的右上角,它的坐标取决与控件的大小。因此需要重写resizeEvent()设置按钮位置。
在Plotter构造函数中,我们没有确定按钮的位置。但是不要紧,在控件第一次显示之前,Qt就会产生一个resize事件。
如果不重写resizeEvent()手工排列子控件,还可以使用布局管理器,如QGridLayout。使用一个布局会有些复杂,也会消耗更多资源。另一方面,它能够把左右排列的布局安排的更好,对Arabic和Hebrew语言尤其适用。
最后,调用refreshPixmap()绘制新的尺寸下的图片。
void Plotter::mousePressEvent(QMouseEvent *event)
{
    QRect rect(Margin, Margin,
              width() - 2 * Margin, height() - 2 * Margin);
    if (event->button() == Qt::LeftButton) {
        if (rect.contains(event->pos())) {
            rubberBandIsShown = true;
            rubberBandRect.setTopLeft(event->pos());
            rubberBandRect.setBottomRight(event->pos());
            updateRubberBandRegion();
            setCursor(Qt::CrossCursor);
        }
    }
}
当用户点击了鼠标左键,在控件上显示出一个橡皮线,显示的条件是rubberBandIsShown为true。把变量rubberBandRect的左上角和右下角都为当前的鼠标点,然后发出一个绘制事件绘制橡皮线,同时把光标改为十字型。
变量rubberBandRect为QRect类型。一个QRect可以由四个量(x,y,width,height)定义。其中(x,y)为矩形左上角的坐标,width*height为矩形的面积。或者由左上角和右下角的坐标对定义。在这里使用了坐标对定义的方法,把矩形的左上角和右下角的坐标都设置为鼠标点击的位置。然后调用updateRubberBandRegion()把橡皮线内的区域绘制出来。
Qt有两种设置光标形状的方法:
QWidget::setCursor(),当鼠标移动到一个控件上时,使用这个函数设置光标的形状。如果子控件上没有设置光标形状,则使用父控件的光标。通常使用的光标是一个箭头式光标。
QApplication::setOverrideCursor()设置应用程序的光标形状,取代控件中设定的光标,调用restoreOverrideCursor()后光标回到原来的状态。
在第四章中,我们调用了QApplication::setOverrideCursor()把光标设置为Qt::WaitCursor,把应用程序光标设置为等待式光标。
void Plotter::mouseMoveEvent(QMouseEvent *event)
{
    if (rubberBandIsShown) {
        updateRubberBandRegion();
        rubberBandRect.setBottomRight(event->pos());
        updateRubberBandRegion();
    }
}
当用户点中鼠标左键移动鼠标时,调用updateRubberBandRegion()重新绘制橡皮线所在区域。然后根据鼠标移动的位置重新计算橡皮线区域的大小,最后在调用updateRubberBandRegion()绘制新的橡皮线区域。这样就可以删除原来的橡皮线,在新的位置绘制新的橡皮线。
如果用户向上或者向下移动鼠标,rubberBandRect的右下角可能会到达它的左上角的上面或者左面,QRect的width和height会出现负值,在paintEvent()函数中调用了QRect::normalized()函数,它可以重新计算矩形的左上角和右下角的坐标值,保证得到一个非负的宽和高。
void Plotter::mouseReleaseEvent(QMouseEvent *event)
{
    if ((event->button() == Qt::LeftButton) && rubberBandIsShown) {
        rubberBandIsShown = false;
        updateRubberBandRegion();
        unsetCursor();
        QRect rect = rubberBandRect.normalized();
        if (rect.width() < 4 || rect.height() < 4)
            return;
        rect.translate(-Margin, -Margin);
        PlotSettings prevSettings = zoomStack[curZoom];
        PlotSettings settings;
        double dx = prevSettings.spanX() / (width() - 2 * Margin);
        double dy = prevSettings.spanY() / (height() - 2 * Margin);
        settings.minX = prevSettings.minX + dx * rect.left();
        settings.maxX = prevSettings.minX + dx * rect.right();
        settings.minY = prevSettings.maxY - dy * rect.bottom();
        settings.maxY = prevSettings.maxY - dy * rect.top();
        settings.adjust();
        zoomStack.resize(curZoom + 1);
        zoomStack.append(settings);
        zoomIn();
    }
}
用户释放鼠标左键时,我们删除橡皮线,恢复到正常的箭头式光标。如果橡皮线区域大于4*4,则把这个区域放大。如果小于这个值,则很可能是用户的一个误操作,也许只是想给控件一个焦点罢了,程序返回,什么都不做了。
进行放大的这部分代码有点复杂,因为我们需要同时处理控件坐标和plotter的坐标。大部分代码都是把rubberBandRect从控件坐标转到plotter坐标。完成转换以后,调用PlotSettings::adjust()进行四舍五入,找到一个合理的坐标刻度。图5-10和图5-11示意了这个坐标的转换:
Figure 5.10. Converting the rubber band from widget to plotter coordinates

Figure 5.11. Adjusting plotter coordinates and zooming in on the rubber band

 
坐标转换以后,我们进行放大。同时把放大系数等设置形成一个新的PlotSettings对象,然后把它放到zoomStack的最上面。
void Plotter::keyPressEvent(QKeyEvent *event)
{
    switch (event->key()) {
    case Qt::Key_Plus:
        zoomIn();
        break;
    case Qt::Key_Minus:
        zoomOut();
        break;
    case Qt::Key_Left:
        zoomStack[curZoom].scroll(-1, 0);
        refreshPixmap();
      break;
    case Qt::Key_Right:
        zoomStack[curZoom].scroll(+1, 0);
        refreshPixmap();
        break;
    case Qt::Key_Down:
        zoomStack[curZoom].scroll(0, -1);
        refreshPixmap();
        break;
    case Qt::Key_Up:
        zoomStack[curZoom].scroll(0, +1);
        refreshPixmap();
        break;
    default:
        QWidget::keyPressEvent(event);
    }
}
当当前的焦点在Plotter控件上时,用户敲击了键盘的某一个键值,keyPressEvent()就会调用。这里我们重写了这个函数,相应用户对6个键的相应:+,-,Up,Down,Left和Right。如果用户敲击的键不在这六个之中,则调用基类的函数进行处理。为了简便,我们这里忽略了Shift,Ctrl,和Alt键,这些键可以通过QKeyEvent::modifiers()得到。
void Plotter::wheelEvent(QWheelEvent *event)
{
    int numDegrees = event->delta() / 8;
    int numTicks = numDegrees / 15;
    if (event->orientation() == Qt::Horizontal) {
        zoomStack[curZoom].scroll(numTicks, 0);
    } else {
        zoomStack[curZoom].scroll(0, numTicks);
    }
    refreshPixmap();
}
鼠标滚轮转动时,Qt产生一个滚轮事件(Wheel event)。很多鼠标只有一个垂直的滚轮,但是考虑到一些鼠标也有水平滚轮,Qt对这两种方式的滚轮都支持。滚轮事件只是发生在有焦点的控件上。函数delta()返回的是滚轮滚动了8°时移动的距离。一般鼠标都是以15°事件发生后,我们修改zoomStack最上面的设置,然后刷新图片。
滚轮鼠标一般用来处理滚动条。如果我们使用了QScrollArea提供一个可以滚动的区域,QScrollBar自动处理滚轮事件,我们不用自己重写wheelEvent()函数。
            双缓冲技术(Double Buffering)(4、私有函数的实现)
  以下是私有函数的实现:
  void Plotter::updateRubberBandRegion()
{
    QRect rect = rubberBandRect.normalized();
    update(rect.left(), rect.top(), rect.width(), 1);
    update(rect.left(), rect.top(), 1, rect.height());
    update(rect.left(), rect.bottom(), rect.width(), 1);
    update(rect.right(), rect.top(), 1, rect.height());
}
函数updateRubberBand()在mousePressEvent(),mouseMoveEvent()和mouseReleaseEvent()中被调用,用来删除或者从新绘制橡皮线。函数中调用了四次update(),用四个绘制事件完成由橡皮线组成的四个小矩形的绘制。Qt也提供了一个类QRubberBand用来绘制橡皮线,但是控件自己提供的绘制函数会更好
void Plotter::refreshPixmap()
{
    pixmap = QPixmap(size());
    pixmap.fill(this, 0, 0);
    QPainter painter(&pixmap);
    painter.initFrom(this);
    drawGrid(&painter);
    drawCurves(&painter);
    update();
}
函数refreshPixmap()把plot绘制到图片上,并且更新控件。首先我们把图片的大小调整为和当前控件大小相同,用控件的背景颜色填充整个图片。这个颜色是当前调色版的“dark”部分。如果背景用的刷子不是固体的(solid brush,刷子的样式,只有颜色,没有花纹的那种最简单的),QPixmap::fill()需要知道控件中刷子的偏移量,以便图片和控件保持一致。因为我们保存的是整个控件,那么因此偏移位置为(0,0)。
在这个函数中,我们使用了一个QPainter绘制图片,QPainter::initFrom()设置绘制图片所需画笔,背景和字体,参数this表示这些设置和Plotter控件的相应设置是一致的。然后我们调用drawGrid(),drawCurves()绘制网格和曲线。最后,update()函数更新全部控件,在painteEvent()函数中把图片拷贝到控件上。
void Plotter::drawGrid(QPainter *painter)
{
  QRect rect(Margin, Margin,
              width() - 2 * Margin, height() - 2 * Margin);
    if (!rect.isValid())
        return;
    PlotSettings settings = zoomStack[curZoom];
    QPen quiteDark = palette().dark().color().light();
    QPen light = palette().light().color();
    for (int i = 0; i <= settings.numXTicks; ++i) {
        int x = rect.left() + (i * (rect.width() - 1)
                                / settings.numXTicks);
        double label = settings.minX + (i * settings.spanX()
                                          / settings.numXTicks);
        painter->setPen(quiteDark);
        painter->drawLine(x, rect.top(), x, rect.bottom());
        painter->setPen(light);
        painter->drawLine(x, rect.bottom(), x, rect.bottom() + 5);
        painter->drawText(x - 50, rect.bottom() + 5, 100, 15,
                          Qt::AlignHCenter | Qt::AlignTop,
                          QString::number(label));
    }
    for (int j = 0; j <= settings.numYTicks; ++j) {
        int y = rect.bottom() - (j * (rect.height() - 1)
                                  / settings.numYTicks);
        double label = settings.minY + (j * settings.spanY()
                                          / settings.numYTicks);
        painter->setPen(quiteDark);
        painter->drawLine(rect.left(), y, rect.right(), y);
        painter->setPen(light);
        painter->drawLine(rect.left() - 5, y, rect.left(), y);
        painter->drawText(rect.left() - Margin, y - 10, Margin - 5, 20,
                          Qt::AlignRight | Qt::AlignVCenter,
                          QString::number(label));
    }
    painter->drawRect(rect.adjusted(0, 0, -1, -1));
}
函数drawGrid()在坐标轴和曲线的下面绘制网格。这个区域由一个矩形确定,如果控件太小,则不绘制。第一个循环绘制网格的垂直线,个数为x坐标轴的刻度个数。第二个循环绘制网格的水平线,共y坐标轴的刻度个数。最后,沿边界绘制一个矩形。drawText()绘制两个坐标轴上刻度的个数。
函数painter->drawText()语法如下:
painter->drawText(x, y, width, height, alignment, text);
其中(x,y,width,height)所确定的矩形确定文字的大小和位置alignment为文字的对其方式。
void Plotter::drawCurves(QPainter *painter)
{
    static const QColor colorForIds[6] = {
        Qt::red, Qt::green, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow
    };
    PlotSettings settings = zoomStack[curZoom];
    QRect rect(Margin, Margin,
              width() - 2 * Margin, height() - 2 * Margin);
    if (!rect.isValid())
        return;
    painter->setClipRect(rect.adjusted(+1, +1, -1, -1));
    QMapIterator<int, QVector<QPointF> > i(curveMap);
    while (i.hasNext()) {
        i.next();
        int id = i.key();
        const QVector<QPointF> &data = i.value();
        QPolygonF polyline(data.count());
        for (int j = 0; j < data.count(); ++j) {
            double dx = data[j].x() - settings.minX;
            double dy = data[j].y() - settings.minY;
            double x = rect.left() + (dx * (rect.width() - 1)
                                        / settings.spanX());
            double y = rect.bottom() - (dy * (rect.height() - 1)
                                          / settings.spanY());
            polyline[j] = QPointF(x, y);
        }
        painter->setPen(colorForIds[uint(id) % 6]);
        painter->drawPolyline(polyline);
    }
}
函数drawCurves()在网格上绘制出曲线。调用了QPainter::setClipRect()函数设置绘制曲线的矩形区域(不包括四周的间隙和框架)。QPainter会忽略画到这个区域外的象素。
然后我们遍历所有的曲线,在每一条曲线,遍历它所有的QPointF点。函数key()得到曲线的id,value()函数得到曲线的QVector<QPointF>类型的数据。内层循环把QPointF记录的plotter坐标转换为控件坐标,把它们保存在多段线变量中。
转换坐标后,我们设置画笔的颜色(使用函数前面预定义的颜色),调用drawPolyline()绘制出所有的曲线的点。
        双缓冲技术(Double Buffering)(5、类PlotSettings实现)
下面是PlotSettings的实现:
PlotSettings::PlotSettings()
{
    minX = 0.0;
    maxX = 10.0;
    numXTicks = 5;
    minY = 0.0;
    maxY = 10.0;
    numYTicks = 5;
}
在构造函数中,把两个坐标轴的初始化为从0到10,分为5个刻度。
void PlotSettings::scroll(int dx, int dy)
{
    double stepX = spanX() / numXTicks;
    minX += dx * stepX;
    maxX += dx * stepX;
    double stepY = spanY() / numYTicks;
    minY += dy * stepY;
    maxY += dy * stepY;
}
函数scroll()增加或者减少minX,maxX,minY,maxY的值,放大或缩小控件的尺寸为给定的偏移值乘以坐标刻度的两倍。这个函数在Plotter::keyPressEvent()函数中调用。
void PlotSettings::adjust()
{
    adjustAxis(minX, maxX, numXTicks);
    adjustAxis(minY, maxY, numYTicks);
}
函数adjust()在Plotter::mouseReleaseEvent()中调用。重新计算minX,maxX,minY,maxY的值,重新得到坐标轴刻度的个数。私有函数adjustAxis()一次计算一个坐标轴。
void PlotSettings::adjustAxis(double &min, double &max,
                              int &numTicks)
{
    const int MinTicks = 4;
    double grossStep = (max - min) / MinTicks;
    double step = pow(10.0, floor(log10(grossStep)));
    if (5 * step < grossStep) {
        step *= 5;
    } else if (2 * step < grossStep) {
        step *= 2;
    }
    numTicks = int(ceil(max / step) - floor(min / step));
    if (numTicks < MinTicks)
        numTicks = MinTicks;
    min = floor(min / step) * step;
    max = ceil(max / step) * step;
}
函数adjustAxis()修正minX,maxX,minY,maxY的值,根据给定的最大最小范围值计算刻度的个数。函数修改了参数的值(成员变量的值),所以没有使用常引用。
前部分代码主要是确定坐标轴上单位刻度的值(step)。为了得到合理的刻度数,必须得到准确的步长值。例如,一个坐标轴步长为3.8,坐标轴上其他的刻度值都是3.8的倍数,在用户很不习惯,对于一个整数坐标值,合理的步长应给为10n, 2•10n, 或者5•10n。
首先我们计算最大步长(gross step),然后计算小于或者等于这个步长的10n,通过计算这个步长的以十为底的对数,然后计算这个值的10次方。例如,如果最大步长为236,log (236)为2.37291…,四舍五入为2,得到102 = 100作为候选的步长值。
有了第一个值以后,我们再继续计算其他的候选值2•10n 和 5•10n。如上例中,另外两个可能的值为200和500。500大于最大的步长值不能使用,200小于236,使用200作为步长的值。
接着计算刻度数,min和max就很容易了。新的min值为原来的min值和步长乘积的较小整数值,新的max为原来的max值和步长乘积的较大整数值。新的numTicks为新的min和max的间隔数。例如,输入的min值为240,max为1184,新的值就会变成200,1200,200为步长,就有numTicks值为5;
有时这个算法并不是最优的。一个更加复杂的算法是Paul S. Heckbert在Graphics Gem上发表的一篇名为“Nice Numbers for Graph Labels”(ISBN 0-12-286166-3)
这一章是第一部分的最后一章。介绍了怎样从现有的Qt控件基础上得到一个新的控件,和以QWidget作为基类得到一个新的控件。在第二章我们看到了怎么在一个控件中对其他控件进行组合,在第六章中我们将会继续介绍。
到此为止,我们已经介绍了很多Qt GUI编程方面的知识。在第二部分和第三部分中,我们将会深入介绍Qt编程的其他方面。
离线keisuo

只看该作者 16楼 发表于: 2007-05-11
对于JPG图像,好像是有专利问题……我并没有找到JPG图像格式详细说明。
于是,参考EGui中的Demo程序,调用libjpeg.a库函数,在Framebuffer中显示JPG图像。
在编译libjpeg.a时出了点错,“ar rc rc”多了一个“rc”!
在成功编译后,把libjpeg.a及所有的.h文件都拷贝到程序目录中,就可以使用了。
showjpeg.c
代码:
/******************************************************************************
* showjpeg.c - 调用库函数对JPG文件进行解码, 并在framebuffer中显示出来
*
* Author : allen
* Email  : 408studio@sohu.com
* Date  : 2006/11/20
******************************************************************************/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>

char *fbp = 0;
int xres = 0;
int yres = 0;
int bits_per_pixel = 0;

extern unsigned char * decode_jpeg(char *filename, short *widthPtr, short *heightPtr);

int  show_jpg(char *bmpfile);
/******************************************************************************
*
******************************************************************************/
int main( int argc, char *argv[] )
{
    int fbfd = 0;
    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo;
    long int screensize = 0;

    if (argc != 2)
    {
      printf("usage: %s filename\n", argv[0]);
      return 0;
    }
    // Open the file for reading and writing
    fbfd = open("/dev/fb0", O_RDWR);
    if (!fbfd)
    {
        printf("Error: cannot open framebuffer device.\n");
        exit(1);
    }

    // Get fixed screen information
    if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo))
    {
        printf("Error reading fixed information.\n");
        exit(2);
    }

    // Get variable screen information
    if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo))
    {
        printf("Error reading variable information.\n");
        exit(3);
    }

    printf("%dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel );
    xres = vinfo.xres;
    yres = vinfo.yres;
    bits_per_pixel = vinfo.bits_per_pixel;

    // Figure out the size of the screen in bytes
    screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;

    // Map the device to memory
    fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED,
                      fbfd, 0);
    if ((int)fbp == -1)
    {
        printf("Error: failed to map framebuffer device to memory.\n");
        exit(4);
    }

  show_jpg(argv[1]);

    munmap(fbp, screensize);
    close(fbfd);
    return 0;
}

/******************************************************************************
*
******************************************************************************/
int show_jpg(char *filename)
{
  int i, j;
  short int width = 0, height = 0;
  unsigned char *jpeg = NULL;
  long location = 0, pos = 0;

  jpeg = decode_jpeg(filename, &width, &height);
  if (jpeg == NULL)
  {
      return -1;
  }

  /*显示图像信息*/
  for (i=0; i<height; i++)
  {
      for (j=0; j<width; j++)
      {
        location = i * xres * bits_per_pixel / 8 + j * bits_per_pixel / 8;
        pos = i * (width * 3) + (j * 3);
        *(fbp + location + 0) = *(jpeg + pos + 2);
        *(fbp + location + 1) = *(jpeg + pos + 1);
        *(fbp + location + 2) = *(jpeg + pos + 0);
        *(fbp + location + 3) = 0x00;
      }
  }

  free(jpeg);
  return 0;
}


jpeg.c
代码:

/*Copyright George Peter Staplin 2003*/

/*You may use/copy/modify/distribute this software for any purpose
*provided that I am given credit and you don't sue me for any bugs.
*/

/*Please contact me using GeorgePS@XMission.com if you like this, or
*have questions.
*/
     
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "jpeglib.h"
#include "jerror.h"

#include <time.h>

#ifndef u_char
#define u_char unsigned char
#endif     


void jpeg_error_exit (j_common_ptr cinfo) {
  cinfo->err->output_message (cinfo);
  exit (EXIT_FAILURE);
}

/*This returns an array for a 24 bit image.*/
u_char *decode_jpeg (char *filename, short *widthPtr, short *heightPtr)
{
  register JSAMPARRAY lineBuf;
  struct jpeg_decompress_struct cinfo;
  struct jpeg_error_mgr err_mgr;
  int bytesPerPix;
  FILE *inFile;
  u_char *retBuf;
   
  inFile = fopen (filename, "rb");
  if (NULL == inFile)
    {
      printf ("Open file error %s\n",filename);
      return NULL;
    }

  cinfo.err = jpeg_std_error (&err_mgr);
  err_mgr.error_exit = jpeg_error_exit;   

  jpeg_create_decompress (&cinfo);
  jpeg_stdio_src (&cinfo, inFile);
  jpeg_read_header (&cinfo, 1);
  cinfo.do_fancy_upsampling = 0;
  cinfo.do_block_smoothing = 0;
  jpeg_start_decompress (&cinfo);

  *widthPtr = cinfo.output_width;
  *heightPtr = cinfo.output_height;
  bytesPerPix = cinfo.output_components;

  lineBuf = cinfo.mem->alloc_sarray ((j_common_ptr) &cinfo, JPOOL_IMAGE, (*widthPtr * bytesPerPix), 1);
  retBuf = (u_char *) malloc (3 * (*widthPtr * *heightPtr));
     
  if (NULL == retBuf)
    {
      perror (NULL);
      return NULL;
    }
     

  if (3 == bytesPerPix)
    {
      int x;
      int y;
      int i;
         
      for (y = 0; y < cinfo.output_height; ++y)
  {   
    jpeg_read_scanlines (&cinfo, lineBuf, 1);
    memcpy ((retBuf + y * *widthPtr * 3),lineBuf[0],3 * *widthPtr);
  }
    } else if (1 == bytesPerPix)
      {
  unsigned int col;
  int lineOffset = (*widthPtr * 3);
  int lineBufIndex;
  int x ;
  int y;
                 
  for (y = 0; y < cinfo.output_height; ++y)
    {
      jpeg_read_scanlines (&cinfo, lineBuf, 1);
           
      lineBufIndex = 0;
      for (x = 0; x < lineOffset; ++x) {
        col = lineBuf[0][lineBufIndex];
         
        retBuf[(lineOffset * y) + x] = col;
        ++x;
        retBuf[(lineOffset * y) + x] = col;
        ++x;
        retBuf[(lineOffset * y) + x] = col;
           
        ++lineBufIndex;
      }         
    }
      } else {
  fprintf (stderr, "Error: the number of color channels is %d.  This program only handles 1 or 3\n", bytesPerPix);
  return NULL;
      }
  jpeg_finish_decompress (&cinfo);
  jpeg_destroy_decompress (&cinfo);
  fclose (inFile);
         
  return retBuf;
}


Makefile
代码:


OBJ= showjpeg.o jpeg.o
TARGET=showjpeg

all: $(TARGET)

$(TARGET): $(OBJ)
  $(CC) $(OBJ) libjpeg.a -o $(TARGET)

.c.o:
  $(CC) -c -o $@ $<

clean:
  rm $(TARGET) -f
  rm *.o -f
  rm *~ -f
离线keisuo

只看该作者 15楼 发表于: 2007-05-11
在Linux控制台下显示JPEG图像

1、引言

通常情况下,在Linux控制台下是无法查看图像文件的,要想查看图像文件,比如要查看JPEG格式的图像文件,可能必须启动X-Windows,通过GNOME或者KDE之类的桌面管理器提供的图像查看工具查看图片内容。那么,能不能有办法在控制台下面简单地浏览图像内容呢。实际上,这是完全可以的。在Linux下有一个名为zgv的看图软件就是工作在控制台下的。不过,由于它所使用的底层图形库svgalib已经是一个比较“古老”的图形库了,所以现在知道zgv的人并不是很多,用的人就更少了。

目前Linux上的底层图形支持通常是由Framebuffer提供的,因此,作者试图在本文中说明如何通过Framebuffer和libjpeg在控制台上显示JPEG图像。需要说明的是,本文中所编写的程序fv并非zgv的替代品,而只是一个出于验证想法的简单程序(fv的含义是Framebuffer Vision)。本文将先对Framebuffer和libjpeg的编程做一个简略的说明,然后再给出程序fv的具体实现。
2、Framebuffer介绍

Framebuffer在Linux中是作为设备来实现的,它是对图形硬件的一种抽象[1],代表着显卡中的帧缓冲区(Framebuffer)。通过Framebuffer设备,上层软件可以通过一个良好定义的软件接口访问图形硬件,而不需要关心底层图形硬件是如何工作的,比如,上层软件不用关心应该如何读写显卡寄存器,也不需要知道显卡中的帧缓冲区从什么地址开始,所有这些工作都由Framebuffer去处理,上层软件只需要集中精力在自己要做的事情上就是了。

Framebuffer的优点在于它是一种低级的通用设备,而且能够跨平台工作,比如Framebuffer既可以工作在x86平台上,也能工作在PPC平台上,甚至也能工作在m68k和SPARC等平台上,在很多嵌入式设备上Framebuffer也能正常工作。诸如Minigui之类的GUI软件包也倾向于采用Framebuffer作为硬件抽象层(HAL)。

从用户的角度来看,Framebuffer设备与其它设备并没有什么不同。Framebuffer设备位于/dev下,通常设备名为fb*,这里*的取值从0到31。对于常见的计算机系统而言,32个Framebuffer设备已经绰绰有余了(至少作者还没有看到过有32个监视器的计算机)。最常用到的Framebuffer设备是/dev/fb0。通常,使用Framebuffer的程序通过环境变量FRAMEBUFFER来取得要使用的Framebuffer设备,环境变量FRAMEBUFFER通常被设置为”/dev/fb0”。

从程序员的角度来看,Framebuffer设备其实就是一个文件而已,可以像对待普通文件那样读写Framebuffer设备文件,可以通过mmap()将其映射到内存中,也可以通过ioctl()读取或者设置其参数,等等。最常见的用法是将Framebuffer设备通过mmap()映射到内存中,这样可以大大提高IO效率。

要在PC平台上启用Framebuffer,首先必须要内核支持,这通常需要重新编译内核。另外,还需要修改内核启动参数。在作者的系统上,为了启用Framebuffer,需要将/boot/grub/menu.lst中的下面这一行:

kernel /boot/vmlinuz-2.4.20-8 ro root=LABEL=/1

修改为

kernel /boot/vmlinuz-2.4.20-8 ro root=LABEL=/1 vga=0x0314

即增加了vga=0x0314这样一个内核启动参数。这个内核启动参数表示的意思是:Framebuffer设备的大小是800x600,颜色深度是16bits/像素。

下面,来了解一下如何编程使用Framebuffer设备。由于对Framebuffer设备的读写应该是不缓冲的,但是标准IO库默认是要进行缓冲的,因此通常不使用标准IO库读写Framebuffer设备,而是直接通过read()、write()或者mmap()等系统调用来完成与Framebuffer有关的IO操作。又由于mmap()能够大大降低IO的开销,因此与Framebuffer设备有关的IO通常都是通过mmap()系统调用来完成的。mmap()的函数原型如下(Linux系统上的定义):

#include <sys/mman.h>

#ifdef _POSIX_MAPPED_FILES



void  *  mmap(void *start, size_t length, int prot , int flags, int fd,

      off_t offset);



int munmap(void *start, size_t length);



#endif

系统调用mmap()用来实现内存映射IO。所谓内存映射IO,是指将一个磁盘文件的内容与内存中的一个空间相映射。当从这个映射内存空间中取数据时,就相当于从文件中读取相应的字节,而当向此映射内存空间写入数据时,就相当于向磁盘文件中写入数据。这就是内存映射IO的含义。

具体到对mmap()而言,当调用成功时,返回值就是与磁盘文件建立了映射关系的内存空间的起始地址,当调用失败时,mmap()的返回值是-1。第一个参数start通常设置为0,表示由系统选择映射内存空间;第二个参数length指定了要映射的字节数;第三个参数指明了映射内存空间的保护属性,对于Framebuffer通常将其设置为PROT_READ | PROT_WRITE,表示既可读也可写;第四个参数flags指明了影响映射内存空间行为的标志,对于Framebuffer编程而言,要将flags设置为MAP_SHARED,表明当向映射内存空间写入数据时,将数据写入磁盘文件中;第五个参数fd是要映射的文件的文件描述符;第六个参数offset指明了要映射的字节在文件中的偏移量。

如果mmap()调用成功,就可以在程序中对得到的映射内存空间进行读写操作了。所有的读写都将由操作系统内核转换成IO操作。

在使用完映射内存空间之后,应当将其释放,这是通过munmap()系统调用完成的。munmap()的第一个参数是映射内存空间的起始地址,第二个参数length是映射内存空间的长度,单位为字节。如果释放成功,munmap()返回0,否则返回-1。

如果应用程序需要知道Framebuffer设备的相关参数,必须通过ioctl()系统调用来完成。在头文件<linux/fb.h>中定义了所有的ioctl命令字,不过,最常用的ioctl命令字是下面这两个:FBIOGET_FSCREENINFO和FBIOGET_VSCREENINFO,前者返回与Framebuffer有关的固定的信息,比如图形硬件上实际的帧缓存空间的大小、能否硬件加速等信息;而后者返回的是与Framebuffer有关的可变信息,之所以可变,是因为对同样的图形硬件,可以工作在不同的模式下,简单来讲,一个支持1024x768x24图形模式的硬件通常也能工作在800x600x16的图形模式下,可变的信息就是指Framebuffer的长度、宽度以及颜色深度等信息。这两个命令字相关的结构体有两个:struct fb_fix_screeninfo和struct fb_var_screeninfo,这两个结构体都比较大,前者用于保存Framebuffer设备的固定信息,后者用于保存Framebuffer设备的可变信息。在调用ioctl()的时候,要用到这两个结构体。应用程序中通常要用到struct fb_var_screeninfo的下面这几个字段:xres、yres、bits_per_pixel,分别表示x轴的分辨率、y轴的分辨率以及每像素的颜色深度(颜色深度的单位为bit/pixel),其类型定义都是无符号32位整型数。
3、libjpeg函数库介绍

JPEG是CCITT和ISO定义的一种连续色调图像压缩标准[2]。JPEG是一种有损图像压缩标准,其基础是DCT变换(离散余弦变换)。JPEG图像的压缩过程分为三步:DCT计算,量化,变长编码分配。尽管CCITT定义了JPEG图像压缩标准,但是却并没有为JPEG定义标准的文件格式。这导致了现实世界中出现了各种各样的JPEG文件格式,而一种被称为JFIF的JPEG文件格式逐渐成为JPEG文件格式的主流。

libjpeg是一个被广泛使用的JPEG压缩/解压缩函数库(至少在Unix类系统下是广泛使用的),它能够读写JFIF格式的JPEG图像文件,通常这类文件是以.jpg或者.jpeg为后缀名的。通过libjpeg库,应用程序可以每次从JPEG压缩图像中读取一个或多个扫描线(scanline,所谓扫描线,是指由一行像素点构成的一条图像线条),而诸如颜色空间转换、降采样/增采样、颜色量化之类的工作则都由libjpeg去完成了。

要使用libjpeg,需要读者对数字图像的基本知识有初步的了解。对于libjpeg而言,图像数据是一个二维的像素矩阵。对于彩色图像,每个像素通常用三个分量表示,即R(Red)、G(Green)、B(Blue)三个分量,每个分量用一个字节表示,因此每个分量的取值范围从0到255;对于灰度图像,每个像素通常用一个分量表示,一个分量同样由一个字节表示,取值范围从0到255。由于本文不会涉及到索引图像,因此这里略去对索引图像的说明。

在libjpeg中,图像数据是以扫描线的形式存放的。每一条扫描线由一行像素点构成,像素点沿着扫描线从左到右依次排列。对于彩色图像,每个分量由三个字节组成,因此这三个字节以R、G、B的顺序构成扫描线上的一个像素点。一个典型的扫描线形式如下:

      R,G,B,R,G,B,R,G,B,…

通过libjpeg解压出来的图像数据也是以扫描线的形式存放的。

在本文中,只涉及到JPEG的解压缩,因此只对libjpeg的解压过程进行说明,有关libjpeg的压缩过程和其它高级用法,请参考[3]。一般地,libjpeg的解压过程如下:

1、分配并初始化一个JPEG解压对象(本文中将JPEG解压对象命名为cinfo):

    struct jpeg_decompress_struct cinfo;

    struct jpeg_error_mgr jerr;

    ...

    cinfo.err = jpeg_std_error(&jerr);

    jpeg_create_decompress(&cinfo);

2、指定要解压缩的图像文件:

    FILE * infile;

    ...

    if ((infile = fopen(filename, "rb")) == NULL) {

        fprintf(stderr, "can't open %s\n", filename);

        exit(1);

    }

    jpeg_stdio_src(&cinfo, infile);

3、调用jpeg_read_header()获取图像信息:

    jpeg_read_header(&cinfo, TRUE);

4、这是一个可选步骤,用于设置JPEG解压缩对象cinfo的一些参数,本文可忽略;

5、调用jpeg_start_decompress()开始解压过程:

    jpeg_start_decompress(&cinfo);

调用jpeg_start_decompress()函数之后,JPEG解压缩对象cinfo中的下面这几个字段将会比较有用:

l output_width                这是图像输出的宽度

l output_height                这是图像输出的高度

l output_components              每个像素的分量数,也即字节数

这是因为在调用jpeg_start_decompress()之后往往需要为解压后的扫描线上的所有像素点分配存储空间,这个空间的大小可以通过output_width * output_componets确定,而要读取的扫描线的总数为output_height行。

6、读取一行或者多行扫描线数据并处理,通常的代码是这样的:

      while (cinfo.output_scanline < cinfo.ouput_height) {

              jpeg_read_scanlines();

              /* deal with scanlines */

      }

对扫描线的读取是按照从上到下的顺序进行的,也就是说图像最上方的扫描线最先被jpeg_read_scanlines()读入存储空间中,紧接着是第二个扫描线,最后是图像底边的扫描线被读入存储空间中。

7、调用jpeg_finish_decompress()完成解压过程:

    jpeg_finish_decompress(&cinfo);

8、调用jpeg_destroy_decompress()释放JPEG解压对象cinfo:

    jpeg_destroy_decompress(&cinfo);

以上就是通过libjpeg函数解压JPEG压缩图像的基本过程,由于本文不涉及libjpeg的高级特性和用法,因此,上面的介绍对于说明本文中要用到的libjpeg的功能已经足够了。

另外一个需要说明地方是:由于作者所用的Framebuffer设备的颜色深度为16位,颜色格式为5-6-5格式——即R(红色)在16bit中占据高5位,G(绿色)在16bit中占据中间6位,B(蓝色)在16bit中占据低5位;而libjpeg解压出来的图像数据为24位RGB格式,因此必须进行转换。对于24位的RGB,每个字节表示一个颜色分量,因此转换的方式为:对于R字节,右移3位,对于G字节,右移2位,对于B字节,右移3位,然后将右移得到的值拼接起来,就得到了16位的颜色值。在后面的程序中,将把24位的颜色称为RGB888,而把16位颜色值称为RGB565,这种命名方式可能不太规范,不过无论如何,在本文中就这样称呼了。另外,读者可能会想到,上面这种直接将颜色分量的低位丢弃的方式不是会导致图像细节的丢失吗?比如,对于24位颜色的R字节,假如原来低3位的值在0~7之间均匀分布,转换之后,所有这低3位的值全部都变成了0,这就是颜色细节的丢失。为了处理这个问题,可以采用误差扩散算法或者抖动算法来完成颜色转换。为了保持程序的简单,本文中仍然只采用上面提到的最简单的转换方式——而且事实表明,这样做得到的结果并不差。


4、结论

上述测试结果表明,通过Framebuffer在控制台显示JPEG图像是能够实现的,并且效果能够接受。但是,程序fv目前只能接受JPEG格式的图像,并且图像大小不能超出屏幕大小范围,比如,对于作者测试时所用的Framebuffer设置而言,大小超过800x600的图像是无法显示的。显然,可以通过图像缩放来显示超出屏幕大小的JPEG图像。其次,也可以让fv能够识别更多格式的图像,比如可以通过libpng识别并显示png格式的图像。最后,fv还可以考虑采用误差扩散算法或者抖动算法来完成颜色转换,以减小颜色丢失。
参考文献
离线keisuo

只看该作者 14楼 发表于: 2007-05-10
使用qt绘制背景图片的一种加速方法
离线keisuo

只看该作者 13楼 发表于: 2007-04-26
嵌入式Linux的图像采集与显示
1 系统平台的硬件结构
本文使用的系统平台硬件功能框图如图1所示,该平台采用Samsung公司的处理器S3C2410。该处理器内部集成了ARM公司ARM920T处理器核的32b微控制器,资源丰富,带独立的16kB的指令Cache和16kB数据Cache,LCD控制器、RAM控制器,NAND闪存控制器,3路UART、4路DMA、4路带PWM的Timer、并行I/O口、8路10位ADC、Touch Screen接口,I2C接口,I2S接口、2个USB接口控制器、2路SPI,主频最高可达203MHz。在处理器丰富资源的基础上,还进行了相关的配置和扩展,平台配置了16MB 16b的FLASH和64MB 32位的SDRAM,通过以太网控制器芯片AX88796扩展了一个网口,另外引出了一个HOST USB接口。在USB接口上外接一个带USB口的摄像头。另外,还配有分辨率为320×240,256色的LCD。
2 嵌入式Linux简介
Linux操作系统具有相当多的优点,他的内核稳定、功能强大、支持多种硬件平台、源代码完全开放,可裁减和低成本的特性非常适合于嵌入式应用,并且Linux本身直接提供完整的TCP/IP协议,可非常方便地进行网络应用。但Linux内核本身不具备强实时性,且内核体积较大,而且嵌入式系统的硬件资源有限,因此把Linux用于嵌入式系统,必须对Linux进行实时化和嵌入式化,即通过配置内核,裁减shell和嵌入式C库对系统定制,使整个系统能够存放到容量较小的FLASH中,Linux的动态模块加载,使Linux的裁减极为方便,高度模块化的部件使添加非常容易。


整个系统软件是在嵌入式Linux的基础上构建的。S3C2410平台使用的Linux内核是在Linux-2.4.18内核打上patch-2.4.18-S3C2410这个补丁后编译而成。S3C2410平台使用的文件系统是yaffs,文件系统包括应用程序、模块、配置文件和库等,图像的采集和显示是建立在嵌入式Linux内核之上的,整个软件系统如图2所示。
通常宿主机和目标板上的处理器不同,宿主机通常为Intel处理器,而目标板如图1所示为SAMSUNG S3C2410,所以程序需要使用针对处理器特点的编译器才能生成在相应平台上可运行的代码,GNU编译器提供这样的功能,在编译时,可以选择开发所需的宿主机和目标机,从而建立开发环境。在进行嵌入式开发前的第一步工作就是把一台PC机作为宿主机开发机,并在其上安装指定操作系统。对于嵌入式Linux,宿主机PC上应安装Linux系统。之后,在宿主机上建立交叉编译调试的开发环境,开发环境的具体建立这里不细谈。本文采用移植性很强的C语言在宿主机上编写视频采集程序,再利用交叉编译调试工具编译链接生成可执行代码,最后向目标平台移植。


3 基于Video4Linux的图像采集
Video4Linux是Linux中关于视频设备的内核驱动,他为针对视频设备的应用程序编程提供一系列接口函数,在Linux下,视频采集设备的正常使用依赖于对Video4 Linux标准的支持。如果使用Video4Linux,在编译内核时,一定要选中Multimedia Devices下的Video for Linux选项,本文针对的设备文件是/dev/video,使用的器件是基于OV511的USB摄像头。在运行程序前,一定要先加载USB及OV511设备驱动模块,同时加载Video4Linux模块,分别使用命令:modprobe usbcore,modprobe usbohci,modprobe videodev和modprobe ov511,以确保生成设备文件/dev/video,若使用的Linux操作系统不支持modprobe命令,也可使用insmod命令。一般来讲,基于Video4Linux的图像采集的程序流程如图3所示。


以下简单介绍程序的编写,在这里只给出关键部分的实现代码。
首先,必须声明包含2个头文件:


在获取图像信息后,还可根据需要改变这些信息,例如对比度、亮度、调色板等,具体做法是先给video_picture中相应变量赋新值,再利用VIDIOCSPICT ioct1函数。
第2部分,使用mmap方式的单帧图象采集:


然后调用ioct1(grab_fd,VIDIOCSYNC,&frame)函数,该函数成功返回则表示采集完毕,采集到的图像数据放到以data为起始地址,长度为240×320×3的内存区域中,读取该内存中的数据便可得到图像数据。
在此基础上同样可实现连续帧的采集,即一次采集连续多帧图像的数据,Video4Linux最多支持一次采集32帧,此时首先要设置grab_buf.frame为要采集的帧数,而每一帧的数据在内存中的位置为data+grab_vm.offsets[frame],其中grab_vm为video_mbuf结构体变量的一个声明,利用ioct1(fd,VIDIOCGMBUF,&grab_vm)便可获得grab_vm的信息。
4 基于FrameBuffer的图象显示
当Video4Linux使用mmap方式采集图像时,他总是尽最大努力将图像直接显示在屏幕上,但并不一定能够完成,因此一个完整的设备应该具有图像显示的功能,一般来讲,嵌入式Linux下显示一幅图像总共有以下几种方法:
(1)在利用Video4Linux采集图像时,将采集到的图象数据直接放到FrameBuffer的内存映射区中,而Video4Linux也支持这种功能,利用VIDIOCSFBUF和VIDIOCGFBUF这两个ioct1函数,可设置和获得struct video_buffer。但该方法并不是每个图像采集设备都支持。
(2)进图像数据存成各种格式(例如bmp),在各种GUI软件中,均会直接显示不同格式的图像的函数,如MiniGui中的FillBoxWithBitmap函数。
(3)直接将图像数据写入FrameBuffer中。
在这里主要介绍第3种。FrameBuffer设备是运行在Linux控制台上的一个优秀的图形接口,他几乎支持所有的硬件,提供了统一的API接口,很好地实现了硬件无关性,他可以直接操作显存,而且还留有提供图形加速功能的接口,运行时不需要root权限;FrameBuffer的设备节点是/dev/fb*,用户若要使用他,需要在编译内核时选中FrameBuffer,其简单的使用程序如下:


从vinfo和finfo中取得显存起始地址、分辨率、色深等信息,然后根据这些计算出需映射显存的大小。


由此便可直接操作大小为screensize,起始地址为fbp的内存区域,在LCD上直接显示图像、图形、文字等,例如执行memset(fbp,0,screensize)将进行清屏操作。
需要注意的是,对于色深为8位或8位以下的设备,在进行绘图操作前还需要设置合适的调色板,操作调色板要用到fb_camp结构,执行ioctl(fd,FBIOGETCMAP,&old_cmap)将保存调色板信息,执行ioctl(fd,FBIO-PUTCMAP,&new_cmap)将设置新的调色板。
以下介绍如何显示一个象素,这里假设LCD为24位色的。


由此便可逐一显示每个象素,进而显示整幅图像。
5 结语
由于Linux的驱动模型支持模块堆叠技术,内核开发者已提供了一些通用模块,因此,虽然文中是以USB摄像头为例,但只要针对自己的图像采集设备编写基于Video4 Linux的驱动程序,针对自己的LCD编写基于FrameBuffer的驱动程序,以上的程序便可成为通用的图像采集与显示程序。应用本文所述方法完成图像采集与显示工作,再加上相关的处理并接入网络,就构成了一个智能终端设备,可用于工厂、银行等场合全天候的智能监控,图像的网络通信等,具有广阔的是市场和应用前景。
离线keisuo

只看该作者 12楼 发表于: 2007-04-26
Linux 下基于ARM920T 的USB 摄像头图像采集
                          王永清 何 波 王 乾 郭 磊
(中国海洋大学,山东青岛,266071)

摘要:随着USB摄像头的普及和基于ARM核的嵌入式芯片的快速发展,二者结合的便携性越来越受到人们欢迎,而嵌入式Linux的迅速发展更为二者的结合铺平了道路,本文介绍了基于ARM920T的嵌入式Linux下利用USB摄像头采集图像的硬件、软件设计过程,最终实现了在目标板上图像的采集和显示。
关键词:ARM;USB摄像头;Video for Linux;图像采集;嵌入式Linux
中图分类号:TP335;TP274  文献标识码:B
基金资助: 教外司留[2005]383号
Getting image data with USB camera based on Linux and ARM920T
Wang Yongqing, He Bo, Wang Qian, Guo Lei
(Ocean University of China, Qingdao, Shandong, China 266071)

Abstract:With widely and rapidly using of USB camera and ARM-based microcontroller, it is very popular that the USB camera is used with ARM processor. At the same time , the embedded Linux technology is boosting application jointing. Hardware and software of gathering image data via USB camera based on Linux and ARM920T is proposed in this paper. Finally, we successfully get the image data and display the image on the target board.

Keyword:ARM; USB camera; Video for Linux; Getting image data; embedded Linux
Chinese Library Classification:TP335;TP274  Document code:B

1、基于ARM920T的USB摄像头图像采集硬件平台















                                图(1) 硬件结构原理图
图(1)中各个主要模块基本组成描述如下:
① 微处理器(MPU):针对开发多媒体视频终端的需要,并考虑到系统外围设备的需求情况,本系统采用SAMSUNG公司内嵌ATM920T内核的三星S3C2410处理器。最高主频可达203MHz [1]。
② SDRAM存储部分采用两颗Hynix公司的HY57V561620CT内存,大小为32M。
③ FLASH存储器采用SAMSUNG公司的K9F1208UOM Nand Flash,大小为64M。
④ USB集线器芯片:采用ALCOR MICRO公司的AU9254A21,可扩展为4个USB外围接口,分别连接图(1)中所示的四个外围设备。
⑤ LCD:采用Sharp公司的3.5寸LCD,分辨率为240×320。

2、基于ARM920T的USB摄像头图像采集的软件系统
由于嵌入式Linux具有成本低、代码开放、移植性好的特点,其用于嵌入式系统的优势和发展潜力是不容置疑的。软件部分的搭建主要依赖于以下三个部分:
① Boatloader:可以从SAMSUNG公司的官方网站获取,经过交叉编译生成映像文件,然后通过JTAG接口将映像烧写到目标板,实现引导程序的装载[2]。
② Kernel:本系统采用Linux-2.4.18.tar.gz版本的内核。
③ 文件系统:由于本系统要进行动态的擦写FLASH,所以采用了支持此功能的YAFFS文件系统[3]。
Linux平台的驱动一般分为字符设备、块设备和网络设备三种类型。而在Linux下要使系统所挂接的外部设备正常工作,必须加载相应的驱动程序。Linux下对于一个硬件的驱动,可以有两种方式:一种是直接加载到系统的内核当中去,另一种是以模块方式进行加载,就是在编译内核的时候,同时生成可重定位的目标文件(.o文件)[4]。项目中所用的SBC2410X的实验板的USB主控器驱动程序模块为USB-OHCI-S3C2410.o。在Linux下要采集视频类数据,需要加载Video4Linux驱动模块Videodev.o。然后再加相应的摄像头驱动程序。在项目开发中,我们所使用的摄像头采用的USB控制器为ov511+,所对应的驱动程序模块为ov511.o。所以在系统启动时必须要通过如下命令:
>>insmod videodev.o
>>insmod usb-ohci-s3c2410.o
>>insmod ov511.o
来加载所需要的模块。通过开源项目spca5xx可以得到上边所需模块的全部源代码。上层软件部分我们参考了vidcat,vgrabber,w3cam,gqcam这几种软件的操作过程,重点参照了vidcat进行了V4L编程,使用了v4l.c和vidcat.c 中的函数,经过交叉编译,在实验板上实现了实时图像采集的目的。
    在图(2)中显示了各个模块之间的关系,其中从上到下的箭头流向表示通过各个模块启动和配置摄像头,从下到上的箭头流向表示由摄像头所采集的图像数据经各个模块采集到用户指定的位置。

3、在Linux下采集并显示USB摄像头数据
Linux下摄像头的驱动程序是以81为主设备号,在编写应用程序的时候,要通过打开一个具有该主设备号的设备文件来建立与设备驱动程序的通信,我们所使用的Linux没有该文件,所以需要手工创建,并建立其软连接,因为要对文件进行操作,所以要改变其访问权限为666。我们用到的videodev.o模块即为视频部分的标准Video for Linux (简称V4L)。这个标准定义了一套接口,内核、驱动、应用程序以这个接口为标准进行通信。

3.1图像数据的采集过程:
第一步:要打开摄像头设备,而在Linux下可以通过系统的设备文件来访问设备,在前面我们创建并建立了摄像头的设备文件,所以文件描述符(dev)可以如下方法获取:
    while (max_try) {
dev = open (device, O_RDWR);
        if (dev == -1) {
            if (!--max_try) {
                fprintf (stderr, "Can't open device %s\n", device);
                return (1);}  /*max_try为试图打开设备的最多次数*/
            sleep (1);
        } else { break; }}
第二步:进行访问摄像头设备的状态信息。
首先我们可以在kernel的源代码中找到头文件videodev.h,这个头文件定义了我们要编写的应用程序的所有数据结构和函数。当然我们先要获得摄像头的信息,可以通过头文件中的video_capability结构来了解摄像头的性能。其函数接口是int v4l_check_size (int fd, int *width, int *height),读出其中的单元可按如下方法,宏VIDIOCGCAP定义为 _IOR('v',1,struct video_capability)。
  struct video_capability vid_caps;
if (ioctl (fd, VIDIOCGCAP, &vid_caps) == -1) {
        perror ("ioctl (VIDIOCGCAP)"); return -1;}
然后通过访问结构体vid_caps就可以读出摄像头可拍摄的图片类型、图片的最大最小高度和宽度。
第三步:通过控制摄像头来采集图象数据。
实现函数为image = get_image (dev, width, height, palette, &size),通过该函数可以将设备文件中的图象数据的信息读出来,该函数的返回值image为图片要存储的格式,例如png、jpeg等。但这样必须首先申请一块足够大的内存空间,我们是这样完成的:
map = malloc (width * height * bytes);
        len = read (dev, map, width * height * bytes);
        if (len <=  0) { free (map); return (NULL); }
这样采集到的图像数据就会先存到所分配到的内存空间中,然后进行下一步的象素和图片格式存储处理。
第四步:按照预定的象素值和图片格式来存储图像。
在驱动程序向应用程序传递图像数据是一个拷贝过程,所以应用程序在采集图片数据时,先将驱动程序中图片缓冲区中的数据拷贝到应用程序中,然后再控制和处理图片数据。
if (palette == VIDEO_PALETTE_YUV420P) {
            convmap = malloc ( width * height * bytes );
            v4l_yuv420p2rgb (convmap, map, width, height, bytes * 8);
            memcpy (map, convmap, (size_t) width * height * bytes);
            free (convmap); }
这就是拷贝的过程,memcpy()为拷贝函数,v4l_yuv420p2rgb()函数用来将原生图片转换为RGB格式的图像信息。然后通过前边image的返回值来分别调用函数put_image_png,put_image_jpeg来生成相应格式的图像信息。

3.2 通过QT编译的图片查看器查看摄像头采集的图像。
QT目前是在嵌入式Linux领域中比较流行的图形开发工具,在我们的文件系统中,采用了基于QT的图形界面Qtopia,以下是实现调用图像信息的QT语句。第一句表示将图片的路径以及图片的名字传给pm1,然后通过Qlabel类的pl传出图片给图片查看器,从而实现了图像信息的显示。
QPixmap pm1("picture_path/picture_name");
Qlabel p1;
p1—>SetPixmap(pm1);了;‘

在采集的图像数据中,可以自定义所存储的图片格式,大小及其像素,方便迅速,而Qtopia是基于QT的比较成熟的嵌入式图形界面,利用其来显示我们所采集的图像数据效果良好。

4、结束语
本文详细介绍了基于ARM920T的嵌入式Linux下的USB摄像头图像采集的硬件、软件构建过程,可以灵活应用于基于嵌入式的各种电子产品中。由于所采用的软件全部是开放源码而且免费获得,所以对于需要便携好而又要有较高的数据处理能力且成本要求严格的方面尤其适合。
本文作者创新点:通用串行总线是一种非常实用的通信接口,其应用日益广泛,而Linux+ARM9下USB设备的应用也逐渐完善;并且采用了具有永久性存储功能的Yaffs文件系统,为客户进行实时处理图像提供了方便;同时使用QT编译的图像采集界面使图像数据的采集更加人性化,这几方面的结合使其必有很广的市场前景。

参考文献:
[1] SAMSUNG主页:http://www.samsungsemi.com/
[2]《基于ARM-Linux 嵌入式系统引导程序的设计》刘晶晶 《微计算机信息》2006第2-2期
[3]《构建嵌入式Linux系统》,Karim Yaghmour著,中国电力出版社
[4]《Linux设备驱动程序(第三版),Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman著,魏永明等译,中国电力出版社

作者简介:
1、    王永清(1980—),男,硕士生,主要研究方向:嵌入式技术。
Wang Yongqing,male,Master candidate,research interest:embedded technique.
2、    何 波(1971—),男,硕士生导师,副教授,主要研究方向:通信与信息系统;信号处理
He Bo,male,Master tutor,association professor,research interest:Communication and signal processing system.
3、    王 乾(1980—),男,硕士生,主要研究方向:嵌入式技术。
Wang Qian,male,Master candidate,research interest:embedded technique.
通信地址:青岛市香港东路23号,中国海洋大学信息学院302室(邮编:266071),
E-mail:qinwyo@gmail.com
离线keisuo

只看该作者 11楼 发表于: 2007-04-26
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 1 -
基于Video4Linux 的USB 摄像头图像采集实现
Write by daily3(戴小鼠) 著作权:戴丽(合肥工业大学)
(email:daily3@126.com)
做了一段时间的摄像头图像采集,有了一些心得。在论坛上开的2410摄像头
问题专贴(http://www.hhcn.com/cgi-bin/topic.cgi?forum=1&topic=247&show=0
也得到了大家的关注。在此,我将这一阶段遇到的问题,解决方法等做个总结,
希望对您有所帮助。
Linux本身自带了采用ov511芯片的摄像头,而市场上应用最广泛的是采用中
芯微公司生产的zc301芯片的摄像头,下面我将针对这两大系列的摄像头分别做
介绍。(注:所有的开发都是在华恒HHARM-2410-EDU上完成,ov511摄像头采
用的是网眼webeye3000,zc301摄像头采用的是ANC奥尼S888)。
一 驱动加载
1.1 ov511 驱动
1.静态加载
(1)在arm linux的kernel目录下make menuconfig。
(2)首先(*)选择Multimedia device->下的Video for linux。加载video4linux模块,
为视频采集设备提供了编程接口;
(3)然后在usb support->目录下(*)选择support for usb和usb camera ov511
support。这使得在内核中加入了对采用OV511接口芯片的USB数字摄像头的驱动
支持。
(4)保存配置退出。
(5)make dep;make zImage
此时在/tftpboot 下就生成了带有ov511 驱动的内核。
2.动态加载
(1)在arm linux的kernel目录下make menuconfig。
(2)首先<*>选择Multimedia device->下的Video for linux。
(3)然后在usb support->目录下<*>选择support for usb和<M>选择usb camera
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 2 -
ov511 support。
(4)保存退出。
(5)Make dep;make zImage;make modules然后就在/driver/usb下生成ov511.o,同
时生成的zImage自动放在/tftpboot下。
(6)然后用新内核启动板子后insmod ov511.o就可以成功加载。
动态方式与静态方式相比,测试时要简单的多。不需要下载整个内核,只需
通过nfs,加载驱动即可测试。在测试成功后就可以编译进内核。
模块加载中出现的问题:
1.insmod 和modprobe 间的一个区别试后者不会在当前目录中查找模块,它只
在/lib/modules 下的缺省目录下查找,这是因为该程序只是一个系统实用例程,
不是一个交互工具。可以通过在/etc/modules.conf 中指定自己的目录,来把它们
加到缺省目录集中。
2.如果插入模块ov511.o 时,出现以下信息:
Ov511.o:unresolved symbol video********之类的,说明还有其它模块videodev.o
没有加。
3.出现错误:ov511.o:couldn’t find the kernel version this modules was compiled
for。这是试图插入一个不是可装入模块的目标文件。因为在内核配置阶段,是
把ov511 模块静态加到内核中的,虽然看起来和可装入模块的文件名ov511.o 完
全一样,但是不能用insmod 命令加入。
4.如果出现Ov511.o:unresolved symbol video********,那就<M>选中video for
linux,用新生成的内核启动系统,再insmod videodev.o,insmod ov511.o 就可以啦。
1.2 zc301 驱动
摄像头的驱动是从http://mxhaard.free.fr/下的针对embeded环境,有专门的patch,
我用的是usb-2.4.31LE06.patch。
(1)把它放到/HHARM9-EDU/kernel/driver/usb下,解压,打补丁。就会在此目
录下看到spca5xx文件夹了。可能会有一些错误,我的错误是在Makefile和config.in
文件中,根据它的提示,进行相应的修改即可。Patch时会将修改方法写到
Makefile.rej和config.in.rej文件中,把这两个文件里的内容加到Makefile和config.in
中就行了。
(2)编译内核,进入/HHARM9-EDU/kernel,make menuconfig。我采用和上面
介绍的ov511驱动的方法一样,动态加载。(M)选中SPCA5XX这一项。
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 3 -
( 3 ) make dep ; make zImage ; make modules 。就会在
/HHARM9-EDU/kernel/driver/usb/spca5xx 中生成
spca5xx.o,spcadecoder.o,spca_core.o啦。这就是我们要的驱动。
(4)用新内核启动,insmod这三个.o文件(可以不用加载spcadecoder.o),摄像
头就加载成功啦。
不过这种LE的驱动有许多问题,比如运行到设置图像格式(RGB565或RGB24)时
出错, 说不支持此参数。原因在于: ( 摘自驱动程序主页
http://mxhaard.free.fr/spca5le.html)
The spca5xx-LE design is very different from the spca5xx full package(LE版的驱动
和完全版的差很多)。
The memory in use are the most smaller as possible(LE版的驱动会尽量减少内存的
使用)
The spcadecoder is reduce and only raw jpeg webcam are used.(驱动模块只支持输
出原始jpeg格式)。
还有一种方法, 从http://mxhaard.free.fr/download.html 下载最新的驱动
spca5xx-20060402.tar.gz。这个可独立编译,无需放到linux内核里面,编译生成一个spca5xx.o
即可,不要三个.o做驱动了。因为这个驱动是针对2.6的,编译时会出现很多错误,修改
CFLAGS即可。华恒的群里已经有编译好的驱动提供大家下载。
模块加载中出现的问题:
1.运行./servfox时出现Error Opening V4L interface.
我测试一下,是没有加载驱动。虽然内核中(M)选中了驱动,但是启动后要手
工加进去。insmod一下啦。
2.insmod spcadecoder.o时,出现错误:spcadecoder.o:couldn’t find the kernel version
this modules was compiled for。如果你insmod spca5xx.o成功的话就不需要再
insmod其他模块了。
3.insmod video.o时却说can't find the kernel version the modules was compiled for。
这是因为video for linux一般是直接编译到内核中去的.不需要加载的。
二 Video4linux 编程
2.1 Video4linux 简介
Video4Linux是为市场现在常见的电视捕获卡和并口及USB口的摄像头提供
统一的编程接口。同时也提供无线电通信和文字电视广播解码和垂直消隐的数据
接口。本文主要针对USB摄像头设备文件/dev/video0,进行视频图像采集方面的
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 4 -
程序设计。
2.2 Video4linux 编程指南
1.视频编程的流程
(1)打开视频设备:
(2)读取设备信息
(3)更改设备当前设置(可以不做)
(4)进行视频采集,两种方法:
a.内存映射
b.直接从设备读取
(5)对采集的视频进行处理
(6)关闭视频设备。
定义的数据结构及使用函数
struct _v4l_struct
{
int fd;
struct video_capability capability;
struct video_buffer buffer;
struct video_window window;
struct video_channel channel[8];
struct video_picture picture;
struct video_mmap mmap;
struct video_mbuf mbuf;
unsigned char *map;
};
typedef struct _v4l_struct v4l_device;
extern int v4l_open(char *, v4l_device *);
extern int v4l_close(v4l_device *);
extern int v4l_get_capability(v4l_device *);
extern int v4l_set_norm(v4l_device *, int);
extern int v4l_get_picture(v4l_device *);
extern int v4l_grab_init(v4l_device *, int, int);
extern int v4l_grab_frame(v4l_device *, int);
extern int v4l_grab_sync(v4l_device *);
extern int v4l_mmap_init(v4l_device *);
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 5 -
extern int v4l_get_mbuf(v4l_device *);
extern int v4l_get_picture(v4l_device *);
extern int v4l_grab_picture(v4l_device *, unsigned int);
extern int v4l_set_buffer(v4l_device *);
extern int v4l_get_buffer(v4l_device *);
extern int v4l_switch_channel(v4l_device *, int);
3.Video4linux支持的数据结构及其用途
(1)video_capability 包含设备的基本信息(设备名称、支持的最大最小分辨
率、信号源信息等)
name[32] 设备名称
maxwidth
maxheight
minwidth
minheight
Channels 信号源个数
type 是否能capture , 彩色还是黑白, 是否能裁剪等等。值如
VID_TYPE_CAPTURE等
(2)video_picture 设备采集的图象的各种属性
Brightness 0~65535
hue
colour
contrast
whiteness
depth 8 16 24 32
palette VIDEO_PALETTE_RGB24 | VIDEO_PALETTE_RGB565|
VIDEO_PALETTE_JPEG| VIDEO_PALETTE_RGB32
(3)video_channel 关于各个信号源的属性
Channel 信号源的编号
name
tuners
Type VIDEO_TYPE_TV | IDEO_TYPE_CAMERA
Norm 制式 PAL|NSTC|SECAM|AUTO
(4)video_window 包含关于capture area的信息
x x windows 中的坐标.
y y windows 中的坐标.
width The width of the image capture.
height The height of the image capture.
chromakey A host order RGB32 value for the chroma key.
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 6 -
flags Additional capture flags.
clips A list of clipping rectangles. (Set only)
clipcount The number of clipping rectangles. (Set only)
(5)video_mbuf 利用mmap进行映射的帧的信息
size 每帧大小
Frames 最多支持的帧数
Offsets 每帧相对基址的偏移
(6)video_mmap 用于mmap
4.关键步骤介绍
【注】接多个摄像头。方法如下:买一个usb hub接到开发板的usb host上。cat
/proc/devices可以知道video capture device的major是81,再ls –l /dev看到video0
的次设备号是0。两个摄像头当然要两个设备号,所以mknod /dev/video1 c 81 1,
如果接4个,就mknod /dev/video2 c 81 2,mknod /dev/video3 c 81 3。依次类推。
(1)打开视频:
int v4l_open(char *dev, v4l_device *vd)
{
if (!dev)
dev = ”/dev/video0”;
if ((vd ->fd = open(dev, O_RDWR)) < 0) {
perror("v4l_open:");
return -1;
}
if (v4l_get_capability(vd))
return -1;
if (v4l_get_picture(vd))
retu rn -1;
return 0;
}
(2)读video_capability 中信息
int v4l_get_capability(v4l_device *vd)
{
if (ioctl(vd ->fd, VIDIOCGCAP, &(vd->capability)) < 0) {
perror("v4l_get_capability:");
return -1;
}
return 0;
}
成功后可读取vd->capability各分量
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 7 -
(3)读video_picture中信息
int v4l_get_picture(v4l_device *vd)
{
if (ioctl(vd ->fd, VIDIOCGPICT, &(vd->picture)) < 0) {
perror("v4l_get_picture:");
return -1;
}
return 0;
}
成功后可读取图像的属性
(4)改变video_picture中分量的值 (可以不做的)
先为分量赋新值,再调用VIDIOCSPICT
vd->picture.colour = 65535;
if(ioctl(vd->fd, VIDIOCSPICT, &(vd->picture)) < 0)
{
perror("VIDIOCSPICT");
return -1;
}
(5)初始化channel (可以不做的)
必须先做得到vd->capability中的信息
int v4l_get_channels(v4l_device *vd)
{
int i;
for (i = 0; i < vd ->capability.channels; i++) {
vd ->channel.channel = i;
if (ioctl(vd ->fd, VIDIOCGCHAN, &(vd->channel)) < 0) {
perror("v4l_get_channel:");
return -1;
}
}
return 0;
}
(6)关闭设备
int v4l_close(v4l_device *vd)
{
close(vd ->fd);
return 0;
}
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 8 -
重点:截取图象的两种方法
一、用mmap(内存映射)方式截取视频
mmap( )系统调用使得进程之间通过映射同一个普通文件实现共享内存。普
通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访
问,不必再调用read(),write()等操作。两个不同进程A、B共享内存的意思是,
同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进
程B对共享内存中数据的更新,反之亦然。
采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写
内存,而不需要任何数据的拷贝
(1)设置picture的属性
(2) 初始化video_mbuf,以得到所映射的buffer的信息
ioctl(vd->fd, VIDIOCGMBUF, &(vd->mbuf))
(3)可以修改video_mmap和帧状态的当前设置
(4)将mmap与video_mbuf绑定
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
len:映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始
算起
Prot:指定共享内存的访问权限 PROT_READ(可 读), PROT_WRITE (可写),
PROT_EXEC (可执行)
Flags:MAP_SHARED MAP_PRIVATE中必选一个,MAP_ FIXED不推荐使用
Addr:共内存享的起始地址,一般设0,表示由系统分配
Mmap( ) 返回值是系统实际分配的起始地址
int v4l_mmap_init(v4l_device *vd)
{
if (v4l_get_mbuf(vd) < 0)
return -1;
if ((vd ->map = mmap(0, vd->mbuf.size, PROT_READ|PROT_WRITE,
MAP_SHARED, vd->fd, 0)) < 0) {
perror("v4 l_mmap_init:mmap");
return -1;
}
return 0;
}
(5)Mmap方式下真正做视频截取的 VIDIOCMCAPTURE
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 9 -
ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) ;
若调用成功,开始一帧的截取,是非阻塞的,
是否截取完毕留给VIDIOCSYNC来判断
(6)调用VIDIOCSYNC等待一帧截取结束
if(ioctl(vd->fd, VIDIOCSYNC, &frame) < 0)
{
perror("v4l_sync:VIDIOCSYNC");
return -1;
}
若成功,表明一帧截取已完成。可以开始做下一次 VIDIOCMCAPTURE
frame是当前截取的帧的序号。
********关于双缓冲************
video_bmuf bmuf.frames = 2;一帧被处理时可以采集另一帧
int frame; //当前采集的是哪一帧
int framestat[2]; //帧的状态 没开始采集|等待采集结束
帧的地址由vd->map + vd->mbuf.offsets[vd->frame]得到。
采集工作结束后调用munmap取消绑定
munmap(vd->map, vd->mbuf.size)
在实际应用时还可以采用缓冲队列等方式。
二、视频截取的第二种方法:直接读设备
关于缓冲大小,图象等的属性须由使用者事先设置
调用read();
int read (要访问的文件描述符;指向要读写的信息的指针;应该读写的字符数);
返回值为实际读写的字符数
int len ;
unsigned char
*vd->map=
(unsigned char *) malloc(vd◊capability.maxwidth*vd◊capability.maxheight );
len = read(vd◊fd,vd◊ vd->map,
vd◊capability.maxwidth*vd◊capability.maxheight*3 );
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 10 -
2.3 编程实例(mouse_capture)
不管是ov511还是zc301的摄像头,它们采集的方式都是相同的,只不过采集
到的数据有所差异,ov511的就是rgb的位流,而zc301是jpeg编码的位流。
mouse_capture是根据servfox改编的一个专门从zc301摄像头获取一张jpeg图片,
用来测试摄像头是否加载成功的小程序。这样就可以不用cat /dev/video0>1.jpg来
测试摄像头是否正常。cat命令一运行,就源源不断地采集jpeg流。但是采到的图
片只能显示第一个jpeg头和jpeg尾之间的数据。mouse_capture仅仅获得一张完整
的jpeg。 可以从
http://www.hhcn.com/cgi-bin/topic.cgi?forum=1&topic=247&start=144&show=0
处下载参考。
现将主要函数的功能介绍如下:
static int GetVideoPict (struct vdIn *vd);//获取图片属性信息。
static int SetVideoPict (struct vdIn *vd);//设置图片属性。
static int isSpcaChip (const char *BridgeName);//测试芯片类型
static int GetStreamId (const char *BridgeName); //测试输出数据的格式
static int GetDepth (int format);//获取颜色深度。
void exit_fatal(char *messages);//错误显示。
int init_videoIn(struct vdIn *vd,char *device,int width,int height,int format,int
grabmethod);//初始化设备。
int convertframe(unsigned char *dst,unsigned char *src, int width,int height, int
formatIn, int size);//把共享缓冲区中的数据放到一个变量中,通知系统已获得一
帧。
int v4lGrab (struct vdIn *vd,char *filename );//从摄像头采集图片。
int close_v4l (struct vdIn *vd);//关闭摄像头。
int get_jpegsize (unsigned char *buf, int insize);//获取jpeg图片大小。
三 实例程序
3.1 LCD 实时显示从ov511 上采集的图像
参考HHARM9-EDU/applications/usbcam2lcd。从摄像头获取bmp位流直接显示
在framebuffer中。此程序图像的采集采用read的方式,注意由于lcd液晶屏显示的
是16bits的RGB图片,所以,ov511输出的图片格式也应该是16bits的RGB图片数
据,宏VIDEO_PALETTE_RGB565定义的就是16bits的RGB数据图片。而linux自
带的ov511驱动中图像采集是32位的,这样采集到的图片显示在lcd上是雪花点。
因此需要修改驱动。 在kernet/driver/usb/目录下有ov511芯片的驱动ov511.c,驱
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 11 -
动里的ov51x_set_default_params函数是设置芯片默认的输出图片的格式,该函数
中的
for (i = 0; i < OV511_NUMFRAMES; i++)
{
ov511->frame.width = ov511->maxwidth;
ov511->frame.height = ov511->maxheight;
ov511->frame.bytes_read = 0;
if (force_palette)
ov511->frame.format = force_palette;
else
ov511->frame.format = VIDEO_PALETTE_RGB24;
ov511->frame.depth = ov511_get_depth(ov511->frame.format);
}
部分语句是主要设置ov511默认输出图片格式的,其中maxwidth和maxheight
设置了图片的最大的宽度和高度。Ifelse语句设置了图片的格式,作如下的修改:
for (i = 0; i < OV511_NUMFRAMES; i++)
{
ov511->frame.width = ov511->maxwidth;
ov511->frame.height = ov511->maxheight;
ov511->frame.bytes_read = 0;
ov511->frame.format = VIDEO_PALETTE_RGB565;
ov511->frame.depth = ov511_get_depth(ov511->frame.format);
}
如果需要,也可以改变图片的默认输出大小。
3.2 LCD 实时显示从zc301 上采集的图像
编程思想:从摄像头采集到的图片存放在本地文件夹,通过minigui加载jpeg
来实现显示。
具体过程:
1.从网上下载jpegsrc-6b的jpeg库,交叉编译。
(1)./configure –enable-static –enable-shared –prefix=.libs
(2)修改Makefile,将编译器改成交叉编译器。
例如:我改成/opt/host/armv4l/bin/armv4l-unknown-linux-gcc
(3)make 后即在.libs目录中生成for arm的
libjpeg.a, libjpeg.la,libjpeg.so,libjpeg.so.62,libjpeg.so.62.0.0。将这些文件拷贝到系
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 12 -
统库文件目录,我的是/usr/lib中。
2.因为看从zc301采集的图片的二进制位流,jpeg头是ff d8 ff db。而在minigui库
文件libminigui的源文件src/mybmp/jpeg.c中,load_jpg和check_jpg的时候测试的头
位EXIF和JFIF两种格式的jpeg图片。这两种对应的二进制分别是ff d8 ff e1和ff d8
ff e0。所以我们minigui通过判断认为这是错误的jpeg格式而不加载,故无法显示。
实际上通过测试,在源码中去掉这两个判断就能正确加载。
3.交叉编译minigui
( 1 ) 编译库: ./configure --host=arm-unknown-linux --enable-jpgsupport=yes
--enable-pngsupport=no --enable-gifsupport=no --disable-lite
--prefix=/HHARM9-EDU/applications/minigui-free/nfsroot
--enable-smdk2410ial=yes
make
make install
(2)编译实例程序时,要加上jpeg库的支持,即在Makefile中加上-ljpeg。此时
将在nfsroot生成的库文件和可执行文件移到ramdisk.image.gz相应的目录下。(具
体参考华恒的2410开发手册)。
3.Minigui程序的编写
编程小技巧,我采取的方法是一刻不停地从摄像头采集到图片存储在
/tmp/1.jpg中,在minigui中通过loadbitmap函数来加载图片。而图片加载后不会自
动更新,不能自动根据1.jpg的改变自动变化。因此,我在程序中设定一个timer。
每隔100ms刷新屏幕,基本上实现实时更新了。而出现另外一个问题,刷新时会
以背景色来填充桌面,导致屏幕闪烁严重。故想到采用MSG_ERASEBKGND的
方式,用前一张图片做为刷新屏幕时的填充背景图片。这样就保证了lcd上图像
的连续性啦。
Minigui程序如下:其中一些自定义的函数跟mouse_capture中的一样,只是
变采集单幅到采集多幅。具体您可以自己改一下:)。也可以向我索取源码。
#include <minigui/common.h>
#include <minigui/minigui.h>
#include <minigui/gdi.h>
#include <minigui/window.h>
#include <minigui/control.h>
#include "spcav4l.h"
#define IDTIMER 100
static BITMAP bmp;
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 13 -
static int LoadBmpWinProc(HWND hWnd, int message, WPARAM wParam,
LPARAM lParam)
{
HDC hdc;
RECT rc={0,0,240,320};
switch (message) {
case MSG_CREATE:
SetTimer(hWnd,IDTIMER,100);
return 0;
case MSG_ERASEBKGND:
{
RECT rcTemp;
if( LoadBitmap(HDC_SCREEN,&bmp,"/tmp/1.jpg"))
{
printf("load wrong!\n");
return -1;
}
GetClientRect(hWnd, &rcTemp);
hdc = BeginPaint (hWnd);
FillBoxWithBitmap (hdc, rcTemp.left, rcTemp.top, rcTemp.right-rcTemp.left,
rcTemp.bottom-rcTemp.top, &bmp);
EndPaint(hWnd, hdc);
return 0;
}
case MSG_TIMER:
InvalidateRect(hWnd,&rc,TRUE);
return 0;
case MSG_CLOSE:
UnloadBitmap (&bmp);
DestroyMainWindow (hWnd);
PostQuitMessage (hWnd);
return 0;
}
return DefaultMainWinProc(hWnd, message, wParam, lParam);
}
int MiniGUIMain (int argc, const char* argv[])
{
MSG Msg;
HWND hMainWnd;
MAINWINCREATE CreateInfo;
char videodevice[] = "/dev/video0";
char jpegfile[] = "/tmp/1.jpg";
int grabmethod = 0;
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 14 -
int format = VIDEO_PALETTE_JPEG;
int width = 240;
int height = 320;
int i;
#ifdef _LITE_VERSION
SetDesktopRect(0, 0, 1024, 768);
#endif
CreateInfo.dwStyle = WS_VISIBLE | WS_BORDER | WS_CAPTION;
CreateInfo.dwExStyle = WS_EX_NONE;
CreateInfo.spCaption = "Load and display a bitmap";
CreateInfo.hMenu = 0;
CreateInfo.hCursor = GetSystemCursor(0);
CreateInfo.hIcon = 0;
CreateInfo.MainWindowProc = LoadBmpWinProc;
CreateInfo.lx = 0;
CreateInfo.ty = 0;
CreateInfo.rx = 240;
CreateInfo.by = 320;
CreateInfo.iBkColor = PIXEL_lightwhite;
CreateInfo.dwAddData = 0;
CreateInfo.hHosting = HWND_DESKTOP;
hMainWnd = CreateMainWindow (&CreateInfo);
if (hMainWnd == HWND_INVALID)
return -1;
ShowWindow (hMainWnd, SW_SHOWNORMAL);
memset(&videoIn, 0, sizeof (struct vdIn));
if(init_videoIn(&videoIn, videodevice, width, height, format,grabmethod) == 0)
{
printf("init is ok!\n");
}
else printf("init is wrong!\n");
while (GetMessage(&Msg, hMainWnd)) {
TranslateMessage(&Msg);
v4lGrab(&videoIn, jpegfile);
DispatchMessage(&Msg);
}
close_v4l (&videoIn);
MainWindowThreadCleanup (hMainWnd);
return 0;
}
#ifndef _LITE_VERSION
#include <minigui/dti.c>
#endif
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 15 -
先写到这里吧,呵呵,希望能对您有所帮助。如果您在阅读的过程中发现问
题,欢迎和我交流。
2006-7-7 晚
参考文献
1.HHARM2410摄像头调试记录 华恒科技
2.基于video4linux的视频设备编程 Lingzhi_Shi Apr 7 2004
3.《video4linux programming》 Alan Cox
4.《video streaming 探讨》 陈俊宏
5.《Video4Linux Kernel API Reference 》
6.http://www.hhcn.com/cgi-bin/topic.cgi?forum=1&topic=247&show=0
离线keisuo

只看该作者 10楼 发表于: 2007-04-26
    基于S3C2410平台与嵌入式Linux的图像采集应用
Application of Image Gather Based on the S3C2410 Platform And Embedded Linux
        北京邮电大学自动化学院    李侃  廖启征 
        BeiJing University Of Posts And Telecommunications ,Automation School ,  Likan  Liaoqizheng.

摘要:本文首先简介基于嵌入式Linux系统的S3C2410平台和在平台上进行开发所需的软件环境,接着论述在该平台上如何实现图像采集这一应用,并对图像采集程序的实现进行具体的介绍,最后完成JPEG压缩的实现。

关键词: 嵌入式Linux ,图像采集,Video4Linux, JPEG压缩

Abstract : This article introduce the S3C2410 platform which is based on the Embedded linux  ,and the develop software environment on the platform  firstly. And then describe how to achieve the image gathering application  on  the platform  and introduce detailedly the program of image gather. Finally, we accomplish the JPEG compression achievement.

Keyword:  Embedded Linux , Image Gather , Video4Linux,  JPEG Compression

引言:随着嵌入式技术的迅猛发展和linux在信息行业中广泛应用的,利用嵌入式linux系统实现远程监控、图像采集已成为可能。为了实现这些应用,实时获得图像数据是一个重要环节。针对这一点,本文在基于嵌入式Linux系统平台上,利用linux支持的以Omnivision公司ov511为控制芯片的USB摄像头采集数据,并利用Video4Linux内核应用编程接口函数,实现了单帧图像的采集,并保存成文件的形式供进一步处理和网络传输使用。

1.系统平台上的硬件系统与软件系统

    本文使用的系统硬件平台采用Samsung公司的处理器S3C2410。该处理器内部集成了ARM公司ARM920T处理器核的32位微控制器,资源丰富,带独立的16KB的指令Cache和16KB数据Cache、LCD控制器、RAM控制器、NAND闪存控制器、3路UART、4路DMA、4路带PWM的Timer、并行I/O口、8路10位ADC、Touch Screen接口、I2C接口、I2S接口、2个USB接口控制器、2路SPI,主频最高可达203MHz。在处理器丰富资源的基础上,还进行了相关的配置和扩展,平台配置了8MB 16位的Flash和16MB 32位的SDRAM。通过以太网控制器芯片DM9000E扩展了一个网口,另外引出了一个HOST USB接口。通过在USB接口上外接一个带USB口的摄像头,将采集到的视频图像数据放入输入缓冲区中,然后通过应用程序对图像数据进行处理。
平台的软件系统采用uClinux操作系统,它是源码开放的嵌入式操作系统,支持多任务,具有完备的TCP/IP协议栈并支持多种文件系统。另外,uClinux的可移植性和方便的内核定制也为其在路由器,网络摄像机,机顶盒等诸多领域的应用提供了良好的基础。

2 .开发环境的建立
2.1 uClinux内核定制
由于采用USB摄像头进行数据采集,首先要重新编译uClinux内核,使其支持USB总线和ov511摄像头。OV511 是一个专用的数字摄像IC 到 USB 的接口芯片,内含数字摄像IC接口、DRAM 接口、实时图像压缩引擎、USB 接口、I2C 接口、FIFO 等功能,用于将摄取的数字视频图像直接通过USB 接口送入开发板进行处理。所以应该确保该OV511 接口芯片在Linux 下的正常驱动,即能够支持该主机的USB 控制器UHCI 或OHCI,以及内核中Video4Linux 的支持。

在定制内核时,选中
[* ] Video for Linux
[* ] Support  for  USB
[* ] UHCI
[* ] OHCI
[* ]USB OV511 Camera Support
后编译uClinux内核,并将新内核替换原内核,这样在硬件平台上,一个支持USB摄像头的uClinux操作系统为视频采集提供了基础。
2.2 交叉开发环境的建立
由于嵌入式系统本身的局限性,使其没有足够资源运行开发工具和编译工具,所以嵌入式系统开发通常采用交叉编译调试的方式。交叉编译调试环境建立在宿主机上,而系统平台叫做目标板(即嵌入式S3C2410系统)。
通常宿主机和目标板上的处理器不同,宿主机通常为Intel处理器,而目标板如图1所示为SAMSUNG S3C2410,所以程序需要使用针对处理器特点的编译器才能生成在相应平台上可运行的代码。GNU编译器提供这样的功能,在编译时,可以选择开发所需的宿主机和目标机,从而建立开发环境。在进行嵌入式开发前的第一步工作就是把一台PC机作为宿主机开发机,并在其上安装指定的操作系统。对于嵌入式Linux,宿主机PC上应安装Linux系统。本系统采用arm-linux进行交叉编译,生成针对S3C2410处理器可执行的二进制代码,并进行调试和移植。

3 .图像采集的具体实现

3.1 摄像头驱动与V4L简介
在Linux下,设备驱动程序可以看成Linux内核与外部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件实现了的细节,使得应用程序可以像操作普通文件一样来操作外部设备,可以使用和操作文件中相同的、标准的系统调用接口函数来完成对硬件设备的打开、关闭、读写和I/O控制操作,而驱动程序的主要任务也就是要实现这些系统调用函数。本系统采用linux支持的以OV511为控制芯片的USB摄像头,因此系统启动后应自动识别USB设备,并将该设备模块加载到内核,在设备目录下出现/dev/video0设备,可直接利用系统调用获取设备信息和数据,为应用程序的开发提供了方便。
对于USB口摄像头,其驱动程序中需要提供基本的I/O操作接口函数open、read、write、close的实现。对中断的处理实现,内存映射功能以及对I/O通道的控制接口函数ioct1的实现等,并把它们定义在struct file_operations中。这样当应用程序对设备文件进行诸如open、close、read、write等系统调用操作时,Linux内核将通过file_operations结构访问驱动程序提供的函数。
  Video4Linux(简V4L)是Linux中关于视频设备的内核驱动,它为针对视频设备的应用程序编程提供一系列接口函数,这些视频设备包括现今市场上流行的TV卡、视频捕捉卡和USB摄像头等。Linux 内核提供Video4Linux 应用程序接口,在程序开发时,首先是基于Video4Linux API 函数来设计程序。
3.2 图像采集实现
采集图像数据程序中需要各种图像数据参数,而Video4Linux中一个重要的数据结构video_device提供了视频设备的信息,该结构如下:
typedef struct{
int fd;
int use_mmap;
int width, height;
int frame_rate;
int frame_size;
struct video_capability capability;
struct video_buffer buffer;
struct video_window vwin;
struct video_picture picture;
struct video_mmap vmmap;
struct video_mbuf vmbuf;
unsigned char *frame_buffer;
int dev;
} video_device;

其中video_capability,video_picture,video_buffer等数据结构包含设备名称、支持的最大最小分辨率、信号源、亮度、色度、对比度、深度、帧的大小、最多支持的帧数等基本信息,而video_mmap和vdieo_mbuf用于内存映射。
根据此数据结构,我们可以编写获取设备信息的函数:
int Cmera_open(char *dev, video_device *vd) 负责打开设备,并获得video_device结构中分拣描述符fd的值,以便后续函数对设备进行操作。
int Cmera_capability(video_device *vd);  获取设备信息
int Cmera_picture(video_device *vd);  获取图像信息
获取基本信息后,可以输出显示,也可以通过ioct1系统调用对这些值进行修改。然后调用函数Char * Carmera_get_image(video_device *vd) 获得图像数据指针。此函数比较重要,关系到图像采集的效率。获取图像数据有两种方法,一是通过read()系统调用直接读取设备中图像数据,二是通过mmap方式将设备文件映射到内存,这样绕过了I/O访问,使得读取速度更快,但也同时占用更多的系统资源。下面分别介绍:
1)read系统调用
此方式比较简单,只需将前面得到摄像头参数传入read函数中,得到图像数据指针picture_p后返回即可:  read(vd->fd, picture_p ,width*hight);其中vd->fd为设备文件描述符。
2)利用mmap方式
先使用ioct1(vd->fd,VIDIOCGMBUF,&vmbuf)函数获得摄像头存储缓冲区的帧信息,之后修改voideo_mmap中的设置,例如重新设置图像帧的垂直及水平分辨率、彩色显示格式。      接着把摄像头对应的设备文件映射到内存区,具体使用(unsigned char*)mmap(0,vmbuf.size,
PROT_READ|PROT_WRITE,MAP_SHARED,vd->fd,0)操作。这样设备文件的内容就映射到内存区,该映射内容区可读可写并且不同进程间可共享。该函数成功时返回图像数据的指针,失败时返回值为-1。
视频采集程序流程图如图1所示
     

4 .对图像数据进行压缩
    通过以上采集程序获得的图像数据为原始数据,根据前面获得的video_picture信息可以确定图像输出格式。根据图像格式我们可以将图像信息存储成文件通过网络用UDP封装传输到服务器端定时刷新显示,以形成较为连续的图像。以RGB24格式为例,可将文件存储成BMP格式或PPM格式,存为PPM格式的部分代码如下(设图象分辨率为640*480)
    fp = fopen("image.ppm", "w");
    fprintf(fp, "P6\n%d %d\n255\n", 640, 480);
    fwrite(img, 480, 3*640, fp);
    fclose(fp);
    其中fwrtie中640*3是由于RGB24使用3byte表示一个像素。
但由于存为ppm或BMP图像格式对图像不经过压缩,所以图像较大,不便于在网络上传输,这就要求图像采集平台对原始信息进行压缩处理,我们采用JPEG压缩方式。
4.1 JPEG简介及jpeglib安装
JPEG 静止图像压缩标准是一种广泛认可的标准,一般操作系统和应用都支持。JPEG 标准是在变换编码的基础上,综合应用了DCT 和哈夫曼编码两种手段达到了很好的图像压缩效果。基于离散余弦变换(DCT)的编码方法,是JPEG 算法的核心内容。
uClinux中没有jpeg函数库,需要下载jpegsrc.v6b.tar.gz后,在/usr/src解压安装:
cd jpeg-6b
./configure
Make
Make install
这样,uCLinux就支持jpeg函数库了,注意用编译器编译连接时带有 –ljpeg选项才能生效。
4.2 JPEG压缩具体实现
为实现图像压缩编写如下函数:
int write_jpeg(char *filename, char *img, int width, int height,int quality, int gray)
调用该函数时需传入六个参数,第一个参数是图像文件名称,第二参数是采集到的原始图像数据,第三、四个参数确定图像的长宽大小,第五个参数用于设定JEPG 图像的压缩质量,第六个参数确定是否为彩色图。部分代码如下
  jcfg.image_width = width;
  jcfg.image_height = height;
  jcfg.input_components = gray ? 1: 3;
jcfg.in_color_space = gray ? JCS_GRAYSCALE: JCS_RGB;
  jpeg_set_defaults(&jcfg);
  jpeg_stdio_dest(&jcfg, fp);
  jpeg_set_quality(&jcfg, quality, TRUE);
  jpeg_start_compress(&jcfg, TRUE);
  line_length = gray ? width : width * 3;
  line = (unsigned char *)img;
  for (i = 0; i < height; i++, line += line_length)
      jpeg_write_scanlines(&jcfg, &line, 1);
  jpeg_finish_compress(&jcfg);
  jpeg_destroy_compress(&jcfg);
  在具体实现时,服务器端可根据网络情况,通过设置图像压缩质量来保证图像的连续性。
5 .结束语
    由于嵌入式系统能保证系统响应的实时性和运行的可靠性,目前广泛应用在各个领域。本文设计的视频采集系统可以做为简单的安全监控,远程操作等应用。笔者的开发实例表明,嵌入式linux系统在图像采集及处理方面,不但在开发过程中简捷高效,而且在现场应用中也具有灵活多变的优势,在机器人监控系统,视频电话,远程教学等应用中有广阔的发展空间。


参考文献:
[1] Alessandro Rubini,  Linux 设备驱动程序 第二版 中国电力出版社 2002。
[2] 博嘉科技,  Linux 内核分析与实例应用, 国防工业出版社, 2002.
[3] David S.Taubman,  JPEG2000图像压缩基础、标准和实践, 电子工业出版社,2004
[4] 许宏松 ,  Linux 应用程序开发指南,  机械工业出版社, 2000.
离线keisuo

只看该作者 9楼 发表于: 2007-04-26
Linux 下基于ARM920T 的USB 摄像头图像采集
                          王永清 何 波 王 乾 郭 磊
(中国海洋大学,山东青岛,266071)

摘要:随着USB摄像头的普及和基于ARM核的嵌入式芯片的快速发展,二者结合的便携性越来越受到人们欢迎,而嵌入式Linux的迅速发展更为二者的结合铺平了道路,本文介绍了基于ARM920T的嵌入式Linux下利用USB摄像头采集图像的硬件、软件设计过程,最终实现了在目标板上图像的采集和显示。
关键词:ARM;USB摄像头;Video for Linux;图像采集;嵌入式Linux
中图分类号:TP335;TP274  文献标识码:B
基金资助: 教外司留[2005]383号
1、基于ARM920T的USB摄像头图像采集硬件平台















                                图(1) 硬件结构原理图
图(1)中各个主要模块基本组成描述如下:
① 微处理器(MPU):针对开发多媒体视频终端的需要,并考虑到系统外围设备的需求情况,本系统采用SAMSUNG公司内嵌ATM920T内核的三星S3C2410处理器。最高主频可达203MHz [1]。
② SDRAM存储部分采用两颗Hynix公司的HY57V561620CT内存,大小为32M。
③ FLASH存储器采用SAMSUNG公司的K9F1208UOM Nand Flash,大小为64M。
④ USB集线器芯片:采用ALCOR MICRO公司的AU9254A21,可扩展为4个USB外围接口,分别连接图(1)中所示的四个外围设备。
⑤ LCD:采用Sharp公司的3.5寸LCD,分辨率为240×320。
2、基于ARM920T的USB摄像头图像采集的软件系统
由于嵌入式Linux具有成本低、代码开放、移植性好的特点,其用于嵌入式系统的优势和发展潜力是不容置疑的。软件部分的搭建主要依赖于以下三个部分:
① Boatloader:可以从SAMSUNG公司的官方网站获取,经过交叉编译生成映像文件,然后通过JTAG接口将映像烧写到目标板,实现引导程序的装载[2]。
② Kernel:本系统采用Linux-2.4.18.tar.gz版本的内核。
③ 文件系统:由于本系统要进行动态的擦写FLASH,所以采用了支持此功能的YAFFS文件系统[3]。
Linux平台的驱动一般分为字符设备、块设备和网络设备三种类型。而在Linux下要使系统所挂接的外部设备正常工作,必须加载相应的驱动程序。Linux下对于一个硬件的驱动,可以有两种方式:一种是直接加载到系统的内核当中去,另一种是以模块方式进行加载,就是在编译内核的时候,同时生成可重定位的目标文件(.o文件)[4]。项目中所用的SBC2410X的实验板的USB主控器驱动程序模块为USB-OHCI-S3C2410.o。在Linux下要采集视频类数据,需要加载Video4Linux驱动模块Videodev.o。然后再加相应的摄像头驱动程序。在项目开发中,我们所使用的摄像头采用的USB控制器为ov511+,所对应的驱动程序模块为ov511.o。所以在系统启动时必须要通过如下命令:
>>insmod videodev.o
>>insmod usb-ohci-s3c2410.o
>>insmod ov511.o
来加载所需要的模块。通过开源项目spca5xx可以得到上边所需模块的全部源代码。上层软件部分我们参考了vidcat,vgrabber,w3cam,gqcam这几种软件的操作过程,重点参照了vidcat进行了V4L编程,使用了v4l.c和vidcat.c 中的函数,经过交叉编译,在实验板上实现了实时图像采集的目的。
    在图(2)中显示了各个模块之间的关系,其中从上到下的箭头流向表示通过各个模块启动和配置摄像头,从下到上的箭头流向表示由摄像头所采集的图像数据经各个模块采集到用户指定的位置。
3、在Linux下采集并显示USB摄像头数据
Linux下摄像头的驱动程序是以81为主设备号,在编写应用程序的时候,要通过打开一个具有该主设备号的设备文件来建立与设备驱动程序的通信,我们所使用的Linux没有该文件,所以需要手工创建,并建立其软连接,因为要对文件进行操作,所以要改变其访问权限为666。我们用到的videodev.o模块即为视频部分的标准Video for Linux (简称V4L)。这个标准定义了一套接口,内核、驱动、应用程序以这个接口为标准进行通信。
3.1图像数据的采集过程:
第一步:要打开摄像头设备,而在Linux下可以通过系统的设备文件来访问设备,在前面我们创建并建立了摄像头的设备文件,所以文件描述符(dev)可以如下方法获取:
    while (max_try) {
dev = open (device, O_RDWR);
        if (dev == -1) {
            if (!--max_try) {
                fprintf (stderr, "Can't open device %s\n", device);
                return (1);}  /*max_try为试图打开设备的最多次数*/
            sleep (1);
        } else { break; }}
第二步:进行访问摄像头设备的状态信息。
首先我们可以在kernel的源代码中找到头文件videodev.h,这个头文件定义了我们要编写的应用程序的所有数据结构和函数。当然我们先要获得摄像头的信息,可以通过头文件中的video_capability结构来了解摄像头的性能。其函数接口是int v4l_check_size (int fd, int *width, int *height),读出其中的单元可按如下方法,宏VIDIOCGCAP定义为 _IOR('v',1,struct video_capability)。
  struct video_capability vid_caps;
if (ioctl (fd, VIDIOCGCAP, &vid_caps) == -1) {
        perror ("ioctl (VIDIOCGCAP)"); return -1;}
然后通过访问结构体vid_caps就可以读出摄像头可拍摄的图片类型、图片的最大最小高度和宽度。
第三步:通过控制摄像头来采集图象数据。
实现函数为image = get_image (dev, width, height, palette, &size),通过该函数可以将设备文件中的图象数据的信息读出来,该函数的返回值image为图片要存储的格式,例如png、jpeg等。但这样必须首先申请一块足够大的内存空间,我们是这样完成的:
map = malloc (width * height * bytes);
        len = read (dev, map, width * height * bytes);
        if (len <=  0) { free (map); return (NULL); }
这样采集到的图像数据就会先存到所分配到的内存空间中,然后进行下一步的象素和图片格式存储处理。
第四步:按照预定的象素值和图片格式来存储图像。
在驱动程序向应用程序传递图像数据是一个拷贝过程,所以应用程序在采集图片数据时,先将驱动程序中图片缓冲区中的数据拷贝到应用程序中,然后再控制和处理图片数据。
if (palette == VIDEO_PALETTE_YUV420P) {
            convmap = malloc ( width * height * bytes );
            v4l_yuv420p2rgb (convmap, map, width, height, bytes * 8);
            memcpy (map, convmap, (size_t) width * height * bytes);
            free (convmap); }
这就是拷贝的过程,memcpy()为拷贝函数,v4l_yuv420p2rgb()函数用来将原生图片转换为RGB格式的图像信息。然后通过前边image的返回值来分别调用函数put_image_png,put_image_jpeg来生成相应格式的图像信息。
3.2 通过QT编译的图片查看器查看摄像头采集的图像。
QT目前是在嵌入式Linux领域中比较流行的图形开发工具,在我们的文件系统中,采用了基于QT的图形界面Qtopia,以下是实现调用图像信息的QT语句。第一句表示将图片的路径以及图片的名字传给pm1,然后通过Qlabel类的pl传出图片给图片查看器,从而实现了图像信息的显示。
QPixmap pm1("picture_path/picture_name");
Qlabel p1;
p1—>SetPixmap(pm1);了;‘
在采集的图像数据中,可以自定义所存储的图片格式,大小及其像素,方便迅速,而Qtopia是基于QT的比较成熟的嵌入式图形界面,利用其来显示我们所采集的图像数据效果良好。
4、结束语
本文详细介绍了基于ARM920T的嵌入式Linux下的USB摄像头图像采集的硬件、软件构建过程,可以灵活应用于基于嵌入式的各种电子产品中。由于所采用的软件全部是开放源码而且免费获得,所以对于需要便携好而又要有较高的数据处理能力且成本要求严格的方面尤其适合。
离线keisuo

只看该作者 8楼 发表于: 2007-04-26
Devices
Video4Linux provides the following sets of device files. These live on the character device formerly known as "/dev/bttv". /dev/bttv should be a symlink to /dev/video0 for most people.
Device Name Minor Range Function
/dev/video 0-63 Video Capture Interface
/dev/radio 64-127 AM/FM Radio Devices
/dev/vtx 192-223 Teletext Interface Chips
/dev/vbi 224-239 Raw VBI Data (Intercast/teletext)


Video4Linux programs open and scan the devices to find what they are looking for. Capability queries define what each interface supports. The described API is only defined for video capture cards. The relevant subset applies to radio cards. Teletext interfaces talk the existing VTX API.


Capability Query Ioctl
The VIDIOCGCAP ioctl call is used to obtain the capability information for a video device. The struct video_capability object passed to the ioctl is completed and returned. It contains the following information
name[32] Canonical name for this interface
type Type of interface
channels Number of radio/tv channels if appropriate
audios Number of audio devices if appropriate
maxwidth Maximum capture width in pixels
maxheight Maximum capture height in pixels
minwidth Minimum capture width in pixels
minheight Minimum capture height in pixels


The type field lists the capability flags for the device. These are as follows

Name Description
VID_TYPE_CAPTURE Can capture to memory
VID_TYPE_TUNER Has a tuner of some form
VID_TYPE_TELETEXT Has teletext capability
VID_TYPE_OVERLAY Can overlay its image onto the frame buffer
VID_TYPE_CHROMAKEY Overlay is Chromakeyed
VID_TYPE_CLIPPING Overlay clipping is supported
VID_TYPE_FRAMERAM Overlay overwrites frame buffer memory
VID_TYPE_SCALES The hardware supports image scaling
VID_TYPE_MONOCHROME Image capture is grey scale only
VID_TYPE_SUBCAPTURE Capture can be of only part of the image


The minimum and maximum sizes listed for a capture device do not imply all that all height/width ratios or sizes within the range are possible. A request to set a size will be honoured by the largest available capture size whose capture is no large than the requested rectangle in either direction. For example the quickcam has 3 fixed settings.


Frame Buffer
Capture cards that drop data directly onto the frame buffer must be told the base address of the frame buffer, its size and organisation. This is a privileged ioctl and one that eventually X itself should set.
The VIDIOCSFBUF ioctl sets the frame buffer parameters for a capture card. If the card does not do direct writes to the frame buffer then this ioctl will be unsupported. The VIDIOCGFBUF ioctl returns the currently used parameters. The structure used in both cases is a struct video_buffer.

void *base Base physical address of the buffer
int height Height of the frame buffer
int width Width of the frame buffer
int depth Depth of the frame buffer
int bytesperline Number of bytes of memory between the start of two adjacent lines


Note that these values reflect the physical layout of the frame buffer. The visible area may be smaller. In fact under XFree86 this is commonly the case. XFree86 DGA can provide the parameters required to set up this ioctl. Setting the base address to NULL indicates there is no physical frame buffer access.


Capture Windows
The capture area is described by a struct video_window. This defines a capture area and the clipping information if relevant. The VIDIOCGWIN ioctl recovers the current settings and the VIDIOCSWIN sets new values. A successful call to VIDIOCSWIN indicates that a suitable set of parameters have been chosen. They do not indicate that exactly what was requested was granted. The program should call VIDIOCGWIN to check if the nearest match was suitable. The struct video_window contains the following fields.
x The X co-ordinate specified in X windows format.
y The Y co-ordinate specified in X windows format.
width The width of the image capture.
height The height of the image capture.
chromakey A host order RGB32 value for the chroma key.
flags Additional capture flags.
clips A list of clipping rectangles. (Set only)
clipcount The number of clipping rectangles. (Set only)


Clipping rectangles are passed as an array. Each clip consists of the following fields available to the user.

x X co-ordinate of rectangle to skip
y Y co-ordinate of rectangle to skip
width Width of rectangle to skip
height Height of rectangle to skip


Merely setting the window does not enable capturing. Overlay capturing (i.e. PCI-PCI transfer to the frame buffer of the video card) is activated by passing the VIDIOCCAPTURE ioctl a value of 1, and disabled by passing it a value of 0.

Some capture devices can capture a subfield of the image they actually see. This is indicated when VIDEO_TYPE_SUBCAPTURE is defined. The video_capture describes the time and special subfields to capture. The video_capture structure contains the following fields.

x X co-ordinate of source rectangle to grab
y Y co-ordinate of source rectangle to grab
width Width of source rectangle to grab
height Height of source rectangle to grab
decimation Decimation to apply
flags Flag settings for grabbing
The available flags are

Name Description
VIDEO_CAPTURE_ODD Capture only odd frames
VIDEO_CAPTURE_EVEN Capture only even frames



Video Sources
Each video4linux video or audio device captures from one or more source channels. Each channel can be queries with the VDIOCGCHAN ioctl call. Before invoking this function the caller must set the channel field to the channel that is being queried. On return the struct video_channel is filled in with information about the nature of the channel itself.
The VIDIOCSCHAN ioctl takes an integer argument and switches the capture to this input. It is not defined whether parameters such as colour settings or tuning are maintained across a channel switch. The caller should maintain settings as desired for each channel. (This is reasonable as different video inputs may have different properties).

The struct video_channel consists of the following

channel The channel number
name The input name - preferably reflecting the label on the card input itself
tuners Number of tuners for this input
flags Properties the tuner has
type Input type (if known)
norm The norm for this channel


The flags defined are

VIDEO_VC_TUNER Channel has tuners.
VIDEO_VC_AUDIO Channel has audio.
VIDEO_VC_NORM Channel has norm setting.


The types defined are

VIDEO_TYPE_TV The input is a TV input.
VIDEO_TYPE_CAMERA The input is a camera.



Image Properties
The image properties of the picture can be queried with the VIDIOCGPICT ioctl which fills in a struct video_picture. The VIDIOCSPICT ioctl allows values to be changed. All values except for the palette type are scaled between 0-65535.
The struct video_picture consists of the following fields

brightness Picture brightness
hue Picture hue (colour only)
colour Picture colour (colour only)
contrast Picture contrast
whiteness The whiteness (greyscale only)
depth The capture depth (may need to match the frame buffer depth)
palette Reports the palette that should be used for this image


The following palettes are defined

VIDEO_PALETTE_GREY Linear intensity grey scale (255 is brightest).
VIDEO_PALETTE_HI240 The BT848 8bit colour cube.
VIDEO_PALETTE_RGB565 RGB565 packed into 16 bit words.
VIDEO_PALETTE_RGB555 RGV555 packed into 16 bit words, top bit undefined.
VIDEO_PALETTE_RGB24 RGB888 packed into 24bit words.
VIDEO_PALETTE_RGB32 RGB888 packed into the low 3 bytes of 32bit words. The top 8bits are undefined.
VIDEO_PALETTE_YUV422 Video style YUV422 - 8bits packed 4bits Y 2bits U 2bits V
VIDEO_PALETTE_YUYV Describe me
VIDEO_PALETTE_UYVY Describe me
VIDEO_PALETTE_YUV420 YUV420 capture
VIDEO_PALETTE_YUV411 YUV411 capture
VIDEO_PALETTE_RAW RAW capture (BT848)
VIDEO_PALETTE_YUV422P YUV 4:2:2 Planar
VIDEO_PALETTE_YUV411P YUV 4:1:1 Planar



Tuning
Each video input channel can have one or more tuners associated with it. Many devices will not have tuners. TV cards and radio cards will have one or more tuners attached.
Tuners are described by a struct video_tuner which can be obtained by the VIDIOCGTUNER ioctl. Fill in the tuner number in the structure then pass the structure to the ioctl to have the data filled in. The tuner can be switched using VIDIOCSTUNER which takes an integer argument giving the tuner to use. A struct tuner has the following fields

tuner Number of the tuner
name Canonical name for this tuner (eg FM/AM/TV)
rangelow Lowest tunable frequency
rangehigh Highest tunable frequency
flags Flags describing the tuner
mode The video signal mode if relevant
signal Signal strength if known - between 0-65535


The following flags exist

VIDEO_TUNER_PAL PAL tuning is supported
VIDEO_TUNER_NTSC NTSC tuning is supported
VIDEO_TUNER_SECAM SECAM tuning is supported
VIDEO_TUNER_LOW Frequency is in a lower range
VIDEO_TUNER_NORM The norm for this tuner is settable
VIDEO_TUNER_STEREO_ON The tuner is seeing stereo audio
VIDEO_TUNER_RDS_ON The tuner is seeing a RDS datastream
VIDEO_TUNER_MBS_ON The tuner is seeing a MBS datastream


The following modes are defined

VIDEO_MODE_PAL The tuner is in PAL mode
VIDEO_MODE_NTSC The tuner is in NTSC mode
VIDEO_MODE_SECAM The tuner is in SECAM mode
VIDEO_MODE_AUTO The tuner auto switches, or mode does not apply


Tuning frequencies are an unsigned 32bit value in 1/16th MHz or if the VIDEO_TUNER_LOW flag is set they are in 1/16th KHz. The current frequency is obtained as an unsigned long via the VIDIOCGFREQ ioctl and set by the VIDIOCSFREQ ioctl.


Audio
TV and Radio devices have one or more audio inputs that may be selected. The audio properties are queried by passing a struct video_audio to VIDIOCGAUDIO ioctl. The VIDIOCSAUDIO ioctl sets audio properties.
The structure contains the following fields

audio The channel number
volume The volume level
bass The bass level
treble The treble level
flags Flags describing the audio channel
name Canonical name for the audio input
mode The mode the audio input is in
balance The left/right balance
step Actual step used by the hardware


The following flags are defined

VIDEO_AUDIO_MUTE The audio is muted
VIDEO_AUDIO_MUTABLE Audio muting is supported
VIDEO_AUDIO_VOLUME The volume is controllable
VIDEO_AUDIO_BASS The bass is controllable
VIDEO_AUDIO_TREBLE The treble is controllable
VIDEO_AUDIO_BALANCE The balance is controllable


The following decoding modes are defined

VIDEO_SOUND_MONO Mono signal
VIDEO_SOUND_STEREO Stereo signal (NICAM for TV)
VIDEO_SOUND_LANG1 European TV alternate language 1
VIDEO_SOUND_LANG2 European TV alternate language 2



Reading Images
Each call to the read syscall returns the next available image from the device. It is up to the caller to set format and size (using the VIDIOCSPICT and VIDIOCSWIN ioctls) and then to pass a suitable size buffer and length to the function. Not all devices will support read operations.
A second way to handle image capture is via the mmap interface if supported. To use the mmap interface a user first sets the desired image size and depth properties. Next the VIDIOCGMBUF ioctl is issued. This reports the size of buffer to mmap and the offset within the buffer for each frame. The number of frames supported is device dependent and may only be one.

The video_mbuf structure contains the following fields

size The number of bytes to map
frames The number of frames
offsets The offset of each frame


Once the mmap has been made the VIDIOCMCAPTURE ioctl starts the capture to a frame using the format and image size specified in the video_mmap (which should match or be below the initial query size). When the VIDIOCMCAPTURE ioctl returns the frame is not captured yet, the driver just instructed the hardware to start the capture. The application has to use the VIDIOCSYNC ioctl to wait until the capture of a frame is finished. VIDIOCSYNC takes the frame number you want to wait for as argument.

It is allowed to call VIDIOCMCAPTURE multiple times (with different frame numbers in video_mmap->frame of course) and thus have multiple outstanding capture requests. A simple way do to double-buffering using this feature looks like this:

/* setup everything */
VIDIOCMCAPTURE(0)
while (whatever) {
  VIDIOCMCAPTURE(1)
  VIDIOCSYNC(0)
  /* process frame 0 while the hardware captures frame 1 */
  VIDIOCMCAPTURE(0)
  VIDIOCSYNC(1)
  /* process frame 1 while the hardware captures frame 0 */
}

Note that you are not limited to only two frames. The API allows up to 32 frames, the VIDIOCGMBUF ioctl returns the number of frames the driver granted. Thus it is possible to build deeper queues to avoid loosing frames on load peaks.
While capturing to memory the driver will make a "best effort" attempt to capture to screen as well if requested. This normally means all frames that "miss" memory mapped capture will go to the display.

A final ioctl exists to allow a device to obtain related devices if a driver has multiple components (for example video0 may not be associated with vbi0 which would cause an intercast display program to make a bad mistake). The VIDIOCGUNIT ioctl reports the unit numbers of the associated devices if any exist. The video_unit structure has the following fields.

video Video capture device
vbi VBI capture device
radio Radio device
audio Audio mixer
teletext Teletext device



RDS Datastreams
For radio devices that support it, it is possible to receive Radio Data System (RDS) data by means of a read() on the device. The data is packed in groups of three, as follows: First Octet Least Significant Byte of RDS Block
Second Octet Most Significant Byte of RDS Block 
Third Octet Bit 7: Error bit. Indicates that an uncorrectable error occurred during reception of this block.
  Bit 6: Corrected bit. Indicates that an error was corrected for this data block.
  Bits 5-3: Received Offset. Indicates the offset received by the sync system.
  Bits 2-0: Offset Name. Indicates the offset applied to this data.
离线keisuo

只看该作者 7楼 发表于: 2007-04-26
如何编写Linux的设备驱动程序Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和
思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的
区别.在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是
支持函数少,只能依赖kernel中的函数,有些常用的操作要自己来编写,而且调
试也不方便.本人这几周来为实验室自行研制的一块多媒体卡编制了驱动程序,
获得了一些经验,愿与Linux fans共享,有不当之处,请予指正.
以下的一些文字主要来源于khg,johnsonm的Write linux device driver,
Brennan's Guide to Inline Assembly,The Linux A-Z,还有清华BBS上的有关
device driver的一些资料. 这些资料有的已经过时,有的还有一些错误,我依
据自己的试验结果进行了修正.
 
 
一. Linux device driver 的概念
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统
内核和机器硬件之间的接口.设备驱动程序为应用程序屏蔽了硬件的细节,这样
在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象操作普通文件
一样对硬件设备进行操作.设备驱动程序是内核的一部分,它完成以下的功能:
1.对设备初始化和释放.
2.把数据从内核传送到硬件和从硬件读取数据.
3.读取应用程序传送给设备文件的数据和回送应用程序请求的数据.
4.检测和处理设备出现的错误.
在Linux操作系统下有两类主要的设备文件类型,一种是字符设备,另一种是
块设备.字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际
的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,
当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际
的I/O操作.块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间
来等待.
已经提到,用户进程是通过设备文件来与实际的硬件打交道.每个设备文件都
都有其文件属性(c/b),表示是字符设备还蔤强樯璞?另外每个文件都有两个设
备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个
设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分
他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号
一致,否则用户进程将无法访问到驱动程序.
最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是
抢先式调度.也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他
的工作.如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就
是漫长的fsck.//hehe
(请看下节,实例剖析)
读/写时,它首先察看缓冲区的内容,如果缓冲区的数据
 
如何编写Linux操作系统下的设备驱动程序
Roy G
二.实例剖析
我们来写一个最简单的字符设备驱动程序.虽然它什么也不做,但是通过它
可以了解Linux的设备驱动程序的工作原理.把下面的C代码输入机器,你就会
获得一个真正的设备驱动程序.不过我的kernel是2.0.34,在低版本的kernel
上可能会出现问题,我还没测试过.//xixi
#define __NO_VERSION__
#include
#include
char kernel_version [] = UTS_RELEASE;
这一段定义了一些版本信息,虽然用处不是很大,但也必不可少.Johnsonm说所
有的驱动程序的开头都要包含,但我看倒是未必.
由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就
是一些系统调用,如 open,read,write,close...., 注意,不是fopen, fread.,
但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据
结构:
struct file_operations {
int (*seek) (struct inode * ,struct file *, off_t ,int);
int (*read) (struct inode * ,struct file *, char ,int);
int (*write) (struct inode * ,struct file *, off_t ,int);
int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);
int (*select) (struct inode * ,struct file *, int ,select_table *);
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long
int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
int (*open) (struct inode * ,struct file *);
int (*release) (struct inode * ,struct file *);
int (*fsync) (struct inode * ,struct file *);
int (*fasync) (struct inode * ,struct file *,int);
int (*check_media_change) (struct inode * ,struct file *);
int (*revalidate) (dev_t dev);
}
这个结构的每一个成员的名字都对应着一个系统调用.用户进程利用系统调用
在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号
找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制
权交给该函数.这是linux的设备驱动程序工作的基本原理.既然是这样,则编写
设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域.
相当简单,不是吗?
下面就开始写子程序.
#include
#include
#include
#include
#include
unsigned int test_major = 0;
static int read_test(struct inode *node,struct file *file,
char *buf,int count)
{
int left;
if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )
return -EFAULT;
for(left = count  left > 0  left--)
{
__put_user(1,buf,1);
buf++;
}
return count;
}
这个函数是为read调用准备的.当调用read时,read_test()被调用,它把用户的
缓冲区全部写1.
buf 是read调用的一个参数.它是用户进程空间的一个地址.但是在read_test
被调用时,系统进入核心态.所以不能使用buf这个地址,必须用__put_user(),
这是kernel提供的一个函数,用于向用户传送数据.另外还有很多类似功能的
函数.请参考.在向用户空间拷贝数据之前,必须验证buf是否可用.
这就用到函数verify_area.
 
static int write_tibet(struct inode *inode,struct file *file,
const char *buf,int count)
{
return count;
}
static int open_tibet(struct inode *inode,struct file *file )
{
MOD_INC_USE_COUNT;
return 0;
} static void release_tibet(struct inode *inode,struct file *file )
{
MOD_DEC_USE_COUNT;
}
这几个函数都是空操作.实际调用发生时什么也不做,他们仅仅为下面的结构
提供函数指针。
struct file_operations test_fops = {
NULL,
read_test,
write_test,
NULL, /* test_readdir */
NULL,
NULL, /* test_ioctl */
NULL, /* test_mmap */
open_test,
release_test, NULL, /* test_fsync */
NULL, /* test_fasync */
/* nothing more, fill with NULLs */
};
设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序
可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(modules),
如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能
动态的卸载,不利于调试,所以推荐使用模块方式。
int init_module(void)
{
int result;
result = register_chrdev(0, "test", &test_fops);
if (result < 0) {
printk(KERN_INFO "test: can't get major number ");
return result;
}
if (test_major == 0) test_major = result; /* dynamic */
return 0;
}
在用insmod命令将编译好的模块调入内存时,init_module 函数被调用。在
这里,init_module只做了一件事,就是向系统的字符设备表登记了一个字符
设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是
零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,
参数三用来登记驱动程序实际执行操作的函数的指针。
如果登记成功,返回设备的主设备号,不成功,返回一个负值。
void cleanup_module(void)
{
unregister_chrdev(test_major, "test");
}
在用rmmod卸载模块时,cleanup_module函数被调用,它释放字符设备test
在系统字符设备表中占有的表项。
一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。
下面编译
$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c
得到文件test.o就是一个设备驱动程序。
如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后
ld -r file1.o file2.o -o modulename.
驱动程序已经编译好了,现在把它安装到系统中去。
$ insmod -f test.o
如果安装成功,在/proc/devices文件中就可以看到设备test,
并可以看到它的主设备号,。
要卸载的话,运行
$ rmmod test
下一步要创建设备文件。
mknod /dev/test c major minor
c 是指字符设备,major是主设备号,就是在/proc/devices里看到的。
用shell命令
$ cat /proc/devices | awk "\$2=="test" {print \$1}"
就可以获得主设备号,可以把上面的命令行加入你的shell script中去。
minor是从设备号,设置成0就可以了。
我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。
#include
#include
#include
#include
main()
{
int testdev;
int i;
char buf[10];
testdev = open("/dev/test",O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file ");
exit(0);
}
read(testdev,buf,10);
for (i = 0; i < 10;i++)
printf("%d ",buf);
close(testdev);
}
编译运行,看看是不是打印出全1 ?
 
以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,
DMA,I/O port等问题。这些才是真正的难点。请看下节,实际情况的处理。
如何编写Linux操作系统下的设备驱动程序
Roy G
三 设备驱动程序中的一些具体问题。
1. I/O Port.
和硬件打交道离不开I/O Port,老的ISA设备经常是占用实际的I/O端口,
在linux下,操作系统没有对I/O口屏蔽,也就是说,任何驱动程序都可以
对任意的I/O口操作,这样就很容易引起混乱。每个驱动程序应该自己避免
误用端口。
有两个重要的kernel函数可以保证驱动程序做到这一点。
1)check_region(int io_port, int off_set)
这个函数察看系统的I/O表,看是否有别的驱动程序占用某一段I/O口。
参数1:io端口的基地址,
参数2:io端口占用的范围。
返回值:0 没有占用, 非0,已经被占用。
2)request_region(int io_port, int off_set,char *devname)
如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用
之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports
文件中可以看到你登记的io口。
参数1:io端口的基地址。
参数2:io端口占用的范围。
参数3:使用这段io地址的设备名。
在对I/O口登记后,就可以放心地用inb(), outb()之类的函来访问了。
在一些pci设备中,I/O端口被映射到一段内存中去,要访问这些端口就相当
于访问一段内存。经常性的,我们要获得一块内存的物理地址。在dos环境下,
(之所以不说是dos操作系统是因为我认为DOS根本就不是一个操作系统,它实
在是太简单,太不安全了)只要用段:偏移就可以了。在window95中,95ddk
提供了一个vmm 调用 _MapLinearToPhys,用以把线性地址转化为物理地址。但
在Linux中是怎样做的呢?
2 内存操作
在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用
get_free_pages直接申请页。释放内存用的是kfree,或free_pages. 请注意,
kmalloc等函数返回的是物理地址!而malloc等返回的是线性地址!关于
kmalloc返回的是物理地址这一点本人有点不太明白:既然从线性地址到物理
地址的转换是由386cpu硬件完成的,那样汇编指令的操作数应该是线性地址,
驱动程序同样也不能直接使用物理地址而是线性地址。但是事实上kmalloc
返回的确实是物理地址,而且也可以直接通过它访问实际的RAM,我想这样可
以由两种解释,一种是在核心态禁止分页,但是这好像不太现实;另一种是
linux的页目录和页表项设计得正好使得物理地址等同于线性地址。我的想法
不知对不对,还请高手指教。
言归正传,要注意kmalloc最大只能开辟128k-16,16个字节是被页描述符
结构占用了。kmalloc用法参见khg.
内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000
以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得
重新映射以后的地址。
另外,很多硬件需要一块比较大的连续内存用作DMA传送。这块内存需要一直
驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。
这可以通过牺牲一些系统内存的方法来解决。
具体做法是:比如说你的机器由32M的内存,在lilo.conf的启动参数中加上
mem=30M,这样linux就认为你的机器只有30M的内存,剩下的2M内存在vremap
之后就可以为DMA所用了。
请记住,用vremap映射后的内存,不用时应用unremap释放,否则会浪费页表。
3 中断处理
同处理I/O端口一样,要使用一个中断,必须先向系统登记。
int request_irq(unsigned int irq ,
void(*handle)(int,void *,struct pt_regs *),
unsigned int long flags,
const char *device);
irq: 是要申请的中断。
handle:中断处理函数指针。
flags:SA_INTERRUPT 请求一个快速中断,0 正常中断。
device:设备名。
如果登记成功,返回0,这时在/proc/interrupts文件中可以看你请求的
中断。
4一些常见的问题。
对硬件操作,有时时序很重要。但是如果用C语言写一些低级的硬件操作
的话,gcc往往会对你的程序进行优化,这样时序就错掉了。如果用汇编写呢,
gcc同样会对汇编代码进行优化,除非你用volatile关键字修饰。最保险的
办法是禁止优化。这当然只能对一部分你自己编写的代码。如果对所有的代码
都不优化,你会发现驱动程序根本无法装载。这是因为在编译驱动程序时要
用到gcc的一些扩展特性,而这些扩展特性必须在加了优化选项之后才能体现
出来。
关于kernel的调试工具,我现在还没有发现有合适的。有谁知道请告诉我,
不胜感激。我一直都在printk打印调试信息,倒也还凑合。
关于设备驱动程序还有很多内容,如等待/唤醒机制,块设备的编写等。
离线keisuo

只看该作者 6楼 发表于: 2007-04-26
http://www.xsbase.com/
USB摄像头我现在的项目设计需要从USB摄像头读图片到开发板。
USB控制器为cy7c67300,操作系统为嵌入式LINUX,
现在需要让USB控制器支持等时传输模式,不知道该如何修改驱动。
/////////////////////////////////////////////////////
moonse给个联系方式吧,我们好一起交流
我的邮箱:bai1981@sina.com.cn
qq:3833905
USB摄像头的问题在于:
一、现在提供的代码中有部分操作是多余的,因为只用到一个PORT,
    不需要对所有的PORT操作;
二、底层硬件处理完TD_List后,上层HCD对TD数据的处理不正确,
    主要是无法得到正确的读取的数据的字节数(在ISOC模式下)
三、设备驱动程序方面,对ov511的支持不够好,
    原因在于2.4.18中提供的ov511驱动程序并不完善,需要修改。
    其中一个重要的结构是URB数据包的大小.
修改完这些以后就可以很顺利地使用ov511驱动采集视频了。
//////////////////////////////////////////////////
图像采集的实现
    图像采集程序的编写基于linux内核中提供的Video4Linux 接口。
Video4Linux是2.2.0版本之后linux内核提供给网络摄像头、视频采集卡、
电视卡等设备软件开发的接口标准。这个标准为内核、驱动、应用程序提供一个API进行交流。
目前的最新Video4Linux版本为V4L2。
    使用双URB轮流通信对于对时间敏感而对数据的正确性要求不高的图像采集应用,
USB总线定义了ISOC传输模式,USB摄像头应当使用这种传输方式。
为了尽可能快地得到图像数据,应当在URB中指定USB_ISO_ASAP标志。
urb->transfer_flags=USB_ISO_ASAP;//尽可能快地发出本URB
    Linux系统中任何USB传输都通过URB实现。为提高速度,可以考虑扩大URB的缓冲,
也可以建立两个URB,在等待一个URB被回收时,也就是图像正在被传感器采集时,
处理、初始化另一个URB,并在回收后立刻将其发出。两个URB交替使用,
大大减少了额外时间。使用内存映射并用双帧缓冲提高效率
    Linux系统通过read,write等来实现对硬件的操作,它们通过copy_to_user()、
copy_from_user()等函数在内核和用户内存空间中互相拷贝。
但是对于视频采集这类需要大量高速传输数据的应用来说,
这种方法耗费的硬件资源过大,通过内存映射的方法可以使这一问题得到有效解决。
    首先使用vmalloc()申请足够大的核态内存,将其作为图像数据缓冲空间,
两个URB带回的图像数据在这里暂存;然后使用remap_page_range()函数将其逐页映射到用户空间中。
户态的图像采集处理程序使用mmap()函数,直接读写内核图像缓冲内存,大大减少额外开销。
另外,为了进一步提高帧速率,本文采用双帧缓冲方式进行图像采集。
/////////////////////////////////////////////////////
XSBase中如何使用USB摄像头各位好!
我想在XSBase上使用USB摄像头,采集图像,摄像头芯片是ov511,内核中已经包含该驱动.
插上摄像头后,摄像头的灯也亮了,但在采集图像时,却看不到,console下出现下面的信息:
qu_queue_active_urb: add urb to iso_list, start_frame = 0x2c6, # Pkts = 0xa, err Cnt = 0x0
当cat /dev/video 时,也出现上面的信息.
请问如何使用ov511 USB摄像头?
这是因为usb host的驱动程序中不支持IOSC传输模式,
你需要增加IOSC的传输模式才可以支持usb camera
/////////////////////////////////////////////////////
Linux内核编译另外:
还想问一下斑竹,在编译内核时,为了支持usb存储,需不需要
UHCI OHCI。。。之类??
我在原有基础上只添加了:
1 SCSI support
  SCSI disk support
2 DOs FAT fs
  MSDOS fs
  VfAf fs
3.USB mess support
  preliminary usb device
不知道是否少选了某些模块???
Preliminary USB DEVICE Filesystem
USB Controllers-> CY7C67300,你的可能不一样
USB Mass Storage support
离线keisuo

只看该作者 5楼 发表于: 2007-04-26
Video4Linux 编程获取数据。
  现有的video4linux有两个版本,v4l和v4l2。本文主要是关于v4l的编程。
  利用v4l API获取视频图像一般有以下几步:
    a>  打开设备
    b>  设置设备的属性,比如图像的亮度,对比度等等
    c>  设定传输格式和传输方式
    d>  开始传输数据,一般是一个循环,用以连续的传输数据
    e>  关闭设备
    下面具体介绍v4l编程的过程。首先指出,
    在video4linux编程时要包含<videodev.h>头文件,
    其中包含了video4linux的数据结构和函数定义。
  1)v4l的数据结构
    在video4linux API中定义了如下数据结构,详细的数据结构定义可以参考v4l API的文档,
    这里就编程中经常使用的数据结构作出说明。
    首先我们定义一个描述设备的数据结构,它包含了v4l中定义的所有数据结构:
      typedef struct _v4ldevice
      {
int fd;    //设备号
struct video_capability capability;
struct video_channel channel[10];
struct video_picture picture;
struct video_clip clip;
struct video_window window;
struct video_capture capture;
struct video_buffer buffer;
struct video_mmap mmap;
struct video_mbuf mbuf;
struct video_unit unit;
unsigned char *map;  //mmap方式获取数据时,数据的首地址
pthread_mutex_t mutex;
int frame;
int framestat[2];
int overlay;
      }v4ldevice;
      下面解释上面这个数据结构中包含的数据结构,这些结构的定义都在<videodev.h>中。
      * struct video_capability
        name[32]  Canonical name for this interface
        type  Type of interface
        channels  Number of radio/tv channels if appropriate
        audios  Number of audio devices if appropriate
        maxwidth  Maximum capture width in pixels
        maxheight  Maximum capture height in pixels
        minwidth  Minimum capture width in pixels
        minheight  Minimum capture height in pixels
      这一个数据结构是包含了摄像头的属性,name是摄像头的名字,
      maxwidth maxheight是摄像头所能获取的最大图像大小,用橡素作单位。
      在程序中,通过ioctl函数的VIDIOCGCAP控制命令读写设备通道已获取这个结构,
      有关ioctl的使用,比较复杂,这里就不说了。下面列出获取这一数据结构的代码:
int v4lgetcapability(v4ldevice *vd)
{
  if(ioctl(vd->fd, VIDIOCGCAP, &(vd->capability)) < 0)
  {
      v4lperror("v4lopen:VIDIOCGCAP");
      return -1;
  }
  return 0;
}
      * struct video_picture
        brightness    Picture brightness
        hue            Picture hue (colour only)
        colour        Picture colour (colour only)
        contrast      Picture contrast
        whiteness    The whiteness (greyscale only)
        depth        The capture depth (may need to match the frame buffer depth)
        palette      Reports the palette that should be used for this image
      这个数据结构主要定义了图像的属性,诸如亮度,对比度,等等。
      这一结构的获取通过ioctl发出VIDIOCGPICT控制命令获取。
      * struct    video_mbuf
        size      The number of bytes to map
        frames    The number of frames
        offsets  The offset of each frame
      这个数据结构在用mmap方式获取数据时很重要:
      size表示图像的大小,如果是640*480的彩色图像,size=640*480*3
      frames表示帧数
      offsets表示每一帧在内存中的偏移地址,通过这个值可以得到数据在图像中的地址。
      得到这个结构的数据可以用ioctl的VIDIOCGMBUF命令。源码如下:
int v4lgetmbuf(v4ldevice *vd)
{
  if(ioctl(vd->fd, VIDIOCGMBUF, &(vd->mbuf))<0)
  {
    v4lperror("v4lgetmbuf:VIDIOCGMBUF");
    return -1;
  }
  return 0;
}
  而数据的地址可以有以下方式计算:
unsigned char *v4lgetaddress(v4ldevice *vd)
{
    return (vd->map + vd->mbuf.offsets[vd->frame]);
}
2)获取影像mmap方式。
      在video4linux下获取影像有两种方式:overlay和mmap。由于我的摄像头不支持overlay方式,所以这里只谈mmap方式。
      mmap方式是通过内存映射的方式获取数据,系统调用ioctl的VIDIOCMCAPTURE后,将图像映射到内存中,
      然后可以通过前面的v4lgetmbuf(vd)函数和v4lgetaddress(vd)函数获得数据的首地址,
      这是李可以选择是将它显示出来还是放到别的什么地方。
      下面给出获取连续影像的最简单的方法(为了简化,将一些可去掉的属性操作都去掉了):
  char* devicename="/dev/video0";
  char* buffer;
  v4ldevice device;
  int width = 640;
  int height = 480;
  int frame = 0;
  v4lopen("/dev/video0",&device);    //打开设备
  v4lgrabinit(&device,width,height); //初始化设备,定义获取的影像的大小
  v4lmmap(&device);                  //内存映射
  v4lgrabstart(&device,frame);      //开始获取影像
  while(1)
  {
    v4lsync(&device,frame);          //等待传完一帧
    frame = (frame+1)%2;              //下一帧的frame
    v4lcapture(&device,frame);        //获取下一帧
    buffer = (char*)v4lgetaddress(&device);//得到这一帧的地址
    //buffer给出了图像的首地址,你可以选择将图像显示或保存......
    //图像的大小为 width*height*3
  ..........................
  }
为了更好的理解源码,这里给出里面的函数的实现,这里为了简化,将所有的出错处理都去掉了。
int v4lopen(char *name, v4ldevice *vd)
{
  int i;
  if((vd->fd = open(name,O_RDWR)) < 0)
  {
      return -1;
  }
  if(v4lgetcapability(vd))
      return -1;
}
int v4lgrabinit(v4ldevice *vd, int width, int height)
{
  vd->mmap.width = width;
  vd->mmap.height = height;
  vd->mmap.format = vd->picture.palette;
  vd->frame = 0;
  vd->framestat[0] = 0;
  vd->framestat[1] = 0;
  return 0;
}
int v4lmmap(v4ldevice *vd)
{
  if(v4lgetmbuf(vd)<0)
      return -1;
  if((vd->map = mmap(0, vd->mbuf.size, PROT_READ|PROT_WRITE, MAP_SHARED, vd->fd, 0)) < 0)
  {
      return -1;
  }
  return 0;
}
int v4lgrabstart(v4ldevice *vd, int frame)
{
  vd->mmap.frame = frame;
  if(ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) < 0)
  {
      return -1;
  }
  vd->framestat[frame] = 1;
  return 0;
}
int v4lsync(v4ldevice *vd, int frame)
{
  if(ioctl(vd->fd, VIDIOCSYNC, &frame) < 0)
  {
      return -1;
  }
  vd->framestat[frame] = 0;
  return 0;
}
int c4lcapture(v4ldevice *vd, int frame)
{
  vd->mmap.frame = frame;
  if(ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) < 0)
  {
      return -1;
  }
  vd->framestat[frame] = 1;
  return 0;
}
离线keisuo

只看该作者 4楼 发表于: 2007-04-26
Double Buffering 


Flicker闪烁 can be completely avoided if there is no delay延迟 between painting the background and painting the content. This can be achieved完成 by rendering 着色 the background into a pixmap 象素映射, then painting the content onto that pixmap, and finally painting the pixmap onto the widget. This technique is known as double buffering.

The quick and easy way to do this is to create a pixmap that's the same size as the rectangle that needs to be repainted. Then paint everything into the pixmap and finally paint the pixmap onto the widget. While doing this is completely flicker-free, it means paying a price in memory consumption and processing. For example, you would need to allocate and deallocate a pixmap of width × height × color-depth bits in every paint event, and this is a lot of memory if the widget gets maximized on a 32-bpp (bits per pixel) 1280 × 1024 screen. It is possible to reuse the same pixmap to save reallocations, but it would need to be the maximum possible size of the widget, so it will still consume a big chunk大块 of memory.

A neat灵巧的 solution is to combine double buffering with stripes条纹. Instead of storing the whole widget's rectangle in a pixmap, we can store one line instead. We then paint the background and content of a single line at a time and copy the pixmap back to the widget for each line that's painted. This requires a pixmap that's only line-width × line-height × color-depth bits in size; a lot less than the whole widget. We can also reuse the same pixmap, resizing it to be larger if necessary.

Here's another version of the paintEvent() we wrote earlier, this time using double buffering, reusing the pixmap, and painting in stripes.

    void MyWidget::paintEvent(QPaintEvent *evt)
    {
        // any initialization
        QPainter painter(this);
        QRegion unpainted(evt->clipRegion());
        static QPixmap *doubleBuffer = 0;
        if (!doubleBuffer)
            doubleBuffer = new QPixmap;
        QPainter dbPainter(doubleBuffer);

    for (int i = 0; i < lines.count(); i++) {
            Line *line = lines;
            doubleBuffer->resize(QMAX(doubleBuffer->width(), line->boundingRect().width()),
                 QMAX(doubleBuffer->height(), line->boundingRect().height()));
            doubleBuffer->fill(backgroundColor);
            line->draw(&dbPainter);
            painter.drawPixmap(0, line->boundingRect().y(), *doubleBuffer, 0, 0,
                              line->boundingRect().width(), line->boundingRect().height());
            unpainted -= line->boundingRect();
        }
        painter.setClipRegion(unpainted);
        painter.fillRect(unpainted.boundingRect(), backgroundColor);
    }
    We reuse the pixmap by declaring it as a static pointer and allocating it if it is 0 (i.e. the first time we paint). Instead of painting directly on the widget, we paint on the pixmap. We must ensure that the pixmap is large enough each time we use it, resizing it if necessary. The painting code is almost the same as before because Qt provides a device-independent painting API. We just have to copy the pixmap to the widget after painting, using drawPixmap(). (We could have used bitBlt(), but drawPixmap() is faster if the painter is already open.)

This technique completely eliminates flicker, without consuming too much memory. Qt uses this approach in many standard widgets, including QTextEdit, QListView and QMenuBar. In fact, Qt applies a further memory optimization. Instead of allocating a double buffer pixmap for every single widget, Qt allocates a global pixmap for every type of widget, no matter how many instances of that widget are used.
离线keisuo

只看该作者 3楼 发表于: 2007-04-26
  最简单的显示图片的例子
////////////////////// 
  #include <QtGui/QApplication>
    #include <QtGui/QImage>
    #include <QtGui/QLabel>
    #include <QtGui/QPixmap>

    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);

        QLabel imageLabel(0);
        QPixmap pixmap;

        if(!pixmap.load(":/logo.jp2"))
            imageLabel.setText("Could not load image");
        else
            imageLabel.setPixmap(pixmap);

        imageLabel.show();
        return app.exec();
    }
离线keisuo

只看该作者 2楼 发表于: 2007-04-26
Double Buffering 


Flicker闪烁 can be completely avoided if there is no delay延迟 between painting the background and painting the content. This can be achieved完成 by rendering 着色 the background into a pixmap 象素映射, then painting the content onto that pixmap, and finally painting the pixmap onto the widget. This technique is known as double buffering.

The quick and easy way to do this is to create a pixmap that's the same size as the rectangle that needs to be repainted. Then paint everything into the pixmap and finally paint the pixmap onto the widget. While doing this is completely flicker-free, it means paying a price in memory consumption and processing. For example, you would need to allocate and deallocate a pixmap of width × height × color-depth bits in every paint event, and this is a lot of memory if the widget gets maximized on a 32-bpp (bits per pixel) 1280 × 1024 screen. It is possible to reuse the same pixmap to save reallocations, but it would need to be the maximum possible size of the widget, so it will still consume a big chunk大块 of memory.

A neat灵巧的 solution is to combine double buffering with stripes条纹. Instead of storing the whole widget's rectangle in a pixmap, we can store one line instead. We then paint the background and content of a single line at a time and copy the pixmap back to the widget for each line that's painted. This requires a pixmap that's only line-width × line-height × color-depth bits in size; a lot less than the whole widget. We can also reuse the same pixmap, resizing it to be larger if necessary.

Here's another version of the paintEvent() we wrote earlier, this time using double buffering, reusing the pixmap, and painting in stripes.

    void MyWidget::paintEvent(QPaintEvent *evt)
    {
        // any initialization
        QPainter painter(this);
        QRegion unpainted(evt->clipRegion());
        static QPixmap *doubleBuffer = 0;
        if (!doubleBuffer)
            doubleBuffer = new QPixmap;
        QPainter dbPainter(doubleBuffer);

    for (int i = 0; i < lines.count(); i++) {
            Line *line = lines;
            doubleBuffer->resize(QMAX(doubleBuffer->width(), line->boundingRect().width()),
                 QMAX(doubleBuffer->height(), line->boundingRect().height()));
            doubleBuffer->fill(backgroundColor);
            line->draw(&dbPainter);
            painter.drawPixmap(0, line->boundingRect().y(), *doubleBuffer, 0, 0,
                              line->boundingRect().width(), line->boundingRect().height());
            unpainted -= line->boundingRect();
        }
        painter.setClipRegion(unpainted);
        painter.fillRect(unpainted.boundingRect(), backgroundColor);
    }
    We reuse the pixmap by declaring it as a static pointer and allocating it if it is 0 (i.e. the first time we paint). Instead of painting directly on the widget, we paint on the pixmap. We must ensure that the pixmap is large enough each time we use it, resizing it if necessary. The painting code is almost the same as before because Qt provides a device-independent painting API. We just have to copy the pixmap to the widget after painting, using drawPixmap(). (We could have used bitBlt(), but drawPixmap() is faster if the painter is already open.)

This technique completely eliminates flicker, without consuming too much memory. Qt uses this approach in many standard widgets, including QTextEdit, QListView and QMenuBar. In fact, Qt applies a further memory optimization. Instead of allocating a double buffer pixmap for every single widget, Qt allocates a global pixmap for every type of widget, no matter how many instances of that widget are used.
离线keisuo

只看该作者 1楼 发表于: 2007-04-26
发表于: 2007-02-08 17:09    发表主题: 请教:QT图片循环显示,闪烁如何优化?谢谢 

--------------------------------------------------------------------------------

我刚刚接触QT,本应该从头慢慢学起的,但目前有一个问题急于解决,还望各位高手不吝赐教一下。

为了使几副图片循环显示,在paintEvent里加了几副图片的显示调用painter.drawPixmap();并在主窗口里加了一个Timer,timeout触发repaint();

虽然可以实现,但效果不理想,刷新时,感觉图片会有闪烁。

查了资料http://doc.trolltech.com/qq/qq06-flicker-free.html
可以利用double buffer缓存方法解决,但我却看不明白,能否指导一下,谢谢。

double buffer是不是指开一个新的空间,提前将所要显示的内容存储进去,然后再进行显示?因为我所要显示的是透明背景的图片,是否需要将背景和图片先进行叠加,然后再进行显示?如果需要叠加,那应该调用什么函数?
盼复,谢谢。


大致的代码,可以缩减为如下:

#include "t1111dialog.h"
#include <qtimer.h>
#include <qpixmap.h>

t1111Dialog::t1111Dialog( QWidget* parent, const char* name, bool modal, WFlags f )
: t1111DialogBase( parent, name, modal, f )
{
num = 0;
// Add your code
QTimer *movieTimer = new QTimer( this ); // create internal timer
connect( movieTimer, SIGNAL(timeout()), SLOT(timeout()) );
movieTimer->start( 500 ); // emit signal every 5 seconds
}


void t1111Dialog::timeout()
{
repaint();
}


void t1111Dialog::paintEvent( QPaintEvent *evt )
{
QPainter painter;
QPixmap image1("PICTURE-01.png");
QPixmap image2("PICTURE-02.png");
QPixmap image3("PICTURE-03.png");
QPixmap image4("PICTURE-04.png");

painter.begin(this);


switch(num){
case 0:
painter.drawPixmap(0,0,image1);
break;
case 1:
painter.drawPixmap(0,0,image2);
break;
case 2:
painter.drawPixmap(0,0,image3);
break;
case 3:
painter.drawPixmap(0,0,image4);
break;
default:
break;
}
painter.end();
num++;
if( num >= 4 )
num = 0;


}

double buffer建议你看看C++ Programming with Qt 3/4
里面都有专门的小节讲这个

你是想做动画?
想不闪烁建议用update
不要用repaint
这部分看看文档吧


谢谢站长的回复.

我去找下C++ Programming with Qt 3/4看一下.

但另外的你所指的文档,是指?QT 类的说明吗?

或者是否可以提供个网址给我,谢谢了.

我是想做动画,显示的图片是透明背景的,因为刚接触QT,所以调用的函数也显得很局限.

我原先一开始就是用update(),后来改用repaint()感觉没太大区别,呵呵.

另外,我用的是QDialog,这个跟用QWidget在调用double buffer处理闪烁问题应该没什么区别吧?

话罗嗦了点,烦请指教.

好好看看QWidget中有关paint(), update(), repaint()的介绍吧
快速回复
限100 字节
 
上一个 下一个