How to avoid detecting image frame when using findContours - image

How can I avoid detecting the frame of an image when using findContours (OpenCV)? Until I found OpenCV findContours allways finds two contours for every object and implemented that answer, I was not detecting the internal object consistently (object line was broken into several pieces), but now I detect the image frame every time.
The image is of a quad-rotor UAV seen from the bottom; I am using a series of pictures for 'training' object detection. For that, I need to be sure that I can consistently get the UAV object. I guess I could invert the colors, but that seems like a dirty hack.
The images are first the input image just before findContours, and the resulting contours. I have seven test images, and all seven has a frame and the UAV. The hu moments are very similar (as expected).
The code (C++11, and quite messy) for finding the contours/objects and calculating the hu moments:
#include <opencv/cv.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <fstream>
#include <string>
using namespace cv;
using namespace std;
#define EROSION_SIZE 1
#define ERODE_CANNY_PREP_ITERATIONS 5
int main() {
Mat image, canny_output, element, padded;
RNG rng(12345);
int numbers[] = {195, 223, 260, 295, 331, 368, 396};
string pre = "/home/alrekr/Pictures/UAS/hu-images/frame_";
string middle = "_threshold";
string post = ".png";
string filename = "";
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
ofstream fout("/home/alrekr/Pictures/UAS/hu-data/hu.dat");
element = getStructuringElement(MORPH_RECT,
Size(2*EROSION_SIZE + 1, 2*EROSION_SIZE+1),
Point(EROSION_SIZE, EROSION_SIZE));
namedWindow("Window", CV_WINDOW_AUTOSIZE);
for (int i : numbers) {
filename = pre + to_string(i) + middle + post;
image = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
erode(image, image, element, Point(-1,-1), ERODE_CANNY_PREP_ITERATIONS);
imwrite("/home/alrekr/Pictures/UAS/hu-data/prep_for_canny_" + to_string(i) + ".png", image);
findContours(image, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
vector<Moments> mu(contours.size());
if(contours.size() < 1) {
cout << "No contours found" << endl;
} else {
cout << "Contours found: " << contours.size() << endl;
}
vector<Point2f> mc(contours.size());
for(int j = 0; j < (int)contours.size(); j++) {
mc[j] = Point2f(mu[j].m10/mu[j].m00 , mu[j].m01/mu[j].m00);
}
Mat drawing = Mat::zeros(image.size(), CV_8UC3);
for(int j = 0; j < (int)contours.size(); j++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255));
drawContours(drawing, contours, j, color, 2, 8, hierarchy, 0, Point());
imshow("Window", drawing);
waitKey(0);
}
imwrite("/home/alrekr/Pictures/UAS/hu-data/cannied_" + to_string(i) + ".png", drawing);
fout << "Frame " << i << "\n";
for(int j = 0; j < (int)contours.size(); j++) {
mu[j] = moments(contours[j]);
double hu[7];
HuMoments(mu[j], hu);
fout << "Object " << to_string(j) << "\n";
fout << hu[0] << "\n";
fout << hu[1] << "\n";
fout << hu[2] << "\n";
fout << hu[3] << "\n";
fout << hu[4] << "\n";
fout << hu[5] << "\n";
fout << hu[6] << "\n";
}
}
fout.close();
return 0;
}

