Copy image to section of another image - c++

I am attempting to copy an image to another image (place a logo in the bottom right side of an image). My code successfully copies the logo onto the room image but it does some weird stretching when I apply a mask.
// Overlay logo
// src = 3 channel image of a room
// logo = 3 channel image of a logo (most of the Mat is black except for the logo).
// logo is smaller than the room image
Mat res;
Mat thresh;
src.copyTo(res);
threshold(logo, thresh, 1, 255, CV_THRESH_BINARY);
Rect r(res.cols-logo.cols, res.rows-logo.rows, logo.cols, logo.rows);
logo.copyTo(res(r), thresh);
My result:
Mask is:

Your logo seems to be a color image. Which means that your thresh image is also a color image. When you use it as a mask, it's going to be horizontally stretched because the extra channels are interpreted as more columns.
Try adding cvtColor(logo, grayLogo, CV_BGR2GRAY) before the threshold and use the grayLogo in the threshold.

Mask size and image size are not equal (thats why mask is streched). To solve this, you should create mask image and copy your threshed logo in good position.
[EDIT]: According to sunreef's answer - three channels in mask could also be a source of your problem.
Code posted below worked for me.
cv::Mat t_roomImage = cv::imread("E:\\Workspace\\KS\\misc\\M4nKr.jpg");
cv::Mat t_logoImage = cv::imread("E:\\Workspace\\KS\\misc\\7sFbv.jpg",0);
cv::threshold(t_logoImage, t_logoImage, 10, 255, CV_THRESH_BINARY);
cv::Mat t_mask = cv::Mat::zeros(t_roomImage.size(), CV_8U);
cv::Rect t_rect = cv::Rect(t_mask.cols - t_logoImage.cols, t_mask.rows -
t_logoImage.rows, t_logoImage.cols, t_logoImage.rows);
t_logoImage.copyTo(t_mask(t_rect));
t_roomImage.setTo(cv::Scalar(255, 0, 0), t_mask);
I used setTo, because I had no logo image. There you can use copyTo instead.
Results:

Related

How to remove protruding part of a square shape in an image?

]2
I would like to get a square shape from the right image above. But when I try to get it, it also includes other protruding parts because they have similar color. Are there any solutions to get the result like below? (The square lines are not 100 % straight. They are little distorted.)
This is the code I wrote.
cv::Mat img_gray, img, clahe_img, threshold_img, bitwise_img, morph_img;
cv::Mat rectified_CCD_img = cv::imread('img.png')
cv::Mat kernel = cv::Mat::ones(99, 99, CV_8U);
clahe = cv::createCLAHE(10, cv::Size(100, 100));
cv::cvtColor(rectified_CCD_img, img_gray, cv::COLOR_BGR2GRAY);
cv::medianBlur(img_gray, img, 33);
clahe->apply(img, clahe_img);
cv::threshold(clahe_img, threshold_img, 0, 255, cv::THRESH_OTSU);
cv::bitwise_not(threshold_img, bitwise_img);
cv::morphologyEx(bitwise_img, morph_img, cv::MORPH_OPEN, kernel);
That's the original image:
Google Drive link
For this specific image my pipeline would be very simple:
Binary threshold the image with a fixed threshold. The rectangle is quite dark compared to the rest of the image.
Morphological opening with a large rectangular kernel to get rid of the "noise".
To get a perfect rectangle, determine the bounding rectangle of the remaining part, and draw a white rectangle.
That'd be the whole code:
// Read image
cv::Mat img = cv::imread("OTH61.png", cv::IMREAD_GRAYSCALE);
// Binary threshold image at fixed threshold
cv::Mat img_thr;
cv::threshold(img, img_thr, 32, 255, cv::THRESH_BINARY_INV);
// Morphological opening with large rectangular kernel
cv::Mat img_mop;
cv::morphologyEx(img_thr, img_mop, cv::MORPH_OPEN, cv::Mat::ones(51, 51, CV_8UC1));
// Draw rectangle w.r.t. to the bounding rectangle of the remaining part
cv::rectangle(img_mop, cv::boundingRect(img_mop), 255, cv::FILLED);
The thresholded image:
The morphological opened image:
The cleaned image:

OpenCV keep background transparent during warpAffine

