I have an object that lights up a certain color, say blue. It then blinks a different color every 5 seconds, say red. I have written a program to track the object using color based detection and draw a circle over it using opencv, but every time the object blinks red, I momentarily lose the ability to track it because the computer no longer sees the object. My question is, can I continuously track the blue object and have my program recognize the object as blue even while it is blinking red?
Code:
Note: I have an "object" class that has getters/setters for color, hsv min and max for its color, name, etc. trackObject() uses findContours method to track and draw graphics on the object.
int main(int argc, char** argv)
{
resizeWindow("trackbar", 500, 0);
VideoCapture stream1(0);
if (!stream1.isOpened()) {
cout << "cannot open camera";
}
while (true) {
Mat cameraFrame;
Mat hsvFrame;
Mat binMat;
Mat grey;
Mat grayBinMat;
stream1.read(cameraFrame);
if (cameraFrame.empty()) {
break;
}
object blue("BLUE");
cvtColor(cameraFrame, hsvFrame, CV_BGR2HSV, 0);
inRange(hsvFrame, blue.getHSVMin(), blue.getHSVMax(), binMat);
morphOps(binMat);
trackobject(cameraFrame, binMat, blue);
object red("RED");
cvtColor(cameraFrame, hsvFrame, CV_BGR2HSV, 0);
inRange(hsvFrame, red.getHSVMin(), red.getHSVMax(), binMat);
morphOps(binMat);
trackobject(cameraFrame, binMat, red);
imshow("cam", cameraFrame);
//imshow("gray bin", grayBinMat);
imshow("hsv bin", binMat);
imshow("hsv", hsvFrame);
//1. waitKey waits (x ms) before resuming program 2. returns the ascii value of any key pressed during the wait time
if (waitKey(30) >= 0) {
break;
}
}
return 0;
}
Related
How can I add a small video sequence to another video using OpenCV?
To elaborate, let's say I have a video playing which is to be interactive where let's say the user viewing the video gestures something and a short sequence plays at the bottom or at the corner of the existing video.
For each frame, you need to copy an image with the content you need inside the video frame. The steps are:
Define the size of the overlay frame
Define where to show the overlay frame
For each frame
Fill the overlay frame with some content
Copy the overlay frame in the defined position in the original frame.
This small snippet will show a random noise overlay window on bottom right of the camera feed:
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
// Video capture frame
Mat3b frame;
// Overlay frame
Mat3b overlayFrame(100, 200);
// Init VideoCapture
VideoCapture cap(0);
// check if we succeeded
if (!cap.isOpened()) {
cerr << "ERROR! Unable to open camera\n";
return -1;
}
// Get video size
int w = cap.get(CAP_PROP_FRAME_WIDTH);
int h = cap.get(CAP_PROP_FRAME_HEIGHT);
// Define where the show the overlay frame
Rect roi(w - overlayFrame.cols, h - overlayFrame.rows, overlayFrame.cols, overlayFrame.rows);
//--- GRAB AND WRITE LOOP
cout << "Start grabbing" << endl
<< "Press any key to terminate" << endl;
for (;;)
{
// wait for a new frame from camera and store it into 'frame'
cap.read(frame);
// Fill overlayFrame with something meaningful (here random noise)
randu(overlayFrame, Scalar(0, 0, 0), Scalar(256, 256, 256));
// Overlay
overlayFrame.copyTo(frame(roi));
// check if we succeeded
if (frame.empty()) {
cerr << "ERROR! blank frame grabbed\n";
break;
}
// show live and wait for a key with timeout long enough to show images
imshow("Live", frame);
if (waitKey(5) >= 0)
break;
}
// the camera will be deinitialized automatically in VideoCapture destructor
return 0;
}
I've been trying to simultaneously grab frames from two different cameras, but no matter how many times I call VideoCapture::grab(), there seems to be no effect. The frame retrieved using VideoCapture::retrieve() is always the first frame captured from the last VideoCapture::retrieve().
I've tested it on both OpenCV 2.4 and 3.1, with a Logitech C920 camera on windows.
Example:
VideoCapture vCapture;
Mat imgResult;
vCapture.open(0); //at this point, there is a green sheet in front of the camera
Sleep(100000); //change green sheet with red sheet
vCapture.grab(); //returns true
vCapture.retrieve(imgResult); //image with green sheet is retrieved
Sleep(100000); //change red sheet with blue sheet
vCapture.retrieve(imgResult); //red sheet is retrieved
I've also tried:
for(int i = 0; i < 1000; i++){
vCapture.grab();
} //takes almost no processing time, like an empty for
vCapture.retrieve(imgResult); //same as before
Retrieve always returns true and retrieves a frame, even if no grab was called since opening vCapture.
My current solution has been retrieving the frame twice (multi-threaded) to ensure the latest frame, but it isn't reliable enough to sync both cameras. Can anyone shed some light on how to force the camera to grab the current frame?
Thanks!
Edit:
A variation of the first example, for clarity:
VideoCapture vCapture;
Mat imgResult;
vCapture.open(0); //at this point, there is a green sheet in front of the camera
vCapture.retrieve(imgResult); //image with green sheet is retrieved
Sleep(100000); //change green sheet with red sheet
vCapture.grab(); //returns true
vCapture.retrieve(imgResult); //green sheet is retrieved
vCapture.retrieve(imgResult); //red sheet is retrieved
Sleep(100000); //change red sheet with blue sheet
vCapture.retrieve(imgResult); //red sheet is retrieved
vCapture.retrieve(imgResult); //blue sheet is retrieved
Expected behavior:
VideoCapture vCapture;
Mat imgResult;
vCapture.open(0); //at this point, there is a green sheet in front of the camera
vCapture.retrieve(imgResult); //error or image with green sheet is retrieved
Sleep(100000); //change green sheet with red sheet
vCapture.grab(); //returns true
vCapture.retrieve(imgResult); //red sheet is retrieved
Per OpenCV documentation:
VideoCapture::grab: The methods/functions grab the next frame from video file or camera and return true (non-zero) in the case of success.
VideoCapture::retrieve: The methods/functions decode and return the just grabbed frame. If no frames has been grabbed (camera has been disconnected, or there are no more frames in video file), the methods return false and the functions return NULL pointer.
Please try this code with the following instructions:
before and while you start the program, hold a red sheet in front of the camera. That moment, the first .grab will be called.
Once you see the black window popping up, remove the red sheet and hold a blue sheet or something else (except the red or the green sheet) in front of the camera. Then press keyboard key 'q'.
Now you have 5 seconds time to change the scene again. Hold hold the green sheet in front of the camera and wait. The black window will be switched to one of your camera images.
int main(int argc, char* argv[])
{
cv::Mat input = cv::Mat(512,512,CV_8UC1, cv::Scalar(0));
cv::VideoCapture cap(0);
while (cv::waitKey(10) != 'q')
{
cap.grab();
cv::imshow("input", input);
}
cv::waitKey(5000);
cap.retrieve(input);
cv::imshow("input", input);
cv::waitKey(0);
return 0;
}
3 possible results:
you see the red sheet: this means that the first grab was called and fixed the image, until a retrieve was called.
you see blue sheet: this means every .grab call "removes" one image and the camera will capture a new image on the next call of .grab
you see the green sheet: this means your .retrieve doesn't need a .grab at all and just grabs images automatically.
For me, result 1 occurs, so you can't grab and grab and grab and just .retrieve the last image.
Test 2: control about everything:
looks like you are right, on my machine no matter when or how often I call grab, the next image will be the one captured at the time when I called the previous .retrieve and the calls of .grab don't seem to influence the time position of capturing at all.
Would be very interesting whether the same behaviour occurs for different (all) kind of cameras and operating systems.
I've tested on the internal camera of a T450s and Windows 7.
int main(int argc, char* argv[])
{
cv::Mat input = cv::Mat(512,512,CV_8UC1, cv::Scalar(0));
cv::VideoCapture cap(0);
bool grabbed;
bool retrieved;
while (true)
{
char w = cv::waitKey(0);
switch (w)
{
case 'q': return 0;
case 27: return 0;
case ' ': retrieved = cap.retrieve(input); break;
case 'p': grabbed = cap.grab(); break;
}
cv::imshow("input", input);
}
return 0;
}
In addition, this simple code seems to be off 1 frame for my camera (which therefore probably has a buffersize of 2??):
while (true)
{
cap >> input;
cv::imshow("input", input);
cv::waitKey(0);
}
I ran my test and note very strange behavior of .garab and .retrive functions.
This is example:
cv::Mat input = cv::Mat(512, 512, CV_8UC1, cv::Scalar(0));
cv::VideoCapture cap(0);
while (true)
{
cap.grab();
cap.retrieve(input, 5);
cv::imshow("input", input);
cv::waitKey(0);
}
If you press any key slowly, about every 5 seconds, and change something in front of the camera between pressing, the position of the object on the image will change every second image showing, that is, every second call of the .grab and .retrive functions.
If you press any key quickly, about every 1 seconds, and also change something in front of the camera between pressing, the position of the object on the image will be changed every image showing.
This circumstance tell about that this function can be used to sync cameras.
I am working on a project with OpenFrameworks using ofxCV, ofxOpencv and ofxColorQuantizer. Technically, the project is analyzing live video captured via webcam and analysis's the image in real time to gather and output the most prominent color in the current frame. When generating the most prominent color I am using the pixel difference between the current frame and the previous frame to generate the what colors have updated and use the updated or moving areas of the video frame to figure out the most prominent colors.
The reason for using the pixel difference's to generate the color pallet is because I want to solve for the case of a user walks into the video frame, I want try and gather the color pallet of the person, for instance what they are wearing. For example red shirt, blue pants will be in the pallet and the white background will be excluded.
I have a strong background in Javascript and canvas but am fairly new to OpenFrameworks and C++ which is why I think I am running into a roadblock with this problem I described above.
Along with OpenFrameworks I am using ofxCV, ofxOpencv and ofxColorQuantizer as tools for this installation. I am taking a webcam image than making it a cv:Mat than using pyrdown on the webcam image twice followed by a absdiff of the mat which I am than trying to pass the mat into the ofxColorQuantizer. This is where I think I am running into problems — I don't think the ofxColorQuantizer likes the mat format of the image I am trying to use. I've tried looking for the different image format to try and convert the image to to solve this issue but I haven't been able to come to solution.
For efficiencies I am hoping to to the color difference and color prominence calculations on the smaller image (after I pyrdown' the image) and display the full image on the screen and the generated color palette is displayed at the bottom left like in the ofxColorQuantizer example.
I think there may be other ways to speed up the code but at the moment I am trying to get this portion of the app working first.
I have my main.cpp set up as follows:
#include "ofMain.h"
#include "ofApp.h"
#include "ofAppGlutWindow.h"
//========================================================================
int main( ){
ofAppGlutWindow window;
ofSetupOpenGL(&window, 1024,768, OF_WINDOW); // <-------- setup the GL context
// ofSetupOpenGL(1024,768,OF_WINDOW); // <-------- setup the GL context
// this kicks off the running of my app
// can be OF_WINDOW or OF_FULLSCREEN
// pass in width and height too:
ofRunApp(new ofApp());
}
My ofApp.h file is as follows:
#pragma once
#include "ofMain.h"
#include "ofxOpenCv.h"
#include "ofxCv.h"
#include "ofxColorQuantizer.h"
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
ofVideoGrabber cam;
ofPixels previous;
ofImage diff;
void kMeansTest();
ofImage image;
ofImage img;
cv::Mat matA, matB;
ofImage diffCopy;
ofImage outputImage;
ofxCv::RunningBackground background;
ofxColorQuantizer colorQuantizer;
// a scalar is like an ofVec4f but normally used for storing color information
cv::Scalar diffMean;
};
And finally my ofApp.cpp is below:
#include "ofApp.h"
using namespace ofxCv;
using namespace cv;
//--------------------------------------------------------------
void ofApp::setup(){
ofSetVerticalSync(true);
cam.initGrabber(320, 240);
// get our colors
colorQuantizer.setNumColors(3);
// resize the window to match the image
// ofSetWindowShape(image.getWidth(), image.getHeight());
ofSetWindowShape(800, 600);
// imitate() will set up previous and diff
// so they have the same size and type as cam
imitate(previous, cam);
imitate(diff, cam);
imitate(previous, outputImage);
imitate(diff, outputImage);
}
//--------------------------------------------------------------
void ofApp::update(){
cam.update();
if(cam.isFrameNew()) {
matA = ofxCv::toCv(cam.getPixelsRef());
ofxCv::pyrDown(matA, matB);
ofxCv::pyrDown(matB, matA);
ofxCv::medianBlur(matA, 3);
ofxCv::toOf(matA, outputImage);
// take the absolute difference of prev and cam and save it inside diff
absdiff(previous, outputImage, diff);
}
}
//--------------------------------------------------------------
void ofApp::draw(){
// If the image is ready to draw, then draw it
if(outputImage.isAllocated()) {
outputImage.update();
outputImage.draw(0, 0, ofGetWidth(), ofGetHeight());
}
ofBackground(100,100,100);
ofSetColor(255);
ofImage diffCopy;
diffCopy = diff;
diffCopy.resize(diffCopy.getWidth()/2, diffCopy.getHeight()/2);
// there is some sort of bug / issue going on here...
// prevent the app from compiling
// comment out to run and see blank page
colorQuantizer.quantize(diffCopy.getPixelsRef());
ofLog() << "the number is " << outputImage.getHeight();
ofLog() << "the number is " << diffCopy.getHeight();
ofSetColor(255);
img.update();
// cam.draw(0, 0, 800, 600);
outputImage.draw(0, 0, 800, 600);
// colorQuantizer.draw(ofPoint(0, cam.getHeight()-20));
colorQuantizer.draw(ofPoint(0, 600-20));
// use the [] operator to get elements from a Scalar
float diffRed = diffMean[0];
float diffGreen = diffMean[1];
float diffBlue = diffMean[2];
ofSetColor(255, 0, 0);
ofRect(0, 0, diffRed, 10);
ofSetColor(0, 255, 0);
ofRect(0, 15, diffGreen, 10);
ofSetColor(0, 0, 255);
ofRect(0, 30, diffBlue, 10);
}
//--------------------------------------------------------------
void ofApp::kMeansTest(){
cv::Mat samples = (cv::Mat_<float>(8, 1) << 31 , 2 , 10 , 11 , 25 , 27, 2, 1);
cv::Mat labels;
// double kmeans(const Mat& samples, int clusterCount, Mat& labels,
cv::TermCriteria termcrit;
int attempts, flags;
cv::Mat centers;
double compactness = cv::kmeans(samples, 3, labels, cv::TermCriteria(), 2, cv::KMEANS_PP_CENTERS, centers);
cout<<"labels:"<<endl;
for(int i = 0; i < labels.rows; ++i)
{
cout<<labels.at<int>(0, i)<<endl;
}
cout<<"\ncenters:"<<endl;
for(int i = 0; i < centers.rows; ++i)
{
cout<<centers.at<float>(0, i)<<endl;
}
cout<<"\ncompactness: "<<compactness<<endl;
}
Apologies in advance for the state of my code — it's getting late and I'm trying to get this done.
My question is what is the image format openFrameworks is using for grabbing the webcam image, what is the image format that openCV expects and what should I use to switch back from a mat image to an ofImage and is there a way to getPixelsRef from a mat image?
The area of code that I think I have something wrong is the following logic.
I have this line of code which gets the video frame from the webcam matA = ofxCv::toCv(cam.getPixelsRef());
Than do a couple ofxCv procedures on the frame such as ofxCv::pyrDown(matA, matB); which I think changes the image format or pixel format of the frame
Than I convert the frame back to OF with ofxCv::toOf(matA, outputImage);,
Next I get the difference in the pixels between the current frame and the last frame, create a copy of the difference between the two frames. Potentially the issue lies here with the diff output image format
Which I pass the diff copy to colorQuantizer.quantize(diffCopy.getPixelsRef()); to try and generate the color palette in for the change in pixels.
It is the colorQuantizer class and function call that is giving me an error which reads thread error [ error ] ofTexture: allocate(): ofTextureData has 0 width and/or height: 0x0
with an EXC_BAD_ACCESS
And lastly, could there be an alternative cause for the exc_bad_access thread error rather than image formatting? Being new to c++ I'm just guessing and going off instinct of what I think the rood cause of my problem is.
Many thanks.
I am attempting to use a straightforward motion detection code to detect movement from a camera. I'm using the OpenCV library and I have some code that takes the difference between two frames to detect a change and then it uses a threshold to create a black/white image of the difference.
My problem: I cannot figure out a simple way to get a true or false output if motion is detected. I got this code from somewhere else and I am not familiar with all the details. I tried to sum the img_diff matrix but it gave me an error. What would be the simplest way to get a 'true' output if motion is detected, meaning that the background difference is not zero? For example, would an if statement comparing two matrices of the current frame and previous frame work?
The code I'm trying to use is below:
int main(int argc, char** argv)
{
const char * _diffType = getCmdOption("-type", "2", argc, argv);
const char * _thresval = getCmdOption("-thr", "60", argc, argv);
int diffType = atoi( _diffType );
int thresval = atoi( _thresval );
VideoCapture cap(0);
if( !cap.isOpened() ) return -1;
Mat cam_frame, img_gray, img_prev, img_diff, img_bin;
const char *win_cam = "Camera input"; namedWindow(win_cam, CV_WINDOW_AUTOSIZE);
const char *win_gray = "Gray image"; namedWindow(win_gray, CV_WINDOW_AUTOSIZE);
const char *win_diff = "Binary diff image"; namedWindow(win_diff, CV_WINDOW_AUTOSIZE);
bool first_frame = true;
while (cvWaitKey(4) == -1) {
cap >> cam_frame;
cvtColor(cam_frame, img_gray, CV_BGR2GRAY);
if (first_frame) {
img_prev=img_gray.clone();
first_frame = false;
continue;
}
absdiff(img_gray, img_prev, img_diff);
threshold(img_diff, img_bin, thresval, 255, THRESH_BINARY);
erode(img_bin, img_bin, Mat(), Point(-1,-1), 3);
dilate(img_bin, img_bin, Mat(), Point(-1,-1), 1);
imshow(win_cam, cam_frame);
imshow(win_gray, img_gray);
imshow(win_diff, img_bin);
if (diffType == 1) img_prev=img_gray.clone();
}
return 0;
}
Any help would be appreciated!
If you are looking for an easy way i would use the average of img_diff as a parameter for motion and just compare the average with a threshold of like 5 or 10 (assuming 8bit gray):
if(mean(img_diff) > thresval){
cout << "motion detected!" << endl;
}
Using this method you don't have to adjust your threshold to the size of the images.
However i see a general problem with detecting motion using only the current and previous frame: it will only detect high frequency motion, or in other words only fast motion. If you want to detect slow motion you need to compare the current frame to an older frame, like 5 or 10 frames before.
I am new to image manipulation. I have noticed that you can specify a rectangular region of interest and others like circles etc in image manipulation libraries like opencv. Basic paint programs like ms-paint incorporate free form selection but i cannot seem to find a function or tutorial on how to do free form image selection in opencv or other image processing libraries. Any ideas on how to achieve this?
PS: My preferred language is c/c++.
One thing you can try:
If the selection can be represented as a sequence of 2d-vectors, you can think of it as a polygon. Allocate a new 1-channel image that will be your mask and fill it with 0. Then use
void cvFillPoly(CvArr* img, CvPoint** pts, int* npts, int contours, CvScalar color, int lineType=8, int shift=0)
documented on
http://opencv.willowgarage.com/documentation/drawing_functions.html
to draw a non-zero region on the mask image to represent the selected part of the image.
I wrote a demo to display an image and paint little green dots while your mouse moves, see below.
You need to know that OpenCV was not designed for this type of interaction, so performance is an issue (and it's bad)! You'll see what I mean.
#include <stdio.h>
#include <cv.h>
#include <highgui.h>
// mouse callback
void on_mouse(int event, int x, int y, int flags, void* param)
{
// Remove comment below to paint only when left mouse button is pressed
//if (event == CV_EVENT_LBUTTONDOWN)
{
//fprintf(stderr, "Painting at %dx%d\n", x, y);
IplImage* img = (IplImage*)param;
cvCircle(img, cvPoint(x,y), 1, CV_RGB(0, 255, 0), -1, CV_AA, 0);
cvShowImage("cvPaint", img);
}
}
int main(int argc, char* argv[])
{
if (argc < 2)
{
fprintf( stderr, "Usage: %s <img>\n", argv[0]);
return -1;
}
IplImage* frame = NULL;
frame = cvLoadImage(argv[1], CV_LOAD_IMAGE_UNCHANGED);
if (!frame)
{
fprintf( stderr, "Failed: Couldn't load file %s\n", argv[1]);
return -1;
}
cvNamedWindow("cvPaint", CV_WINDOW_AUTOSIZE);
cvShowImage("cvPaint", frame);
cvSetMouseCallback("cvPaint", &on_mouse, frame);
while (1)
{
// Keep looping to prevent the app from exiting,
// so the mouse callback can be called by OpenCV and do some painting
char key = cvWaitKey(10);
if (key == 113 || key == 27) // q was pressed on the keyboard
break;
}
cvReleaseImage(&frame);
cvDestroyWindow("cvPaint");
return 0;
}
My suggestion is that you use some other window system for this type of task, where performance is better. Take a look at Qt, for instance. But you can also you platform native ways like win32 or X, if you rather.
For the other part of the question, how to crop on user selection, I suggest you take a look at the code available at: OpenCV resizing and cropping image according to pixel value
Also, recording mouse coordinates while the user is painting the image is much more practical then analyzing the image for the painted green dots. Then analise these coordinates and retrieve the smallest rectangle area from it. That's when this logic gets useful:
CvScalar s;
for (x=0; x<width-1; x++)
{
for(y=0; y<height-1; y++)
{
s = cvGet2D(binImage, y, x);
if (s.val[0] == 1)
{
minX = min(minX, x);
minY = min(minY, y);
maxX = max(maxX, x);
maxY = max(maxY, y);
}
}
}
cvSetImageROI(binImage, cvRect(minX, minY, maxX-minX, maxY-minY));
In this specific case, instead of iterating through the image looking for specific pixels like the user did in that question, you will iterate over your array of coordinates recorded during mouse movement.