]> git.defcon.no Git - qopencamwidget/blob - qopencamwidget.cpp
Qt Widget that handles webcams :D
[qopencamwidget] / qopencamwidget.cpp
1 /*
2 This file is one part of two, that together make
3 QOpenCamWidget, a Qt 4 widget that displays video input
4 from a webcam, along with an optional snapshot button.
5
6 Copyright (C) 2009 Jon Langseth
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation in its version 2
11 of the License.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 */
22
23 #include "qopencamwidget.h"
24
25 // ------------------------------------------------------------- //
26 // Constructor and Destructor
27 // ------------------------------------------------------------- //
28
29 /*!
30 * \brief Consctructs a QWidget based widget for displaying video
31 * coming from an OpenCV capture source
32 *
33 * Including webcam data in a Qt application can be problematic,
34 * at least as long as Phonon does not support webcams, and the
35 * Phonon GStreamer backend only supports simple pipelines.
36 *
37 * This class solves the complexity of adding a webcam view,
38 * by using the cross-platform available OpenCV library.
39 *
40 * Limitations, i.e. reasons to read this code and reimplement,
41 * are: saving or streaming video is not really available (unless
42 * you do repeated timer-triggered connections to the startSnap slot),
43 * the widget size is identical to the video source dimensions (it
44 * resizes the wodget using setMinimuSize to the video dimensions,
45 * and does not handle resizing to sizes above this dimension.
46 *
47 * A brief summary of how to use this class:
48 * \code
49 * QOpenCamWidget *cw = new QOpenCamWidget(this);
50 * if ( cw->grabCapture(-1) ) {
51 * cw->setSnapshotVisible(true);
52 * cw->startCapture();
53 * }
54 * connect( cw, SIGNAL(imageReady(QImage)), this, SLOT(saveImage(QImage)));
55 * \endcode
56 *
57 * \param *parent The parent widget containing this widget, defaults to NULL.
58 *
59 **/
60 QOpenCamWidget::QOpenCamWidget(QWidget *parent)
61 : QWidget(parent)
62 {
63 // private CvCapture *nextFrame from class definition
64 nextFrame = NULL;
65 // private QTimer *frametimer from class definition
66 frametimer = NULL;
67 // private QVBoxLayout *layout from class definition
68 layout = new QVBoxLayout(this);
69
70 // private QLabel *canvas from class definition
71 canvas = new QLabel(this);
72 canvas->setMinimumSize(200, 100);
73 canvas->setAlignment(Qt::AlignCenter);
74
75 // private QPushButton *trigger from class definition
76 trigger = new QPushButton(this);
77 trigger->setText("Take picture");
78 trigger->setEnabled(false);
79 trigger->hide();
80
81 // private bool trigger_active from class definition
82 trigger_active = false;
83
84 layout->addWidget(canvas);
85 this->setLayout(layout);
86 }
87
88 QOpenCamWidget::~QOpenCamWidget(void)
89 {
90 cvReleaseCapture( &capture );
91 delete canvas;
92 delete trigger;
93 }
94 // ------------------------------------------------------------- //
95 // Public methods (not signals/slots)
96 // ------------------------------------------------------------- //
97
98 /*!
99 * \brief Changes the visibility of the optional built-in "Take snapshot" button.
100 *
101 * The widget contains a push-button that optionally can be displayed. When
102 * visible, this button is located at the bottom of the widget, and causes
103 * the SLOT QOpenCamWidget::startSnap to be triggered when clicked.
104 *
105 * \param visible True makes the button display, and trigger,
106 * false turns the feature off. False, i.e. no button, is default.
107 *
108 **/
109 void
110 QOpenCamWidget::setSnapshotVisible(bool visible)
111 {
112 // Checking both parameter and private "sanity" variable,
113 // as a sanity- and error-control
114 if ( visible && !trigger_active )
115 {
116 connect( trigger, SIGNAL(clicked()), this, SLOT(startSnap()));
117 layout->addWidget(trigger);
118 trigger->show();
119 trigger_active = true;
120 }
121 if ( !visible && trigger_active )
122 {
123 layout->removeWidget(trigger);
124 disconnect( trigger, SIGNAL(clicked()), 0, 0 );
125 trigger->hide();
126 trigger_active = false;
127 }
128 }
129 /*!
130 * \brief A paint event is a request to repaint all or part of a widget.
131 *
132 * It can happen for one of the following reasons:
133 *
134 * \li repaint() or update() was invoked,
135 * \li the widget was obscured and has now been uncovered, or
136 * \li many other reasons.
137 *
138 * QOpenCamWidget uses the paintEvent to draw each frame onto the screen.
139 * The paintEvent itself is regularily triggered by explicit update()
140 * calls in QOpenCamWidget::grabFrame().
141 *
142 **/
143 void
144 QOpenCamWidget::paintEvent ( QPaintEvent * event )
145 {
146 QPainter * paint = new QPainter;
147 paint->begin(this);
148
149 if ( nextFrame )
150 {
151 // Using this objects painter to draw the image
152 // causes a cute, but not desired effect..
153 //paint->drawImage(event->rect(), *nextFrame);
154
155 // Until I find a better solution on how to do the
156 // redraw of the image, setPixmap on the canvas
157 // does do the job. This may cause a performance
158 // penalty though, and is considered a FIXME
159 // Another drawback is that the widget does not resize..
160 canvas->setPixmap( QPixmap::fromImage(*nextFrame) );
161 }
162 else
163 {
164 canvas->setText("No data, check/test camera");
165 }
166 paint->end();
167 }
168
169
170 /*!
171 * \brief Grabs an OpenCV video capture source
172 *
173 * By grabbing a source, it is meant to open the capture source,
174 * and have it ready to start streaming/capturing frames.
175 * Returns true on success, false on error. The grabCapture
176 * is separated from the constructor and/or frame-grabbing, so that you
177 * may do the error-checking you really should do before proceeding.
178 *
179 * \param source The OpenCV capture source enumeration index to open
180 *
181 **/
182 bool
183 QOpenCamWidget::grabCapture(int source)
184 {
185 capture = cvCaptureFromCAM(0);
186 if (!capture)
187 {
188 qDebug() << "QOpenCamWidget::grabCapture(" << source << ") failed";
189 return false;
190 }
191 cvGrabFrame(capture); // Grab a single frame, do resizing based on it.
192 IplImage *image = cvRetrieveFrame(capture);
193 QSize t_size = QSize(image->width,image->height);
194
195 qDebug() << "Device image format: " << image->width << "x" << image->height;
196 canvas->setMinimumSize(t_size);
197 canvas->setMaximumSize(t_size);
198
199 return true;
200 }
201
202 /*!
203 * \brief Starts up grabbing of video frames
204 *
205 * The actual grabbing and displaying of video frames is performed by
206 * a QTimer triggering the SLOT QOpenCamWidget::grabFrame().
207 * startCapture() sets up the timer running this captureFrame loop.
208 *
209 * The SLOT QOpenCamWidget::startSnap() is used to get image frames
210 * out from the widget for other uses, like saving or processing.
211 * This function relies on the timer created and configured by
212 * startCapture(), and as such, this function is the only permitted
213 * way to start the actual capture/streaming of video from the source.
214 *
215 **/
216 void
217 QOpenCamWidget::startCapture(void)
218 {
219 frametimer = new QTimer(this);
220 frametimer->start(70);
221 connect(frametimer,SIGNAL(timeout()), this,SLOT(grabFrame()));
222 trigger->setEnabled(true);
223 }
224
225 /*!
226 * \brief Converts from the OpenCV IplImage data structure to a QImage
227 *
228 * OpenCV uses a data strcuture calles IplImage, optimized for
229 * computer vision image processing tasks. This code was adapted
230 * from kcamwidget.cpp, part of the KDE SVN at
231 * playground/multimedia/kcam/kcamwidget.cpp
232 *
233 * In regard that the IplImage can be forced into a format
234 * that aligns well with a RBG888-format, the conversion
235 * becomes one of the shortes, simples IplImage->QImage I've seen.
236 *
237 * \param *img The IplImage to be converted to a QImage.
238 *
239 **/
240 QImage*
241 QOpenCamWidget::Ipl2QImage(const IplImage *img)
242 {
243 IplImage *tmp=cvCloneImage(img);
244 cvConvertImage(img,tmp, CV_CVTIMG_SWAP_RB );
245 QImage * qimage = new QImage(reinterpret_cast<uchar*>(tmp->imageData),
246 tmp->width,
247 tmp->height,
248 3* tmp->width,
249 QImage::Format_RGB888);
250 return qimage;
251
252 }
253 // ------------------------------------------------------------- //
254 // Public SLOTS
255 // ------------------------------------------------------------- //
256
257 /*!
258 * \brief Grabs a frame and causes an update() when triggered.
259 *
260 * This is the SLOT that actually reads the video source and
261 * causes the widget to display live video. Preferably this
262 * slot will never be called my any other signal that a timeout()
263 * on the frametimer, which is controlled by QOpenCamWidget::startCapture()
264 *
265 **/
266 void QOpenCamWidget::grabFrame(void)
267 {
268 if ( !capture )
269 {
270 qDebug() << "Capture device not ready!";
271 return;
272 }
273
274 cvGrabFrame(capture);
275
276 IplImage *iplimage = cvRetrieveFrame(capture);
277
278 if (iplimage)
279 {
280 nextFrame = Ipl2QImage(iplimage);
281 }
282 else
283 {
284 nextFrame = NULL;
285 }
286 update();
287 }
288
289 /*!
290 * \brief Trigger this slot to save a frame from the widget.
291 *
292 * When this slot is triggered, the widgets capture loop is temporarily
293 * stopped, and the last displayed frame is "captured", and made
294 * available through the emitting of the class imageReady SIGNAL.
295 *
296 * With the "Take snapshot" button visible (setSnapshotVisible(true)),
297 * this SLOT is triggered when the user clicks on the trigger button.
298 * If you do not wish to use the internal trigger button, you
299 * will have to add a different mechanism to trigger this SLOT.
300 *
301 * It is possible, though I would not recommend, to use repeated
302 * triggering of this slot to do repeated frame-capture, and thus
303 * make a form of "Animation" or "Video" capture.
304 *
305 **/
306 void QOpenCamWidget::startSnap(void)
307 {
308 if ( frametimer ) {
309 if (frametimer->isActive())
310 {
311 frametimer->stop();
312 qDebug() << "SNAP!";
313
314 emit imageReady(QImage(*nextFrame));
315 frametimer->start();
316 }
317 }
318 }
319