I create a Bird-View-Image with the warpPerspective()-function like this:
warpPerspective(frame, result, H, result.size(), CV_WARP_INVERSE_MAP, BORDER_TRANSPARENT);
The result looks very good and also the border is transparent:
Bird-View-Image
Now I want to put this image on top of another image "out". I try doing this with the function warpAffine like this:
warpAffine(result, out, M, out.size(), CV_INTER_LINEAR, BORDER_TRANSPARENT);
I also converted "out" to a four channel image with alpha channel according to a question which was already asked on stackoverflow:
Convert Image
This is the code: cvtColor(out, out, CV_BGR2BGRA);
I expected to see the chessboard but not the gray background. But in fact, my result looks like this:
Result Image
What am I doing wrong? Do I forget something to do? Is there another way to solve my problem? Any help is appreciated :)
Thanks!
Best regards
DamBedEi
I hope there is a better way, but here it is something you could do:
Do warpaffine normally (without the transparency thing)
Find the contour that encloses the image warped
Use this contour for creating a mask (white values inside the image warped, blacks in the borders)
Use this mask for copy the image warped into the other image
Sample code:
// load images
cv::Mat image2 = cv::imread("lena.png");
cv::Mat image = cv::imread("IKnowOpencv.jpg");
cv::resize(image, image, image2.size());
// perform warp perspective
std::vector<cv::Point2f> prev;
prev.push_back(cv::Point2f(-30,-60));
prev.push_back(cv::Point2f(image.cols+50,-50));
prev.push_back(cv::Point2f(image.cols+100,image.rows+50));
prev.push_back(cv::Point2f(-50,image.rows+50 ));
std::vector<cv::Point2f> post;
post.push_back(cv::Point2f(0,0));
post.push_back(cv::Point2f(image.cols-1,0));
post.push_back(cv::Point2f(image.cols-1,image.rows-1));
post.push_back(cv::Point2f(0,image.rows-1));
cv::Mat homography = cv::findHomography(prev, post);
cv::Mat imageWarped;
cv::warpPerspective(image, imageWarped, homography, image.size());
// find external contour and create mask
std::vector<std::vector<cv::Point> > contours;
cv::Mat imageWarpedCloned = imageWarped.clone(); // clone the image because findContours will modify it
cv::cvtColor(imageWarpedCloned, imageWarpedCloned, CV_BGR2GRAY); //only if the image is BGR
cv::findContours (imageWarpedCloned, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
// create mask
cv::Mat mask = cv::Mat::zeros(image.size(), CV_8U);
cv::drawContours(mask, contours, 0, cv::Scalar(255), -1);
// copy warped image into image2 using the mask
cv::erode(mask, mask, cv::Mat()); // for avoid artefacts
imageWarped.copyTo(image2, mask); // copy the image using the mask
//show images
cv::imshow("imageWarpedCloned", imageWarpedCloned);
cv::imshow("warped", imageWarped);
cv::imshow("image2", image2);
cv::waitKey();
One of the easiest ways to approach this (not necessarily the most efficient) is to warp the image twice, but set the OpenCV constant boundary value to different values each time (i.e. zero the first time and 255 the second time). These constant values should be chosen towards the minimum and maximum values in the image.
Then it is easy to find a binary mask where the two warp values are close to equal.
More importantly, you can also create a transparency effect through simple algebra like the following:
new_image = np.float32((warp_const_255 - warp_const_0) *
preferred_bkg_img) / 255.0 + np.float32(warp_const_0)
The main reason I prefer this method is that openCV seems to interpolate smoothly down (or up) to the constant value at the image edges. A fully binary mask will pick up these dark or light fringe areas as artifacts. The above method acts more like true transparency and blends properly with the preferred background.
Here's a small test program that warps with transparent "border", then copies the warped image to a solid background.
int main()
{
cv::Mat input = cv::imread("../inputData/Lenna.png");
cv::Mat transparentInput, transparentWarped;
cv::cvtColor(input, transparentInput, CV_BGR2BGRA);
//transparentInput = input.clone();
// create sample transformation mat
cv::Mat M = cv::Mat::eye(2,3, CV_64FC1);
// as a sample, just scale down and translate a little:
M.at<double>(0,0) = 0.3;
M.at<double>(0,2) = 100;
M.at<double>(1,1) = 0.3;
M.at<double>(1,2) = 100;
// warp to same size with transparent border:
cv::warpAffine(transparentInput, transparentWarped, M, transparentInput.size(), CV_INTER_LINEAR, cv::BORDER_TRANSPARENT);
// NOW: merge image with background, here I use the original image as background:
cv::Mat background = input;
// create output buffer with same size as input
cv::Mat outputImage = input.clone();
for(int j=0; j<transparentWarped.rows; ++j)
for(int i=0; i<transparentWarped.cols; ++i)
{
cv::Scalar pixWarped = transparentWarped.at<cv::Vec4b>(j,i);
cv::Scalar pixBackground = background.at<cv::Vec3b>(j,i);
float transparency = pixWarped[3] / 255.0f; // pixel value: 0 (0.0f) = fully transparent, 255 (1.0f) = fully solid
outputImage.at<cv::Vec3b>(j,i)[0] = transparency * pixWarped[0] + (1.0f-transparency)*pixBackground[0];
outputImage.at<cv::Vec3b>(j,i)[1] = transparency * pixWarped[1] + (1.0f-transparency)*pixBackground[1];
outputImage.at<cv::Vec3b>(j,i)[2] = transparency * pixWarped[2] + (1.0f-transparency)*pixBackground[2];
}
cv::imshow("warped", outputImage);
cv::imshow("input", input);
cv::imwrite("../outputData/TransparentWarped.png", outputImage);
cv::waitKey(0);
return 0;
}
I use this as input:
and get this output:
which looks like ALPHA channel isn't set to ZERO by warpAffine but to something like 205...
But in general this is the way I would do it (unoptimized)

Opencv create new image using cv::Mat

I'm new to opencv and i'm trying on some sample codes.
in one code, Mat gr(row1,col1,CV_8UC1,scalar(0));
int x = gr.at<uchar> (row,col);
And in another one,
Mat grHistrogram(301,260,CV_8UC1,Scalar(0,0,0));
line(grHistrogram,pt1,pt2,Scalar(255,255,255),1,8,0);
Now my question is if i used scalar(0) instead of scalar(0,0,0) in second code, The code doesn't work.
1.Why this happening since, Both create a Mat image structure.
2.what is the purpose of const cv:Scalar &_s.
I search the Documentaion from Opencv site (opencv.pdf,opencv2refman.pdf) and Oreilly's Opencv book. But couldn't find a explained answer.
I think i'm using the Mat(int _rows,int _cols,int _type,const cv:Scalar &_s) struct.
First, you need the following information to create the image:
Width: 301 pixels
Height: 260 pixels
Each pixel value (intensity) is 0 ~ 255: an 8-bit unsigned integer
Supports all RGB colors: 3 channels
Initial color: black = (B, G, R) = (0, 0, 0)
You can create the Image using cv::Mat:
Mat grHistogram(260, 301, CV_8UC3, Scalar(0, 0, 0));
The 8U means the 8-bit Usigned integer, C3 means 3 Channels for RGB color, and Scalar(0, 0, 0) is the initial value for each pixel. Similarly,
line(grHistrogram,pt1,pt2,Scalar(255,255,255),1,8,0);
is to draw a line on grHistogram from point pt1 to point pt2. The color of line is white (255, 255, 255) with 1-pixel thickness, 8-connected line, and 0-shift.
Sometimes you don't need a RGB-color image, but a simple grayscale image. That is, use one channel instead of three. The type can be changed to CV_8UC1 and you only need to specify the intensity for one channel, Scalar(0) for example.
Back to your problem,
Why this happening since, both create a Mat image structure?
Because you need to specify the type of the Mat. Is it a color image CV_8UC3 or a grayscale image CV_8UC1? They are different. Your program may not work as you think if you use Scalar(255) on a CV_8UC3 image.
What is the purpose of const cv:Scalar &_s ?
cv::Scalar is use to specify the intensity value for each pixel. For example, Scalar(255, 0, 0) is blue and Scalar(0, 0, 0) is black if type is CV_8UC3. Or Scalar(0) is black if it's a CV_8UC1 grayscale image. Avoid mixing them together.
You can create single channel image or multi channel image.
creating single channel image : Mat img(500, 1000, CV_8UC1, Scalar(70));
creating multi channel image : Mat img1(500, 1000, CV_8UC3, Scalar(10, 100, 150));
you can see more example and detail from following page.
https://progtpoint.blogspot.com/2017/01/tutorial-3-create-image.html

Getting masked area to be transparent?

So far i have managed to use masks and get the second image from the first. But what i want is the black area in second image to be transparent (i.e the output i an trying to get is the third image) Here is the code so far. Please advice me on this.
EDIT: Third one is from photoshop
//imwrite parameters
compression_params.push_back(CV_IMWRITE_JPEG_QUALITY);
compression_params.push_back(100);
//reading image to be masked
image = imread(main_img, -1);
//CV_LOAD_IMAGE_COLOR
namedWindow("output", WINDOW_NORMAL);
//imshow("output", image);
//Creating mask image with same size as original image
Mat mask(image.rows, image.cols, CV_8UC1, Scalar(0));
// Create Polygon from vertices
ROI_Vertices.push_back(Point2f(float(3112),float(58)));
ROI_Vertices.push_back(Point2f(float(3515),float(58)));
ROI_Vertices.push_back(Point2f(float(3515),float(1332)));
ROI_Vertices.push_back(Point2f(float(3112),float(958)));
approxPolyDP(ROI_Vertices, ROI_Poly, 1, true);
// Fill polygon white
fillConvexPoly(mask, &ROI_Poly[0] , ROI_Poly.size(), 255, 8, 0);
//imshow("output", mask);
// Create new image for result storage
imageDest = cvCreateMat(image.rows, image.cols, CV_8UC4);
// Cut out ROI and store it in imageDest
image.copyTo(imageDest, mask);
imwrite("masked.jpeg", imageDest, compression_params);
imshow("output", imageDest);
cvWaitKey(0);
This can be done by first setting its alpha value to 0 of the regions that you want to make them fully transparent (255 for others), and then save it to PNG.
To set the alpha value of pixel-(x,y), it can be done:
image.at<cv::Vec4b>(y, x)[3] = 0;
PS: you need to convert it to 4-channel format first if the image is not currently. For example:
cv::cvtColor(image, image, CV_BGR2BGRA);
Updated: It will be easier if you have already computed the mask for the ROI region, where you can simply merge it with the original image (assume having 3 channels) to get the final result. Like:
cv::Mat mask; // 0 for transparent regions, 255 otherwise (serve as the alpha channel)
std::vector<cv::Mat> channels;
cv::split(image, channels);
channels.push_back(mask);
cv::Mat result;
cv::merge(channels, result);

copying ipl image pixel by pixel

The problem is solved....I used cvGet2D,below is the sample code
CvScalar s;
s=cvGet2D(src_Image,pixel[i].x,pixel[i].y);
cvSet2D(dst_Image,pixel[i].x,pixel[i].y,s);
Where src_Iamge and dst_Image is the source and destination image correspondingly and pixel[i] is the selected pixel i wanted to draw in the dst image. I have include the real out image below.
have an source Ipl image, I want to copy some of the part of the image to a new destination image pixel by pixel. can any body tell me how can do it? I use c,c++ in opencv. For example if the below image is source image,
The real output image
EDIT:
I can see the comments suggesting cvGet2d. I think, if you just want to show "points", it is best to show them with a small neighbourhood so they can be seen where they are. For that you can draw white filled circles with origins at (x,y), on a mask, then you do the copyTo.
using namespace cv;
Mat m(input_iplimage);
Mat mask=Mat::zeros(m.size(), CV_8UC1);
p1 = Point(x,y);
r = 3;
circle(mask,p1,r, 1); // draws the circle around your point.
floodFill(mask, p1, 1); // fills the circle.
//p2, p3, ...
Mat output = Mat::zeros(m.size(),m.type()); // output starts with a black background.
m.copyTo(output, mask); // copies the selected parts of m to output
OLD post:
Create a mask and copy those pixels:
#include<opencv2/opencv.hpp>
using namespace cv;
Mat m(input_iplimage);
Mat mask=Mat::zeros(m.size(), CV_8UC1); // set mask 1 for every pixel you wanna copy.
Rect roi=Rect(x,y,width,height); // create a rectangle
mask(roi) = 1; // set it to 0.
roi = Rect(x2,y2,w2,h2);
mask(roi)=1; // set the second rectangular area for copying...
Mat output = 100*Mat::ones(m.size(),m.type()); // output with a gray background.
m.copyTo(output, mask); // copy selected areas of m to output
Alternatively you can copy Rect-by-Rect:
Mat m(input_iplimage);
Mat output = 100*Mat::ones(m.size(),m.type()); // output with a gray background.
Rect roi=Rect(x,y,width,height);
Mat m_temp, out_temp;
m_temp=m(roi);
out_temp = output(roi);
m_temp.copyTo(out_temp);
roi=Rect(x2,y2,w2,h2);
Mat m_temp, out_temp;
m_temp=m(roi);
out_temp = output(roi);
m_temp.copyTo(out_temp);
The answer to your question only requires to have look at the OpenCV documentation or just to search in your favourite search engine.
Here you've an answer for Ipl images and for newer Mat data.
For having an output as I see in your images, I'd do it setting ROI's, it's more efficient.