The function cv::findContours describes the contour of areas consisting of ones. The areas in which you are interested are black, though.
So the solution is simple. Invert the input image before detecting contours:
image = 255 - image;
Below is a code example which I derived from your example above:
#include <opencv2/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <string>
#define EROSION_SIZE 1
#define ERODE_CANNY_PREP_ITERATIONS 5
int main( int argc, char ** argv )
{
// Display the version of the linked OpenCV library.
std::cout << "Using OpenCV " << CV_VERSION_MAJOR << "." << CV_VERSION_MINOR << ".";
std::cout << CV_VERSION_REVISION << CV_VERSION_STATUS << std::endl;
// Load the input file.
std::string filename = std::string( argv[ 1 ] );
cv::Mat image = imread( filename, cv::IMREAD_GRAYSCALE );
// Invert the image so the area of the UAV is filled with 1's. This is necessary since
// cv::findContours describes the boundary of areas consisting of 1's.
image = 255 - image;
// Detect contours.
std::vector< std::vector< cv::Point> > contours;
std::vector< cv::Vec4i > hierarchy;
cv::findContours( image, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE );
std::cout << "Contours found: " << contours.size() << std::endl;
// Display and save the results.
cv::RNG rng( 12345 );
cv::Mat contourImage = cv::Mat::zeros( image.size(), CV_8UC3);
for( size_t j = 0; j < contours.size(); j++ )
{
cv::Scalar color( rng.uniform( 0, 255 ), rng.uniform( 0,255 ), rng.uniform( 0, 255 ) );
cv::drawContours( contourImage, contours, j, color, 2, 8, hierarchy, 0, cv::Point() );
}
// cv::imwrite( "contours.png", contourImage );
cv::imshow( "contours", contourImage );
cv::waitKey( 0 );
return 0;
}
The console output is as follows:
$ ./a.out gvlGK.png
Using OpenCV 3.0.0-beta
Contours found: 1
and the resulting contour image is this:

