]> git.defcon.no Git - qopencamwidget/blob - qopencamwidget.cpp
Updated structure of documentation. No need to include GDB includes and Qt Core/GUI...
[qopencamwidget] / qopencamwidget.cpp
1 /*
2 This file is one part of two, that together make QOpenCamWidget,
3 a Qt 4 widget that displays video input from a webcam.
4
5 Copyright (C) 2009 Jon Langseth
6
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation in its version 2
10 of the License.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #include "qopencamwidget.h"
23
24 // ------------------------------------------------------------- //
25 // Constructor and Destructor
26 // ------------------------------------------------------------- //
27
28 /*!
29 * \brief Consctructs a QWidget based widget for displaying video
30 * coming from an OpenCV capture source
31 *
32 * Including webcam data in a Qt application can be problematic,
33 * at least as long as Phonon does not support webcams, and the
34 * Phonon GStreamer backend only supports simple pipelines.
35 *
36 * This class solves the complexity of adding a webcam view,
37 * by using the cross-platform available OpenCV library.
38 *
39 * Limitations, i.e. reasons to read this code and reimplement, are:
40 *
41 * \li saving or streaming video is not really available (unless you do
42 * repeated timer-triggered connections to the startSnap slot).
43 * \li this is a crude and simple implementation, created to solve
44 * the problem of creating a "view-and-shoot" camera app.
45 * If your needs are more complex than that, read the code,
46 * and reimplement.
47 *
48 * Note that even tough the main file here totals some ~250 lines,
49 * only about 90 of those are actual code lines, and and even those
50 * contain a lot of "air"...
51 *
52 * A brief summary of how to use this class:
53 * \code
54 * QOpenCamWidget *cw = new QOpenCamWidget(this);
55 * if ( cw->grabCapture(-1) ) {
56 * cw->startCapture();
57 * }
58 * connect( cw, SIGNAL(imageReady(QImage)), this, SLOT(saveImage(QImage)));
59 *
60 * QPushButton *trigger = new QPushButton(this);
61 * trigger->setText("Take picture");
62 * connect( trigger, SIGNAL(clicked()), cw, SLOT(startSnap()));
63 *
64 * \endcode
65 *
66 * Setting sizes is done during construction, and are not modifiable
67 * after the fact:
68 * \code
69 * QOpenCamWidget *cw = new QOpenCamWidget(this,
70 * new QSize(320, 240),
71 * new QSize(960, 720));
72 * \endcode
73 *
74 * \param *parent The parent widget containing this widget, defaults to NULL.
75 * \param *viewSize The size of the image displayed, thus the size of the widget,
76 * defaults to 640x480
77 * \param *resolution The actual resolution captured, and size of the snapshot
78 * created, defaults t0 960x720, should be larger than viewSize
79 *
80 **/
81 QOpenCamWidget::QOpenCamWidget(QWidget *parent, QSize *viewSize, QSize *resolution)
82 : QWidget(parent)
83 {
84 // Setting sane default values (i.e. NULL) for
85 // private CvCapture *nextFrame and
86 // private QTimer *frametimer from class definition
87 nextFrame = NULL;
88 frametimer = NULL;
89 if ( viewSize == NULL )
90 {
91 vSize = new QSize(640, 480);
92 }
93 else vSize = viewSize;
94 if ( resolution == NULL )
95 {
96 res = new QSize(960, 720);
97 }
98 else res = resolution;
99
100 this->setMinimumSize(*vSize);
101 this->setMaximumSize(*vSize);
102
103 }
104
105 QOpenCamWidget::~QOpenCamWidget(void)
106 {
107 cvReleaseCapture( &capture );
108 }
109 // ------------------------------------------------------------- //
110 // Public methods (not signals/slots)
111 // ------------------------------------------------------------- //
112
113 /*!
114 * \brief A paint event is a request to repaint all or part of a widget.
115 *
116 * It can happen for one of the following reasons:
117 *
118 * \li repaint() or update() was invoked,
119 * \li the widget was obscured and has now been uncovered, or
120 * \li many other reasons.
121 *
122 * QOpenCamWidget uses the paintEvent to draw each frame onto the screen.
123 * The paintEvent itself is regularily triggered by explicit update()
124 * calls in QOpenCamWidget::grabFrame().
125 *
126 **/
127 void
128 QOpenCamWidget::paintEvent ( QPaintEvent * event )
129 {
130 QPainter * paint = new QPainter;
131 paint->begin(this);
132
133 if ( nextFrame )
134 {
135 // To make the widget as blazingly fast as possible
136 // we output the last captured frame direclty onto
137 // the widget area.
138 // It has been slowed down a bit by doing a resize tho...
139 paint->drawImage(event->rect(), nextFrame->scaled( vSize->width(), vSize->height() ));
140 }
141 else
142 {
143 paint->drawText(event->rect(),"No data, check/test camera");
144 }
145 paint->end();
146 // Clean up..
147 delete(paint);
148 }
149
150
151 /*!
152 * \brief Grabs an OpenCV video capture source
153 *
154 * By grabbing a source, it is meant to open the capture source,
155 * and have it ready to start streaming/capturing frames.
156 * Returns true on success, false on error. The grabCapture
157 * is separated from the constructor and/or frame-grabbing, so that you
158 * may do the error-checking you really should do before proceeding.
159 *
160 * \param source The OpenCV capture source enumeration index to open
161 *
162 **/
163 bool
164 QOpenCamWidget::grabCapture(int source)
165 {
166 capture = cvCreateCameraCapture(0);
167 if (!capture)
168 {
169 qDebug() << "QOpenCamWidget::grabCapture(" << source << ") failed";
170 return false;
171 }
172 cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_WIDTH, res->width() );
173 cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_HEIGHT, res->height() );
174
175 cvGrabFrame(capture); // Grab a single frame, do resizing based on it.
176 IplImage *image = cvRetrieveFrame(capture);
177
178 return true;
179 }
180
181 /*!
182 * \brief Starts up grabbing of video frames
183 *
184 * The actual grabbing and displaying of video frames is performed by
185 * a QTimer triggering the SLOT QOpenCamWidget::grabFrame().
186 * startCapture() sets up the timer running this captureFrame loop.
187 *
188 * The SLOT QOpenCamWidget::startSnap() is used to get image frames
189 * out from the widget for other uses, like saving or processing.
190 * This function relies on the timer created and configured by
191 * startCapture(), and as such, this function is the only permitted
192 * way to start the actual capture/streaming of video from the source.
193 *
194 **/
195 void
196 QOpenCamWidget::startCapture(void)
197 {
198 frametimer = new QTimer(this);
199 frametimer->start(50);
200 connect(frametimer,SIGNAL(timeout()), this,SLOT(grabFrame()));
201 }
202
203 /*!
204 * \brief Converts from the OpenCV IplImage data structure to a QImage
205 *
206 * OpenCV uses a data strcuture calles IplImage, optimized for
207 * computer vision image processing tasks. This code was adapted
208 * from kcamwidget.cpp, part of the KDE SVN at
209 * playground/multimedia/kcam/kcamwidget.cpp
210 *
211 * In regard that the IplImage can be forced into a format
212 * that aligns well with a RBG888-format, the conversion
213 * becomes one of the shortes, simples IplImage->QImage I've seen.
214 *
215 * \param *img The IplImage to be converted to a QImage.
216 *
217 **/
218 QImage*
219 QOpenCamWidget::Ipl2QImage(IplImage *img)
220 {
221 cvConvertImage(img,img, CV_CVTIMG_SWAP_RB);
222 QImage * qimage = new QImage(
223 reinterpret_cast<uchar*>(img->imageData),
224 img->width, img->height,
225 3* img->width, QImage::Format_RGB888);
226 return qimage;
227
228 }
229 // ------------------------------------------------------------- //
230 // Public SLOTS
231 // ------------------------------------------------------------- //
232
233 /*!
234 * \brief Grabs a frame and causes an update() when triggered.
235 *
236 * This is the SLOT that actually reads the video source and
237 * causes the widget to display live video. Preferably this
238 * slot will never be called my any other signal that a timeout()
239 * on the frametimer, which is controlled by QOpenCamWidget::startCapture()
240 *
241 **/
242 void QOpenCamWidget::grabFrame(void)
243 {
244 if ( !capture ) { qDebug() << "Capture device not ready!"; return; }
245
246 cvGrabFrame(capture);
247
248 IplImage *iplimage = cvRetrieveFrame(capture);
249 if (iplimage)
250 {
251 nextFrame = Ipl2QImage(iplimage);
252 }
253 else
254 {
255 nextFrame = NULL;
256 }
257
258 update();
259 }
260
261 /*!
262 * \brief Trigger this slot to save a frame from the widget.
263 *
264 * When this slot is triggered, the widgets capture loop is temporarily
265 * stopped, and the last displayed frame is "captured", and made
266 * available through the emitting of the class imageReady SIGNAL.
267 *
268 * It is possible, though I would not recommend, to use repeated
269 * triggering of this slot to do repeated frame-capture, and thus
270 * make a form of "Animation" or "Video" capture.
271 *
272 **/
273 void QOpenCamWidget::startSnap(void)
274 {
275 if ( frametimer ) {
276 if (frametimer->isActive())
277 {
278 frametimer->stop();
279 emit imageReady(QImage(*nextFrame));
280 frametimer->start();
281 }
282 }
283 }
284