Related
Objective and problem
I'm trying to process a video file on the fly using OpenCV 3.4.1 by grabbing each frame, converting to grayscale, then doing Canny edge detection on it. In order to display the images (on the fly as well), I created a Mat class with 3 additional headers that is three times as wide as the original frame. The 3 extra headers represent the images I would like to display in the composite, and are positioned to the 1st, 2nd and 3rd horizontal segment of the composite.
After image processing however, the display of the composite image is not as expected: the first segment (where the original frame should be) is completely black, while the other segments (of processed images) are displayed fine. If, on the other hand, I display the ROIs one by one in separate windows, all the images look fine.
These are the things I tried to overcome this issue:
use .copyTo to actually copy the data into the appropriate image segments. The result was the same.
I put the Canny image to the compOrigPart ROI, and it did display in the first segment, so it is not a problem with the definition of the ROIs.
Define the composite as three channel image
In the loop convert it to grayscale
put processed images into it
convert back to BGR
put the original in.
This time around the whole composite was black, nothing showed.
As per gameon67's suggestion, I tried to create a namedWindow as well, but that doesn't help either.
Code:
int main() {
cv::VideoCapture vid("./Vid.avi");
if (!vid.isOpened()) return -1;
int frameWidth = vid.get(cv::CAP_PROP_FRAME_WIDTH);
int frameHeight = vid.get(cv::CAP_PROP_FRAME_HEIGHT);
int frameFormat = vid.get(cv::CAP_PROP_FORMAT);
cv::Scalar fontColor(250, 250, 250);
cv::Point textPos(20, 20);
cv::Mat frame;
cv::Mat compositeFrame(frameHeight, frameWidth*3, frameFormat);
cv::Mat compOrigPart(compositeFrame, cv::Range(0, frameHeight), cv::Range(0, frameWidth));
cv::Mat compBwPart(compositeFrame, cv::Range(0, frameHeight), cv::Range(frameWidth, frameWidth*2));
cv::Mat compEdgePart(compositeFrame, cv::Range(0, frameHeight), cv::Range(frameWidth*2, frameWidth*3));
while (vid.read(frame)) {
if (frame.empty()) break;
cv::cvtColor(frame, compBwPart, cv::COLOR_BGR2GRAY);
cv::Canny(compBwPart, compEdgePart, 100, 150);
compOrigPart = frame;
cv::putText(compOrigPart, "Original", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor);
cv::putText(compBwPart, "GrayScale", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor);
cv::putText(compEdgePart, "Canny edge detection", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor);
cv::imshow("Composite of Original, BW and Canny frames", compositeFrame);
cv::imshow("Original", compOrigPart);
cv::imshow("BW", compBwPart);
cv::imshow("Canny", compEdgePart);
cv::waitKey(33);
}
}
Questions
Why can't I display the entirety of the composite image in a single window, while displaying them separately is OK?
What is the difference between these displays? The data is obviously there, as evidenced by the separate windows.
Why only the original frame is misbehaving?
Your compBwPart and compEdgePart are grayscale images so the Mat type is CV8UC1 - single channel and therefore your compositeFrame is in grayscale too. If you want to combine these two images with a color image you have to convert it to BGR first and then fill the compOrigPart.
while (vid.read(frame)) {
if (frame.empty()) break;
cv::cvtColor(frame, compBwPart, cv::COLOR_BGR2GRAY);
cv::Canny(compBwPart, compEdgePart, 100, 150);
cv::cvtColor(compositeFrame, compositeFrame, cv::COLOR_GRAY2BGR);
frame.copyTo(compositeFrame(cv::Rect(0, 0, frameWidth, frameHeight)));
cv::putText(compOrigPart, "Original", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor); //the rest of your code
This is a combination of several issues.
The first problem is that you set the type of compositeFrame to the value returned by vid.get(cv::CAP_PROP_FORMAT). Unfortunately that property doesn't seem entirely reliable -- I've just had it return 0 (meaning CV_8UC1) after opening a color video, and then getting 3 channel (CV_8UC3) frames. Since you want to have the compositeFrame the same type as the input frame, this won't work.
To work around it, instead of using those properties, I'd lazy initialize compositeFrame and the 3 ROIs after receiving the first frame (based on it's dimensions and type).
The next set of problems lies in those two statements:
cv::cvtColor(frame, compBwPart, cv::COLOR_BGR2GRAY);
cv::Canny(compBwPart, compEdgePart, 100, 150);
In this case assumption is made that frame is BGR (since you're trying to convert), meaning compositeFrame and its ROIs are also BGR. Unfortunately, in both cases you're writing a grayscale image into the ROI. This will cause a reallocation, and the target Mat will cease to be a ROI.
To correct this, use temporary Mats for the grayscale data, and use cvtColor to turn it back to BGR to write into the ROIs.
Similar problem lies in the following statement:
compOrigPart = frame;
That's a shallow copy, meaning it will just make compOrigPart another reference to frame (and therefore it will cease to be a ROI of compositeFrame).
What you need is a deep copy, using copyTo (note that the data types still need to match, but that was fixed earlier).
Finally, even though you try to be flexible regarding the type of the input video (judging by the vid.get(cv::CAP_PROP_FORMAT)), the rest of the code really assumes that the input is 3 channel, and will break if it isn't.
At the least, there should be some assertion to cover this expectation.
Putting this all together:
#include <opencv2/opencv.hpp>
int main()
{
cv::VideoCapture vid("./Vid.avi");
if (!vid.isOpened()) return -1;
cv::Scalar fontColor(250, 250, 250);
cv::Point textPos(20, 20);
cv::Mat frame, frame_gray, edges_gray;
cv::Mat compositeFrame;
cv::Mat compOrigPart, compBwPart, compEdgePart; // ROIs
while (vid.read(frame)) {
if (frame.empty()) break;
if (compositeFrame.empty()) {
// The rest of code assumes video to be BGR (i.e. 3 channel)
CV_Assert(frame.type() == CV_8UC3);
// Lazy initialize once we have the first frame
compositeFrame = cv::Mat(frame.rows, frame.cols * 3, frame.type());
compOrigPart = compositeFrame(cv::Range::all(), cv::Range(0, frame.cols));
compBwPart = compositeFrame(cv::Range::all(), cv::Range(frame.cols, frame.cols * 2));
compEdgePart = compositeFrame(cv::Range::all(), cv::Range(frame.cols * 2, frame.cols * 3));
}
cv::cvtColor(frame, frame_gray, cv::COLOR_BGR2GRAY);
cv::Canny(frame_gray, edges_gray, 100, 150);
// Deep copy data to the ROI
frame.copyTo(compOrigPart);
// The ROI is BGR, so we need to convert back
cv::cvtColor(frame_gray, compBwPart, cv::COLOR_GRAY2BGR);
cv::cvtColor(edges_gray, compEdgePart, cv::COLOR_GRAY2BGR);
cv::putText(compOrigPart, "Original", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor);
cv::putText(compBwPart, "GrayScale", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor);
cv::putText(compEdgePart, "Canny edge detection", textPos, cv::FONT_HERSHEY_PLAIN, 1, fontColor);
cv::imshow("Composite of Original, BW and Canny frames", compositeFrame);
cv::imshow("Original", compOrigPart);
cv::imshow("BW", compBwPart);
cv::imshow("Canny", compEdgePart);
cv::waitKey(33);
}
}
Screenshot of the composite window (using some random test video off the web):
I need to create a mask to retrieve an object (foreground object) based on two related images.
Image 1:
[![enter image description here]
Image 2:
[![enter image description here]
The images contain a foreground object and a background with texture.
The two images are mostly the same except that in image2, the foreground object may have changed a little bit (it could have been rotated, translated or/and scaled).
Using OpenCV, I did the followings:
perform image alignment (using findTransformECC with param cv::MOTION_AFFINE) to get transformation of foreground;
do transformation to image1 (using cv::warpAffine with param cv::INTER_LINEAR + cv::WARP_INVERSE_MAP) based on the transform matrix above;
do absolute diff (cv::absdiff & cv::threshold with param cv::THRESH_BINARY_INV) between image2 and already transformed image1.
I think I am close to my goal but I still can not get clean mask of foreground object due to remaining noises on the background area.
What is the solution to remove all noise on the image_absdiff_invert.png (above) in order to create a clean mask of the foreground object ?
I just tried it.
Using morphological operations is often a bit tricky (trial and error) and gives me this result:
While using a median filter might be a good pre-processing (or maybe even enough for your contour extraction) and gives this result (this is just median blur from the input image, no morphological operations yet):
here's the test code:
int main(int argc, char* argv[])
{
cv::Mat input = cv::imread("C:/StackOverflow/Input/maskNoise.png", CV_LOAD_IMAGE_GRAYSCALE);
cv::Mat mask = input.clone();
cv::dilate(mask, mask, cv::Mat());
cv::dilate(mask, mask, cv::Mat());
cv::erode(mask, mask, cv::Mat());
cv::erode(mask, mask, cv::Mat());
cv::erode(mask, mask, cv::Mat());
cv::erode(mask, mask, cv::Mat());
//cv::erode(mask, mask, cv::Mat());
//cv::erode(mask, mask, cv::Mat());
//cv::dilate(mask, mask, cv::Mat());
//cv::dilate(mask, mask, cv::Mat());
cv::dilate(mask, mask, cv::Mat());
cv::dilate(mask, mask, cv::Mat());
cv::Mat median;
cv::medianBlur(input, median, 7);
cv::Mat resizedIn;
cv::Mat resizedMask;
cv::Mat resizedMedian;
cv::resize(mask, resizedMask, cv::Size(), 0.5, 0.5);
cv::resize(median, resizedMedian, cv::Size(), 0.5, 0.5);
cv::resize(input, resizedIn, cv::Size(), 0.5, 0.5);
cv::imshow("input", resizedIn);
cv::imshow("mask", resizedMask);
cv::imshow("median", resizedMedian);
cv::imwrite("C:/StackOverflow/Output/maskNoiseMorph.png", mask);
cv::imwrite("C:/StackOverflow/Output/maskNoiseMedian.png", median);
cv::waitKey(0);
return 0;
}
The implementation of this functionality seems pretty straightforward in Python, as shown here: http://docs.opencv.org/trunk/doc/py_tutorials/py_imgproc/py_grabcut/py_grabcut.html
Yet, when I tried to do exactly the same in C++, I get bad arguments error (for the grabcut function). How to put the mask image in the right format?
I am a newbie at this, so I'd be very thankful if someone could help me understand better. Thank you!
Here's what I have so far:
Mat image;
image= imread(file);
Mat mask;
mask.setTo( GC_BGD );
mask = imread("messi5.png");
Mat image2 = image.clone();
// define bounding rectangle
cv::Rect rectangle(startX, startY, width, height);
cv::Mat result; // segmentation result (4 possible values)
cv::Mat bgModel,fgModel; // the models (internally used)
//// GrabCut segmentation that works, but with a rectangle, not with the mask I need
//cv::grabCut(image, // input image
// result, // segmentation result
// rectangle,// rectangle containing foreground
// bgModel,fgModel, // models
// 1, // number of iterations
// cv::GC_INIT_WITH_RECT); // use rectangle
grabCut( image, mask, rectangle, bgModel, fgModel, 1, GC_INIT_WITH_MASK);
cv::compare(mask,cv::GC_PR_FGD,mask,cv::CMP_EQ);
cv::Mat foreground(image.size(),CV_8UC3,cv::Scalar(255,255,255));
image.copyTo(foreground,mask); // bg pixels not copied
namedWindow( "Display window", WINDOW_AUTOSIZE );
imshow( "Display window", foreground );
waitKey(0);
return 0;
}
It looks like you have misunderstood the guide, repeated here from the linked guide in the question:
# newmask is the mask image I manually labelled
newmask = cv2.imread('newmask.png',0)
# whereever it is marked white (sure foreground), change mask=1
# whereever it is marked black (sure background), change mask=0
mask[newmask == 0] = 0
mask[newmask == 255] = 1
mask, bgdModel, fgdModel = cv2.grabCut(img,mask,None,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_MASK)
mask = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()
this is not what you have done i'm afraid. For a start you seem to have set the mask to the rgb image:
mask = imread("messi5.png");
whereas is should be set to the mask image:
mask = imread("newmask.png",CV_LOAD_IMAGE_GRAYSCALE);
EDIT from comments:
from a pure red mask painted over the image (an actual mask would be better).
maskTmp = imread("messi5.png");
std::vector<cv::Mat> channels(3)
split( messi5, channels);
cv::Mat maskRed = channels[2];
now threshold on the red channel to get your binary mask.
I want to apply a binary mask to a color image.
Please provide a basic code example with proper explanation of how the code works.
Also, is there some option to apply a mask permanently so all functions operate only within the mask?
While #perrejba s answer is correct, it uses the legacy C-style functions. As the question is tagged C++, you may want to use a method instead:
inputMat.copyTo(outputMat, maskMat);
All objects are of type cv::Mat.
Please be aware that the masking is binary. Any non-zero value in the mask is interpreted as 'do copy'. Even if the mask is a greyscale image.
Also be aware that the .copyTo() function does not clear the output before copying.
If you want to permanently alter the original Image, you have to do an additional copy/clone/assignment. The copyTo() function is not defined for overlapping input/output images. So you can't use the same image as both input and output.
You don't apply a binary mask to an image. You (optionally) use a binary mask in a processing function call to tell the function which pixels of the image you want to process. If I'm completely misinterpreting your question, you should add more detail to clarify.
Well, this question appears on top of search results, so I believe we need code example here. Here's the Python code:
import cv2
def apply_mask(frame, mask):
"""Apply binary mask to frame, return in-place masked image."""
return cv2.bitwise_and(frame, frame, mask=mask)
Mask and frame must be the same size, so pixels remain as-is where mask is 1 and are set to zero where mask pixel is 0.
And for C++ it's a little bit different:
cv::Mat inFrame; // Original (non-empty) image
cv::Mat mask; // Original (non-empty) mask
// ...
cv::Mat outFrame; // Result output
inFrame.copyTo(outFrame, mask);
You can use the mask to copy only the region of interest of an original image to a destination one:
cvCopy(origImage,destImage,mask);
where mask should be an 8-bit single channel array.
See more at the OpenCV docs
Here is some code to apply binary mask on a video frame sequence acquired from a webcam.
comment and uncomment the "bitwise_not(Mon_mask,Mon_mask);"line and see the effect.
bests,
Ahmed.
#include "cv.h" // include it to used Main OpenCV functions.
#include "highgui.h" //include it to use GUI functions.
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
int c;
int radius=100;
CvPoint2D32f center;
//IplImage* color_img;
Mat image, image0,image1;
IplImage *tmp;
CvCapture* cv_cap = cvCaptureFromCAM(0);
while(1) {
tmp = cvQueryFrame(cv_cap); // get frame
// IplImage to Mat
Mat imgMat(tmp);
image =tmp;
center.x = tmp->width/2;
center.y = tmp->height/2;
Mat Mon_mask(image.size(), CV_8UC1, Scalar(0,0,0));
circle(Mon_mask, center, radius, Scalar(255,255,255), -1, 8, 0 ); //-1 means filled
bitwise_not(Mon_mask,Mon_mask);// commenté ou pas = RP ou DMLA
if(tmp != 0)
imshow("Glaucom", image); // show frame
c = cvWaitKey(10); // wait 10 ms or for key stroke
if(c == 27)
break; // if ESC, break and quit
}
/* clean up */
cvReleaseCapture( &cv_cap );
cvDestroyWindow("Glaucom");
}
Use copy with a mask.
Code sample:
Mat img1 = imread(path); // Load your image
Mat mask(img1 .size(),img1 .type()); // Create your mask
mask.setTo(0);
Point center(img1.cols/2, img1.rows / 2);
const int radius = img1.cols / 5; // Circle radio
circle(mask, center, radius, 255, FILLED);// Draw a circle in the image center
Mat img2(img1 .size(),img1 .type()); // Outimage
img2.setTo(0); // Clear data
img1.copyTo(img2, mask); // Only values at mask > 0 will be copied.
I just want to try the openCV function -- cvCornerHarris. Here is my c++ code:
//image file
char imagePath[256] = "./images/lena512color.tiff";
printf("%s\n", imagePath);
IplImage* srcImg = cvLoadImage(imagePath, 1);
if(NULL == srcImg){
printf("Can not open image file(s).\n");
return -1;
}
IplImage* srcImgGry = cvCreateImage(cvGetSize(srcImg), IPL_DEPTH_8U, 1);
cvCvtColor(srcImg, srcImgGry, CV_RGB2GRAY);
// Canny and Harris expect grayscale (8-bit) input.
// And output of harris image must be 32-bit float .
IplImage* harrisImg = cvCreateImage(cvGetSize(srcImg), IPL_DEPTH_32F, 1);
IplImage* cannyImg = cvCreateImage(cvGetSize(srcImg), IPL_DEPTH_8U, 1);
//// Corner detection using Harris-corner
cvCornerHarris(srcImgGry, harrisImg, 5, 5, 0.04);
cvCanny(srcImgGry, cannyImg, 50, 100, 3);
// (5)Display the result
cvNamedWindow ("Img", CV_WINDOW_AUTOSIZE);
cvShowImage ("Img", srcImgGry);
cvNamedWindow ("Harris", CV_WINDOW_AUTOSIZE);
cvShowImage ("Harris", harrisImg);
cvNamedWindow ("Canny", CV_WINDOW_AUTOSIZE);
cvShowImage ("Canny", cannyImg);
cvWaitKey (0);
cvDestroyWindow ("Harris");
cvDestroyWindow ("Img");
cvReleaseImage (&srcImg);
cvReleaseImage (&srcImgGry);
cvReleaseImage (&harrisImg);
cvReleaseImage (&cannyImg);
I can get a expected output image of cvCanny (cannyImg) but the output image of cvCornerHarris (harrisImg)is an black image with nothing on it.
Please help to explain how to use this function cvCornerHarris. Thanks!
It's all about parameters! People tend to believe that there are magical parameters that will work for all types of images and scenarios. Unfortunately, this doesn't happen in the real world.
The parameters used to process one image may not produce the same level of results when applied to other type of image. Now, consider the following code:
IplImage* colored = cvLoadImage("house.jpg", CV_LOAD_IMAGE_UNCHANGED);
if (!colored)
{
printf("Can not open image file(s).\n");
return -1;
}
IplImage* gray = cvCreateImage(cvGetSize(colored), IPL_DEPTH_8U, 1);
cvCvtColor(colored, gray, CV_RGB2GRAY);
IplImage* harris = cvCreateImage(cvGetSize(colored), IPL_DEPTH_32F, 1);
cvCornerHarris(gray, harris, 3, 11, 0.07);
cvNamedWindow("Harris", CV_WINDOW_AUTOSIZE);
cvShowImage ("Harris", harris);
As you can see below, these parameters produced a decent result (to my point of view). However, keep in mind that they won't probably work for you. Bad parameters will produce a black image (i.e. will detect nothing) as you have observed on your tests.
The answer is: take a look at the docs to see what those parameters mean and how they influence the result. Most importantly, play with them until they produce images that satisfy your needs.
Input image:
(source: 123desenhosparacolorir.com)
Output: