This post shows a form control widget that performs validation on its input. The invalid input is highlighted in red and the error message is display below the input. The input is validated in real-time (as the text is changed) when invalid the submit button is disabled and when valid it is enabled. The desired effect is shown below:
The Coding Exodus
Saturday, 16 June 2018
Saturday, 9 June 2018
Order Independent Transparency
Transparency in OpenGL is usually achieved using blending. Good looking results can be achieved by changing the blend function used and in the process we can avoid advanced techniques that require depth sorting.
In this post, I show how to achieve order independent transparency with python and OpenGL. The desired effect is shown below:
Saturday, 2 June 2018
Styling QT Applications: Button Tabs
This post demonstrates how to create custom tabs using QPushButtons in a QButtonGroup. The code below is self explanatory, the button group ensures that only a single button can be selected at one time. The buttons are made to look more like tabs using QT style sheet and the final result is shown below
Friday, 25 May 2018
Add checkbox in QTableView header using icons
There is no API to insert a checkbox into the header of a QTableView. The recommended way of achieving this is to subclass the QHeaderView and draw the checkbox in the paintSection() method which is in my opinion overkill for such a simple feature.
In this post, I show how to achieve a similar effect by simply adding icons in the header. The desired effect is shown below:
Thursday, 20 June 2013
Moment-Based De-skewing
I needed to discover the orientation of an object in a binary image then de-skew the image. Hough transform would not work for me cause I needed speed so I opted for the use of image moments. Using second order central moments, it is possible to calculate the angle that the major axis of the object forms with the horizontal axis. The algorithm used can determine this angle to within ±90 degrees. So let get started.
Code
import cv2 from math import atan, pi, ceil img = cv2.imread('mask3.jpg', 0) h,w = img.shape[:2] cv2.imshow('original', img) m = cv2.moments(img) # You can read about Image Moments # Chapter 18 Shape analysis - Page 633 # Digital Image Processing 4th Edition By William K. Pratt # OR http://en.wikipedia.org/wiki/Image_moment x = m['m10']/m['m00'] y = m['m01']/m['m00'] mu02 = m['mu02'] mu20 = m['mu20'] mu11 = m['mu11'] lambda1 = 0.5*( mu20 + mu02 ) + 0.5*( mu20**2 + mu02**2 - 2*mu20*mu02 + 4*mu11**2 )**0.5 lambda2 = 0.5*( mu20 + mu02 ) - 0.5*( mu20**2 + mu02**2 - 2*mu20*mu02 + 4*mu11**2 )**0.5 lambda_m = max(lambda1, lambda2) # Convert from radians to degrees angle = atan((lambda_m - mu20)/mu11)*180/pi print angle # Create a rotation matrix and use it to de-skew the image. center = tuple(map(int, (x, y))) rotmat = cv2.getRotationMatrix2D(center, angle , 1) rotatedImg = cv2.warpAffine(img, rotmat, (w, h), flags = cv2.INTER_CUBIC) cv2.imshow('final', rotatedImg) cv2.waitKey()
Results
I would not be explaining the maths behind Image moments, if you care to know more check the reference in the code. Nothing explains code better than some tests! Here are a few test images and the de-skewed resulting images.Original Image | De-skewed Image | Orientation (degree) |
---|---|---|
-89.99 | ||
-0.0 | ||
-39.03 | ||
46.95 | ||
3.57 |
Conclusion
As you can see the algorithm works pretty well, an anticlockwise skew returns a negative angle and a clockwise skew returns a positive angle. Be aware the results you get will vary based on the quality of the segmented mask. Low quality (see last test image) segmentation would change the centre of the mask and this would after the calculated angle. So depending on your case you might need some pre-processing before de-skewing. Please feel free to post questions in the comments. Happy Coding!
Monday, 27 May 2013
Working with Video Using OpenCV and QT - Part 2
This tutorial was written because of a request from a previous tutorial. In this tutorial, we improve upon the work done in that tutorial by adding a track-bar and display duration of the video. Also a few errors, I discovered will also be corrected in this tutorial. So let get cracking.
Add widgets to GUI
This is an extension to a previous tutorial so I will only point out changes or additions to the previous work so as not to repeat myself. Open the mainwindow.ui file, this file
could be edited manually but for this tutorial we would use the
designer.
- Add a horizontal track-bar to the GUI, this would be used to the adjust the position in the video.
- Add two labels to the GUI; place one on the left and another on the right of the horizontal track-bar ( or however suits your needs) the left one would show the current time into the video whereas the right one would be used to show the total time of the video. The GUI should now look similar to the image above.
Player Class Definition
Now we add a few function definitions to Player class header file -player.h.
#ifndef PLAYER_H #ifndef PLAYER_H #define PLAYER_H #include <QMutex> #include <QThread> #include <QImage> #include <QWaitCondition> #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> using namespace cv; class Player : public QThread { Q_OBJECT private: .... VideoCapture *capture; Mat RGBframe; QImage img; signals: ...... protected: ...... public: ....... //set video properties void setCurrentFrame( int frameNumber); //Get video properties double getFrameRate(); double getCurrentFrame(); double getNumberOfFrames(); }; #endif // VIDEOPLAYER_H
The class definition is still simple and straightforward. We add a few public setter and getter functions to enable us grab some important video parameters. Also I change the capture variable to a pointer I discovered that it was not possible to reload a new video once one has been loaded so to correct this I initialize a new VideoCapture instance when a new video is loaded. So the method used to access the VideoCapture instance members must be changed from the "." to the "->" notation.
Player Class Implementation
Here is the constructor for the Player class.
bool Player::loadVideo(string filename) { capture = new cv::VideoCapture(filename); if (capture->isOpened()) { frameRate = (int) capture->get(CV_CAP_PROP_FPS); return true; } else return false; } void Player::run() { int delay = (1000/frameRate); while(!stop){ if (!capture->read(frame)) { stop = true; } ..... } }
In the loadVideo() method, we use the instance of the VideoCapture class to load the video and set the frame rate. As you should already know the VideoCapture class is from the OpenCV library
double Player::getCurrentFrame(){ return capture->get(CV_CAP_PROP_POS_FRAMES); } double Player::getNumberOfFrames(){ return capture->get(CV_CAP_PROP_FRAME_COUNT); } double Player::getFrameRate(){ return frameRate; } void Player::setCurrentFrame( int frameNumber ) { capture->set(CV_CAP_PROP_POS_FRAMES, frameNumber); }
Here are the getter and setter function to access the video information. I ran into a glitch, with ffmpeg where the capture->get(CV_CAP_PROP_FRAME_COUNT) function returned the wrong frame count. The solution might be to update my ffmpeg. I would update the post once the solution is confirmed.
Player::~Player() { mutex.lock(); stop = true; capture.release(); delete capture; condition.wakeOne(); mutex.unlock(); wait(); }
Here is the rest of the Player class, in the destructor we release the VideoCapture object, also we allocated memory with the new keyword it must be freed.
MainWindow Class Definition
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QFileDialog> #include <QMessageBox> #include <player.h> #include <QTime> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: ....... private slots: ......... QString getFormattedTime(int timeInSeconds); void on_horizontalSlider_sliderPressed(); void on_horizontalSlider_sliderReleased(); void on_horizontalSlider_sliderMoved(int position); private: ........ }; #endif // MAINWINDOW_H
Here is the class definition for the MainWindow class, we include the 3 event slots for the new horizontal slider and a update some other functions
Mainwindow class implementation
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ....... ui->pushButton_2->setEnabled(false); ui->horizontalSlider->setEnabled(false); }
We disable the play button and the horizontal slider; these would be enabled once a video is loaded.
void MainWindow::updatePlayerUI(QImage img) { if (!img.isNull()) { ui->label->setAlignment(Qt::AlignCenter); ui->label->setPixmap(QPixmap::fromImage(img).scaled(ui->label->size(), Qt::KeepAspectRatio, Qt::FastTransformation)); ui->horizontalSlider->setValue(myPlayer->getCurrentFrame()); ui->label_2->setText( getFormattedTime( (int)myPlayer->getCurrentFrame()/(int)myPlayer->getFrameRate()) ); } }
The updatePlayerUI slot receives a QImage and resizes it to fit the label (keeping the aspect ratio) which will be used to display. It displays the image by setting the label pixmap. We also update the horizontal slider position and the label that displays the elapsed time. We calculate the duration of the video but dividing the total number of frames by the frame rate.
void MainWindow::on_pushButton_clicked() { QString filename = QFileDialog::getOpenFileName(this, tr("Open Video"), ".", tr("Video Files (*.avi *.mpg *.mp4)")); QFileInfo name = filename; if (!filename.isEmpty()){ if (!myPlayer->loadVideo(filename.toAscii().data())) { QMessageBox msgBox; msgBox.setText("The selected video could not be opened!"); msgBox.exec(); } else{ this->setWindowTitle(name.fileName()); ui->pushButton_2->setEnabled(true); ui->horizontalSlider->setEnabled(true); ui->horizontalSlider->setMaximum(myPlayer->getNumberOfFrames()); ui->label_3->setText( getFormattedTime( (int)myPlayer->getNumberOfFrames()/(int)myPlayer->getFrameRate()) ); } } } QString MainWindow::getFormattedTime(int timeInSeconds){ int seconds = (int) (timeInSeconds) % 60 ; int minutes = (int) ((timeInSeconds / 60) % 60); int hours = (int) ((timeInSeconds / (60*60)) % 24); QTime t(hours, minutes, seconds); if (hours == 0 ) return t.toString("mm:ss"); else return t.toString("h:mm:ss"); } void MainWindow::on_horizontalSlider_sliderPressed() { myPlayer->Stop(); } void MainWindow::on_horizontalSlider_sliderReleased() { myPlayer->Play(); } void MainWindow::on_horizontalSlider_sliderMoved(int position) { myPlayer->setCurrentFrame(position); ui->label_2->setText( getFormattedTime( position/(int)myPlayer->getFrameRate()) ); }
This is the remaining part of the MainWindow Class, the getFormattedTime function takes the time in seconds and formats it for display, the rest is self explanatory. You can Download the full code Here.
Final words...
This is just a simple tutorial to help anyone get started with videos in OpenCV and QT. Please let me know if this was helpful and ask questions and give suggestions(if any) in the comments. Happy Coding!
Wednesday, 24 April 2013
How to make a custom QT project template
In this article, we will learn how to create a project template in QT creator . Why would I need custom QT project template you ask? If you work with multiple external libraries like OpenCV, QWT etc, you would have to manually add the libraries' path into the project file for every new project that requires them; this quickly becomes an inconvenience if you have a lot of projects and you don't know the configuration by heart. So lets get started.
QT Template
This tutorial is not intended to discuss QT templates in-depth for such a tutorial you would need to refer to the QT documentation. Rather, I have created a sample custom QT project template and would explain the needed alteration so you can use this to create your own template. As soon as you have gotten the sample from here and extracted the contents you should see something similar to the default QT GUI project except for wizard.xml. Before I get into what that does, let's see how to install this project template.
Installing the custom QT project template
After extracting the folder containing the project template, you would need to copy this folder into the share/qtcreator/templates/wizards/ folder in your QtCreator folder, the location of this folder will vary depending on where you installed QT. Also depending on where you installed QtCreator you might need root permission on a Linux machine before copying. Once copied, you can launch Qt creator and open the new Project wizard you should see a new project category and a new project type. selecting this new type would open a wizard which would contain the regular pages used to create a project with an extra page titled "Add External Libraries" in this sample I have included check-boxes for OpenCV and QWT selecting anyone of these would include the specified library into the created project.
How does this work?
The major job is done in wizard.xml. Most of the xml elements are self explanatory...for deeper knowledge about each element checkout the QT documentation. More importantly the <files> element allows you to specify what files you want to be added to the project by changing this you can convert the template into a console project template or any other project. Equally important is the <fields> element which allow us to add a new wizard page and throw in some check-boxes via the <field> tags. Each field has a name, a control and description.
<wizard version="1" kind="project" class="qt4project" firstpage="10" id="A.customqtproject" category="B.CustomProjects"> <icon>console.png</icon> <description>Creates a custom Qt project which allows you to include external libraries.</description> <displayname>Custom QT Projects</displayname>; <displaycategory>Custom Projects</displaycategory> <files> <file source="main.cpp" openeditor="true"/> <file source="mainwindow.cpp" openeditor="false"/> <file source="mainwindow.h" openeditor="false"/> <file source="mainwindow.ui" openeditor="false"/> <file source="project.pro" target="%ProjectName%.pro" openproject="true"/> </files> <!-- Create a 2nd wizard page with parameters --> <fieldpagetitle>Add External Libraries</fieldpagetitle> <fields> <field name="OPENCV"> <fieldcontrol class="QCheckBox" truevalue="" falsevalue="# "/> <fielddescription>Include OpenCV Libraries</fielddescription> </field> <field name="QWT"> <fieldcontrol class="QCheckBox" truevalue="" falsevalue="# "/> <fielddescription>Include QWT Libraries</fielddescription> </field> </fields> </wizard>
A few alterations were made to the project.pro file. We append the following lines to the bottom of the default QT GUI project file.
# Opencv Library %OPENCV%INCLUDEPATH += "/usr/local/include/opencv2" %OPENCV%LIBS += `pkg-config --cflags --libs opencv` # Qwt library %QWT%CONFIG += qwt %QWT%INCLUDEPATH +="/usr/local/qwt-6.1.0-rc3/include" %QWT%LIBS += -L/usr/local/qwt-6.1.0-rc3/lib -lqwt
The first two lines are the configuration for an OpenCv project on my machine. You will need to change this to suit your system. Note that the configuration is preceded by name of the field for the configuration inserted between two percentage signs. To add more libraries to your template you simply add more fields to the wizard and add the configuration for that library to the .pro file preceded by the name of the controlling field.When a new project is created using this template the wizard simply checks which field is not selected and it comments that out but if it is selected it is included.
Conclusion
This tutorial has shown just one way to make a custom QT project template. I chose this method because it is typical for me to start a project with one library and half way into the project remember I need another library so with this method all I need to do is uncomment the desired library in the project file. Happy and hopefully faster coding!
Tuesday, 23 April 2013
How to install OpenCV on OpenShift
I wanted to deploy an OpenCV(Python) application on the internet and needed a free hosting service after searching I found OpenShift which is an Platform as a service provider. They offer a variety of application type (python, ruby, php, django etc.) The free tier allow you to host 3 application with 1GB of storage. So it took me weeks before I could get OpenCV installed properly with no errors - one major problem was storage constraints so I had to delete any file that was unimportant and install only the necessities. After the whole thing was over I thought it would be nice to write about it.
Sign up and create an application
The first step would be to sign up for an OpenShift account. Then create a new application select a Do-It-Yourself (custom) application and complete the wizard. After the wizard is completed you would need to go to the application page and copy the secure shell (ssh) session command which you can access by clicking the "WANT TO LOG IN TO YOUR APPLICATION?" link.
Having copied the command, open a terminal and paste it to get started.
Installation
Disclaimer: From here on you're going to be looking at loads of scrolling text. So if you are allergic to boredom get a drink or open up a movie on the side. Copy the scripts below and paste in the command line. Note that I delete the apps , data and doc folders from the OpenCV installation so as to save space this could be a problem if you need haarcascade.xml or any stuff located in these folders, so if that is your case you must check and customize your installation from line 57 of the script but be careful to avoid the "Disk quota exceeded" error.# Install python cd $OPENSHIFT_TMP_DIR wget http://python.org/ftp/python/2.7.3/Python-2.7.3.tar.bz2 tar jxf Python-2.7.3.tar.bz2 cd Python-2.7.3 ./configure --prefix=$OPENSHIFT_DATA_DIR --enable-shared make make altinstall export PATH=$OPENSHIFT_DATA_DIR/bin:$PATH export LD_LIBRARY_PATH=$OPENSHIFT_DATA_DIR/lib # Install setuptools cd $OPENSHIFT_TMP_DIR wget --no-check-certificate https://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11-py2.7.egg#md5=fe1f997bc722265116870bc7919059ea sh setuptools-0.6c11-py2.7.egg export PATH=$OPENSHIFT_DATA_DIR/bin:$PATH export LD_LIBRARY_PATH=$OPENSHIFT_DATA_DIR/lib # Install pip cd $OPENSHIFT_TMP_DIR wget --no-check-certificate http://pypi.python.org/packages/source/p/pip/pip-1.1.tar.gz tar zxf pip-1.1.tar.gz cd pip-1.1 python2.7 setup.py install --prefix=$OPENSHIFT_DATA_DIR/lib/python2.7/site-packages/ python2.7 setup.py install export PATH=$OPENSHIFT_DATA_DIR/bin:$PATH export LD_LIBRARY_PATH=$OPENSHIFT_DATA_DIR/lib # Install numpy $OPENSHIFT_DATA_DIR/bin/pip install numpy # House-cleaning cd $OPENSHIFT_TMP_DIR rm -r Python-2.7.3 rm Python-2.7.3.tar.bz2 rm -r pip-1.1/ rm pip-1.1.tar.gz rm setuptools-0.6c11-py2.7.egg # Install CMake cd $OPENSHIFT_TMP_DIR wget http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz tar xvf cmake-2.8.10.2.tar.gz cd cmake-2.8.10.2 ./configure --prefix=$OPENSHIFT_DATA_DIR gmake gmake install # Download OPenCV cd $OPENSHIFT_TMP_DIR wget http://sourceforge.net/projects/opencvlibrary/files/opencv-unix/2.4.3/OpenCV-2.4.3.tar.bz2/download tar xvf OpenCV-2.4.3.tar.bz2 # More House-cleaning rm cmake-2.8.10.2.tar.gz rm OpenCV-2.4.3.tar.bz2 cd OpenCV-2.4.3/ rm -r 3rdparty/ rm -r android rm -r doc rm -r data rm -r ios rm -r samples rm -r apps rm README rm -r ../cmake-2.8.10.2/Tests rm -r ../cmake-2.8.10.2/Testing rm -r ../cmake-2.8.10.2/Example rm -r ../cmake-2.8.10.2/Docs rm -r ../cmake-2.8.10.2/Source
Congratulations you are now half way through the process, But now you have to do something before installing OpenCV. To reduce the build size; we turn off build tests and remove the lines that copy the data, apps and doc folders. Just follow these steps:
- Open CMakeList.txt in vi editor using the command vi CMakeList.txt.
- Show line numbers using the :set number command.
- Go to Lines 155 and 156 and turn off regression and performance test.
- Delete Lines 448 to 456.
- Save and Quit using the :wq command.
mkdir release cd release $OPENSHIFT_DATA_DIR/bin/cmake cmake ../OpenCV-2.4.3 -D BUILD_NEW_PYTHON_SUPPORT=ON -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=$OPENSHIFT_DATA_DIR -D PYTHON_LIBRARY=$OPENSHIFT_DATA_DIR/lib/libpython2.7.so -D CMAKE_INCLUDE_PATH=$OPENSHIFT_DATA_DIR/include/python2.7 -D PYTHON_INCLUDE_DIR=$OPENSHIFT_DATA_DIR/include/python2.7 -D PYTHON_PACKAGES_PATH=$OPENSHIFT_DATA_DIR/lib/python2.7/site-packages -D PYTHON_EXECUTABLE=$OPENSHIFT_DATA_DIR/bin/python2.7 -D WITH_OPENEXR=OFF -D BUILD_DOCS=OFF -DBUILD_SHARED_LIBS=ON .. make make install
Test
Open the python console by typing python2.7 in the command line. import cv2, if you have no error you are good to go but if you have a "NameError: name 'cv2' is not defined" something is wrong.
Python 2.7.3 (default, May 28 2013, 07:58:37) [GCC 4.4.7 20120313 (Red Hat 4.4.7-3)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import cv2 >>> A = cv2.imread("A.jpg") >>> print A None
Conclusion
This concludes the installation you can now clean up the $OPENSHIFT_TMP folder and install Django or any other web framework you want to use. Hope this was helpful, feel free to share this post and ask question if any. Happy Coding!
Tuesday, 16 April 2013
The VMMR Project - Part 1
So you ask what is a VMMR? VMMR is an acronym for Vehicle Make and Model Recognition. A VMMR system utilizes computer vision techniques to determine the Make and Model of a vehicle e.g. BMW X3, FORD Focus etc.
The VMMR system could also provide improved surveillance and tracking in the event of crime. Most times when a crime is committed and the criminal escapes using a vehicle. The police officers collect a description of the criminal and the escape vehicle. These officers now have to review large amounts of surveillance videos to find the criminal using the given vehicle description. With a VMMR system, the amounts of video to be watched could be reduced to the few places where a particular make and model has been discovered thereby speeding up the capture of the criminal.
Another use of VMMR could for marketing research; a company might decide to do a survey of about the vehicles they manufacture or sell. They wanted to find out how many of such vehicles are being used in a region and target their marketing and advertising based on the results of the survey. A VMMR system can provide an automated way of performing such a survey and returning very accurate results. Now you know how important this project is ............sure you do. Now what's this project about?
Why VMMR?
Several vehicle monitoring and security systems are based on automated number plate recognition (ANPR), for example, an ANPR systems could be used to prevent illegal entry into a particular location, or to enforce traffic laws or taxes in a city and could also be used to track vehicles in the event of any crime. One of the available ways of circumventing monitoring and security systems that are based on the ANPR is number plate forgery; here the person involved clones an already registered number plate so as to either gain entrance into a facility as the original owner of the number plate or to evade tracking after committing a criminal offence. Because of number plate forgery, ANPR systems are not sufficient to ensure proper security. One way to solve the problem of number plate forgery would be to argument existing security systems that use ANPR with a vehicle make and model recognition (VMMR) system.With such a system, it will be possible to match the vehicle’s plate numbers with the pre-registered make, model, and even colour of the vehicle. This will the make number plate forgery a more difficult task although not completely impossible. Other possible applications of VMMR are tracking and marketing research.The VMMR system could also provide improved surveillance and tracking in the event of crime. Most times when a crime is committed and the criminal escapes using a vehicle. The police officers collect a description of the criminal and the escape vehicle. These officers now have to review large amounts of surveillance videos to find the criminal using the given vehicle description. With a VMMR system, the amounts of video to be watched could be reduced to the few places where a particular make and model has been discovered thereby speeding up the capture of the criminal.
Another use of VMMR could for marketing research; a company might decide to do a survey of about the vehicles they manufacture or sell. They wanted to find out how many of such vehicles are being used in a region and target their marketing and advertising based on the results of the survey. A VMMR system can provide an automated way of performing such a survey and returning very accurate results. Now you know how important this project is ............sure you do. Now what's this project about?
The Project
I will be implementing a web-based VMMR system using the following:- OpenCv Python Libraries
- Django - Python web framework
- OpenShift - Free PAAS used to host the system
- Other web technologies i.e. JQuery, Ajax..
Conclusion
As you can see the project will contain the works - feature extraction, machine learning etc. I might not release all the source code but will place pointers as to how to get started in each section and improve the articles as I learn stuff. So, I have to get back to work but I will see you soon. Happy coding !Tuesday, 12 March 2013
Plot Multi-Channel Histogram in QWT -Part 2
This is the second part in a series on how to do multi-channel plots in QWT. These kinds of plots can be done very simply in Matlab but are more challenging to implement in C++. Armed with the QWT library, we have investigated how to overlay multiple channel histograms on each other. Here I will show you how to make a plot similar to what you would do with the subplot() function in Matlab. This method simply puts multiple channels into one window in a grid form; this method is more presentable and makes it easier to compare plots. Lets get started...
Code
The most important part of the code is the PlotMatrix class which uses a QGridLayout to arrange the sub-plots into a matrix with specified number of rows and cols.The class is defined in the plotmatix.h header file and implemented in plotmatrix.cpp. In main.cpp we define and implement a MainWindow class which inherits from the PlotMatrix class, the histograms are created from an image and displayed in the MainWindow.
The Main function and others
The MainWindow class is a derived class from the PlotMatrix class. Here, we define a 2 by 3 matrix of plots in a static manner(Although, we only want to plot 3 channels). Grid lines are included in the plots for fun but this can be removed or edited as needed.
#include <QApplication> #include <plotmatrix.h> #include <qwt_plot.h> #include <qwt_plot_curve.h> #include <qwt_plot_grid.h> #include <qwt_symbol.h> #include <qwt_legend.h> #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> using namespace cv; class MainWindow: public PlotMatrix { public: MainWindow():PlotMatrix( 2, 3 ) { for ( int row = 0; row < numRows(); row++ ) { for ( int col = 0; col < numColumns(); col++ ) { //set default scale if any. setAxisScale( QwtPlot::yLeft, row, col, 0, 100 ); setAxisScale( QwtPlot::xBottom, row, col, 0, 255 ); QwtPlot *plt = plot( row, col ); plt->setCanvasBackground( QColor( Qt::darkGray ) ); //Allows the Axis to scale automatically. plt->setAxisAutoScale(QwtPlot::yLeft, true); //Show plot grid lines just for aesthetics. QwtPlotGrid *grid = new QwtPlotGrid(); grid->enableXMin( true ); grid->setMajorPen( Qt::white, 0, Qt::DashLine); grid->setMinorPen( Qt::gray, 0 , Qt::DotLine ); grid->attach( plt ); } } } }; void getPoints(MatND& hist, int* histSize, QPolygonF& points) { points.clear(); for( int h = 0; h < histSize[0]; ++h) { float bin_value = hist.at<float>(h); points << QPointF((float)h, bin_value); } } class Curve: public QwtPlotCurve { public: Curve( const QString &title, const QColor &colour, int penSize, QPolygonF &points ):QwtPlotCurve( title ) { QColor c = colour; c.setAlpha( 150 );//handle colour transparency setPen(c, penSize); setBrush( c ); setSamples( points ); setRenderHint( QwtPlotItem::RenderAntialiased ); } }; int main(int argc, char *argv[]) { QApplication a(argc, argv); if (argc < 2) return 1; //Read input image Mat img = imread(argv[1]); //Convert to grayscale if (img.data && img.channels() != 3) return 1; MainWindow mainWindow; mainWindow.resize( 600, 400 ); int histSize[] = {256}; // number of bins float hranges[] = {0.0, 255.0}; // min and max pixel value const float* ranges[] = {hranges}; int channels[] = {0}; // only 1 channel used std::vector<Mat> rgbChannels(3); split(img, rgbChannels); MatND hist; QPolygonF points; calcHist(&rgbChannels[2], 1, channels, Mat(), hist, 1, histSize,ranges); /*Insert the points that should be plotted on the graph in a Vector of QPoints or a QPolgonF */ getPoints(hist, histSize, points); Curve *curve = new Curve("Red Channel", Qt::red, 2, points); curve->attach( mainWindow.plot(0,0) ); // Attach curve to the plot calcHist(&rgbChannels[1], 1, channels, Mat(), hist, 1, histSize,ranges); getPoints(hist, histSize, points); curve = new Curve("Green Channel", Qt::green, 2, points); curve->attach( mainWindow.plot(0,1) ); calcHist(&rgbChannels[0], 1, channels, Mat(), hist, 1, histSize,ranges); getPoints(hist, histSize, points); curve = new Curve("Blue Channel", Qt::blue, 2, points); curve->attach( mainWindow.plot(0,2) ); mainWindow.show(); //Show plot return a.exec(); }
PlotMatrix class
This implementation uses a QVector of pointers to QwtPlot instances to represent the matrix of subplots. It provides a function plot(row, col) which returns the pointer to the desired plot.
//PlotMatrix.h #ifndef _PLOT_MATRIX_H_ #define _PLOT_MATRIX_H_ #include <qframe.h> #include <qwt_plot.h> class PlotMatrix: public QFrame { Q_OBJECT public: PlotMatrix( int rows, int columns, QWidget * parent = NULL ); virtual ~PlotMatrix(); int numRows() const; int numColumns() const; QwtPlot* plot( int row, int column ); const QwtPlot* plot( int row, int column ) const; void setAxisScale(int axisId, int row, int col, double min, double max, double step = 0 ); private: QVector<QwtPlot *> plotWidgets; }; #endif //PlotMatrix.cpp //This is based on the PlotMatrix implementation in the Qwt Samples. #include <qlayout.h> #include <qpen.h> #include <qwt_plot.h> #include <qwt_scale_widget.h> #include <qwt_scale_draw.h> #include "plotmatrix.h" PlotMatrix::PlotMatrix( int numRows, int numColumns, QWidget *parent ): QFrame( parent ) { plotWidgets.resize( numRows * numColumns ); QGridLayout *layout = new QGridLayout( this ); for ( int row = 0; row < numRows; row++ ) { for ( int col = 0; col < numColumns; col++ ) { QwtPlot *plot = new QwtPlot( this ); layout->addWidget( plot, row, col ); plotWidgets[row * numColumns + col] = plot; } } } PlotMatrix::~PlotMatrix() { foreach(QwtPlot* p, plotWidgets) delete p; } int PlotMatrix::numRows() const { const QGridLayout *l = qobject_cast<const QGridLayout *>( layout() ); if ( l ) return l->rowCount(); return 0; } int PlotMatrix::numColumns() const { const QGridLayout *l = qobject_cast<const QGridLayout *>( layout() ); if ( l ) return l->columnCount(); return 0; } QwtPlot* PlotMatrix::plot( int row, int column ) { const int index = row * numColumns() + column; if ( index < plotWidgets.size() ) return plotWidgets[index]; return NULL; } const QwtPlot* PlotMatrix::plot( int row, int column ) const { const int index = row * numColumns() + column; if ( index <plotWidgets.size() ) return plotWidgets[index]; return NULL; } void PlotMatrix::setAxisScale( int axis, int row, int col, double min, double max, double step ) { QwtPlot *plt = plot( row, col ); if ( plt ) { plt->setAxisScale( axis, min, max, step ); plt->updateAxes(); } }
Result
Conclusion
The subplot() is one of the most useful matlab functions if you plot several graphs or images. Now you can have something similar in QWT although not as simple. It is also possible to plot multi-channel histograms in a 3 dimensional plot, we leave this for another time. Happy Coding!.
Subscribe to:
Posts (Atom)