Another solution would be :-
find the bounding Rectangle of the contour
x,y,w,h = cv2.boundingRect(c)
compare the size of the image with the size of the bounding Rectangle for example
cnt_size=w*h
if(abs(cnt_size-img_size<=ERROR_THRESHOLD):
##discard this contour

If you have white background, first inverse it using THRESH_BINARY_INV type and then use contour.
image = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
threshold(image,image,100,255,THRESH_BINARY_INV);
findContours( image, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE );
This will only return the contour that you need.

Related

C++ zbar library - unresolved external symbol

I ran the following code for getting code feature for barcode using zbar library and set the properties for the project. I am getting errors such as unresolved external symbol error (LNK2019). How to resolve this error? The code for my program and the errors are attached below
#include <opencv2/opencv.hpp>
#include <C:/Program Files/ZBar/include/zbar.h>
using namespace cv;
using namespace std;
using namespace zbar;
typedef struct
{
string type;
string data;
vector <Point> location;
} decodedObject;
// Find and decode barcodes and QR codes
void decode(Mat& im, vector<decodedObject>& decodedObjects)
{
// Create zbar scanner
ImageScanner scanner;
// Configure scanner
scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1);
// Convert image to grayscale
Mat imGray;
cvtColor(im, imGray, COLOR_BGR2GRAY);
// Wrap image data in a zbar image
Image image(im.cols, im.rows, "Y800", (uchar*)imGray.data, im.cols * im.rows);
// Scan the image for barcodes and QRCodes
int n = scanner.scan(image);
// Print results
for (Image::SymbolIterator symbol = image.symbol_begin(); symbol != image.symbol_end(); ++symbol)
{
decodedObject obj;
obj.type = symbol->get_type_name();
obj.data = symbol->get_data();
// Print type and data
cout << "Type : " << obj.type << endl;`
cout << "Data : " << obj.data << endl << endl;
// Obtain location
for (int i = 0; i < symbol->get_location_size(); i++)
{
obj.location.push_back(Point(symbol->get_location_x(i), symbol->get_location_y(i)));
}
decodedObjects.push_back(obj);
}
}
// Display barcode and QR code location
void display(Mat& im, vector<decodedObject>& decodedObjects)
{
// Loop over all decoded objects
for (int i = 0; i < decodedObjects.size(); i++)
{
vector<Point> points = decodedObjects[i].location;
vector<Point> hull;
// If the points do not form a quad, find convex hull
if (points.size() > 4)
convexHull(points, hull);
else
hull = points;
// Number of points in the convex hull
int n = hull.size();
for (int j = 0; j < n; j++)
{
line(im, hull[j], hull[(j + 1) % n], Scalar(255, 0, 0), 3);
}
}
// Display results
imshow("Results", im);
waitKey(0);
}
int main(int argc, char* argv[])
{
// Read image
Mat im = imread("zbar-test.jpg");
// Variable for decoded objects
vector<decodedObject> decodedObjects;
// Find and decode barcodes and QR codes
decode(im, decodedObjects);
// Display location
display(im, decodedObjects);
return EXIT_SUCCESS;
}
The errors are as follows,
Your code runs OK! You just need to refer the Zbar library on:
Project Properties -> Linker -> Input -> C:\opencv\Zbar\lib\libzbar64-0.lib (for example)
With kind regards,
PFG

OpenCV perspectiveTransform broken function

Im trying to use perspectiveTransform but I keep getting error. I tried to follow the solution from this thread http://answers.opencv.org/question/18252/opencv-assertion-failed-for-perspective-transform/
_players[i].getCoordinates() is of type Point
_homography_matrix is a 3 x 3 Mat
Mat temp_Mat = Mat::zeros(2, 1, CV_32FC2);
for (int i = 0; i < _players.size(); i++)
{
cout << Mat(_players[i].get_Coordinates()) << endl;
perspectiveTransform(Mat(_players[i].get_Coordinates()), temp_Mat, _homography_matrix);
}
Also, how do I convert temp_Mat into type Point ?
OpenCV Error: Assertion failed (scn + 1 == m.cols) in cv::perspectiveTransform
Basically you just need to correct from
Mat(_players[i].get_Coordinates()) ...
to
Mat2f(_players[i].get_Coordinates()) ...
In the first case you are creating a 2x1, 1 channel float matrix, in the second case (correct) you create a 1x1, 2 channel float matrix.
You also don't need to initialize temp_Mat.
You can also use template Mat_ to better control the types of your Mats. E.g. creating a Mat of type CV_32FC2 is equivalent to create a Mat2f.
This sample code will show you also how to convert back and forth between Mat and Point:
#include <opencv2\opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
// Some random points
vector<Point2f> pts = {Point2f(1,2), Point2f(5,10)};
// Some random transform matrix
Mat1f m(3,3, float(0.1));
for (int i = 0; i < pts.size(); ++i)
{
cout << "Point: " << pts[i] << endl;
Mat2f dst;
perspectiveTransform(Mat2f(pts[i]), dst, m);
cout << "Dst mat: " << dst << endl;
Point2f p(dst(0));
cout << "Dst point: " << p << endl;
}
return 0;
}

Creating .avi video from .jpg images in opencv

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv[])
{
Mat image;
image = imread("C:/Users/Abhilash/Desktop/myFrames/frame1.jpg",1);
if(! image.data ) // Check for invalid input
{
cout << "Could not open or find the image" << endl ;
return -1;
}
gets size of the frame
Size s = image.size();
double dWidth = s.height;
double dHeight = s.width;
cout << "Frame Size = " << dWidth << "x" << dHeight << endl;
Size frameSize(static_cast<int>(dWidth), static_cast<int>(dHeight));
Initializing videowriter
VideoWriter oVideoWriter ("D:/MyVideo.avi", CV_FOURCC('P','I','M','1'), 25,
frameSize, true);
if ( !oVideoWriter.isOpened() ) //if not initialize the VideoWriter successfully, exit the program
{
cout << "ERROR: Failed to write the video" << endl;
return -1;
}
namedWindow("MyVideo",CV_WINDOW_AUTOSIZE); //create a window called "MyVideo"
int i;
char buffer[50];
Reading the images and adding them to video file
for(i=1;i<=250;i++) //putting 250 frames to MyVideo
{
Mat frame;
sprintf(buffer,"C:/Users/Abhilash/Desktop/myFrames/frame%d.jpg",i);
frame = imread(buffer,1);
if(! frame.data ) // Check for invalid input
{
cout << "Could not open or find the image" << endl ;
return -1;
}
oVideoWriter << (frame); //writer the frame into the file
imshow("MyVideo", frame); //show the frame in "MyVideo" window
if (waitKey(10) == 27) //wait for 'esc' key press for 30ms. If 'esc' key is pressed, break loop
{
cout << "esc key is pressed by user" << endl;
break;
}
}
return 0;
}
This code reads 250 .jpg frames and displays them properly. The size of the video file created is 5.54KB. But I am not able to play the video file(in KMPlayer).

Estimating an Affine Transform between Two Images

I have a sample image:
I apply the affine transform with the following warp matrix:
[[ 1.25 0. -128 ]
[ 0. 2. -192 ]]
and crop a 128x128 part from the result to get an output image:
Now, I want to estimate the warp matrix and crop size/location from just comparing the sample and output image. I detect feature points using SURF, and match them by brute force:
There are many matches, of which I'm keeping the best three (by distance), since that is the number required to estimate the affine transform. I then use those 3 keypoints to estimate the affine transform using getAffineTransform. However, the transform it returns is completely wrong:
-0.00 1.87 -6959230028596648489132997794229911552.00
0.00 -1.76 -0.00
What am I doing wrong? Source code is below.
Perform affine transform (Python):
"""Apply an affine transform to an image."""
import cv
import sys
import numpy as np
if len(sys.argv) != 10:
print "usage: %s in.png out.png x1 y1 width height sx sy flip" % __file__
sys.exit(-1)
source = cv.LoadImage(sys.argv[1])
x1, y1, width, height, sx, sy, flip = map(float, sys.argv[3:])
X, Y = cv.GetSize(source)
Xn, Yn = int(sx*(X-1)), int(sy*(Y-1))
if flip:
arr = np.array([[-sx, 0, sx*(X-1)-x1], [0, sy, -y1]])
else:
arr = np.array([[sx, 0, -x1], [0, sy, -y1]])
print arr
warp = cv.fromarray(arr)
cv.ShowImage("source", source)
dest = cv.CreateImage((Xn, Yn), source.depth, source.nChannels)
cv.WarpAffine(source, dest, warp)
cv.SetImageROI(dest, (0, 0, int(width), int(height)))
cv.ShowImage("dest", dest)
cv.SaveImage(sys.argv[2], dest)
cv.WaitKey(0)
Estimate affine transform from two images (C++):
#include <stdio.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/nonfree/nonfree.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <algorithm>
using namespace cv;
void readme();
bool cmpfun(DMatch a, DMatch b) { return a.distance < b.distance; }
/** #function main */
int main( int argc, char** argv )
{
if( argc != 3 )
{
return -1;
}
Mat img_1 = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE );
Mat img_2 = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE );
if( !img_1.data || !img_2.data )
{
return -1;
}
//-- Step 1: Detect the keypoints using SURF Detector
int minHessian = 400;
SurfFeatureDetector detector( minHessian );
std::vector<KeyPoint> keypoints_1, keypoints_2;
detector.detect( img_1, keypoints_1 );
detector.detect( img_2, keypoints_2 );
//-- Step 2: Calculate descriptors (feature vectors)
SurfDescriptorExtractor extractor;
Mat descriptors_1, descriptors_2;
extractor.compute( img_1, keypoints_1, descriptors_1 );
extractor.compute( img_2, keypoints_2, descriptors_2 );
//-- Step 3: Matching descriptor vectors with a brute force matcher
BFMatcher matcher(NORM_L2, false);
std::vector< DMatch > matches;
matcher.match( descriptors_1, descriptors_2, matches );
double max_dist = 0;
double min_dist = 100;
//-- Quick calculation of max and min distances between keypoints
for( int i = 0; i < descriptors_1.rows; i++ )
{ double dist = matches[i].distance;
if( dist < min_dist ) min_dist = dist;
if( dist > max_dist ) max_dist = dist;
}
printf("-- Max dist : %f \n", max_dist );
printf("-- Min dist : %f \n", min_dist );
//-- Draw only "good" matches (i.e. whose distance is less than 2*min_dist )
//-- PS.- radiusMatch can also be used here.
sort(matches.begin(), matches.end(), cmpfun);
std::vector< DMatch > good_matches;
vector<Point2f> match1, match2;
for (int i = 0; i < 3; ++i)
{
good_matches.push_back( matches[i]);
Point2f pt1 = keypoints_1[matches[i].queryIdx].pt;
Point2f pt2 = keypoints_2[matches[i].trainIdx].pt;
match1.push_back(pt1);
match2.push_back(pt2);
printf("%3d pt1: (%.2f, %.2f) pt2: (%.2f, %.2f)\n", i, pt1.x, pt1.y, pt2.x, pt2.y);
}
//-- Draw matches
Mat img_matches;
drawMatches( img_1, keypoints_1, img_2, keypoints_2, good_matches, img_matches,
Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
//-- Show detected matches
imshow("Matches", img_matches );
imwrite("matches.png", img_matches);
waitKey(0);
Mat fun = getAffineTransform(match1, match2);
for (int i = 0; i < fun.rows; ++i)
{
for (int j = 0; j < fun.cols; j++)
{
printf("%.2f ", fun.at<float>(i,j));
}
printf("\n");
}
return 0;
}
/** #function readme */
void readme()
{
std::cout << " Usage: ./SURF_descriptor <img1> <img2>" << std::endl;
}
The cv::Mat getAffineTransform returns is made of doubles, not of floats. The matrix you get probably is fine, you just have to change the printf command in your loops to
printf("%.2f ", fun.at<double>(i,j));
or even easier: Replace this manual output with
std::cout << fun << std::endl;
It's shorter and you don't have to care about data types yourself.

Get RGB pixels from input image and reconstruct an output image in opencv

I want to load the image in opencv and split the image into channels(RGB) and i want to increase any one of the colors and getting that corresponding output image.is there any easiest way to do this problem?
Well to add any scalar to an RGB image you can use cvAddS(srcImage, scalarToAdd, dstImage).
Here is an example:
int main(int argc, char** argv)
{
// Create a named window with the name of the file.
cvNamedWindow( argv[1], 1 );
// Load the image from the given file name.
IplImage* img = cvLoadImage( argv[1] );
//Make a scalar to add 30 to Blue Color and 20 to Red (BGR format)
CvScalar colorAdd = cvScalar(30.0, 0, 20.0);
cvAddS(img, colorAdd, img);
// Show the image in the named window
cvShowImage( argv[1], img );
// Idle until the user hits the “Esc” key.
while( 1 ) {
if( cvWaitKey( 100 ) == 27 ) break;
}
cvDestroyWindow( argv[1] );
cvReleaseImage( &img );
exit(0);
}
Haven't tested the code, hope it helps.
#karlphillip: Generally a better solution for RGB images - handles any padding at row ends, also parallelizes nicely with OMP !
for (int i=0; i < height;i++)
{
unsigned char *pRow = pRGBImg->ptr(i);
for (int j=0; j < width;j+=bpp)
// For educational puporses, here is how to print each R G B channel:
std::cout << std::dec << "R:" << (int) pRow->imageData[j] <<
" G:" << (int) pRow->imageData[j+1] <<
" B:" << (int) pRow->imageData[j+2] << " ";
}
}
With the OpenCV C++ interface you can simply add a Scalar to an image with the overloaded arithmetic operators.
int main(int argc, const char * argv[]) {
cv::Mat image;
// read an image
if (argc < 2)
return 2;
image = cv::imread(argv[1]);
if (!image.data) {
std::cout << "Image file not found\n";
return 1;
}
cv::Mat image2 = image.clone(); // Make a deep copy of the image
image2 += cv::Scalar(30,0,20); // Add 30 to blue, 20 to red
cv::namedWindow("original");
cv::imshow("original", image);
cv::namedWindow("addcolors");
cv::imshow("addcolors", image2);
cv::waitKey(0);
return 0;
}
Another option is to manually iterate on the pixels of the image and work on the channel that interests you. This will give you the flexibility to manipulate each channel individually or as a group.
The following code uses the C interface of OpenCV:
IplImage* pRGBImg = cvLoadImage("test.png", CV_LOAD_IMAGE_UNCHANGED);
int width = pRGBImg->width;
int height = pRGBImg->height;
int bpp = pRGBImg->nChannels;
for (int i=0; i < width*height*bpp; i+=bpp)
{
// For educational puporses, here is how to print each R G B channel:
std::cout << std::dec << "R:" << (int) pRGBImg->imageData[i] <<
" G:" << (int) pRGBImg->imageData[i+1] <<
" B:" << (int) pRGBImg->imageData[i+2] << " ";
}
However, if you want to add a fixed value to a certain channel you might want to check #Popovici's answer.

Resources