OpenCV Image substraction signed output - c++

I wouls like to subtract two gray scale images (CV_8UC1) and get their signed difference as result(CV_16SC1) .
I have tried the code below but i get as difference a CV_8UC1 matrix insted of a signed CV_16SC1.
Could you please help properly defining the Mask matrix and data type parameter?
Thanks!
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include "Imagesubstraction.h"
using namespace cv;
using namespace std;
int main(void)
{
Mat M1, M2,Dif;
M1 = imread("../data/difference/a.bmp", CV_LOAD_IMAGE_GRAYSCALE);
M2 = imread("../data/difference/b.bmp", CV_LOAD_IMAGE_GRAYSCALE);
Mat Mask(1024, 1024, CV_8UC1, Scalar(1));
subtract(M1, M2, Dif,Mask,3);
imwrite("../data/difference/c.bmp", Dif);
return 0;
}

Of course you're going to get an unsigned matrix after saving it to BMP format. As stated in OpenCV documentation:
Only 8-bit (or 16-bit unsigned (CV_16U) in case of PNG, JPEG 2000, and TIFF) single-channel or 3-channel (with ‘BGR’ channel order) images can be saved using this function.
But if you take a look at your Dif matrix before saving it, you will see it is a 16-bit signed matrix.
At least, the following code snippet works as expected:
cv::Mat m1(100, 100, CV_8U, cv::Scalar(50));
cv::Mat m2(100, 100, CV_8U, cv::Scalar(30));
cv::Mat dif;
cv::Mat mask(100, 100, CV_8U, cv::Scalar(255));
cv::subtract(m2, m1, dif, mask, CV_16S);
std::cout << dif << std::endl;

Related

Saving an image with imwrite in opencv writes all black but imshow shows correctly

Original Question
This example code will display the image created correctly, but will save a png with only black pixels. The Mat is in CV_32FC3 format, so 3 channels of floats.
The answered questions I've found deal with image manipulation issues or converting incorrectly or saving in jpeg with various compression.
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
int i = 0;
int j = 0;
Vec3f intensity;
cv::Mat imageF;
imageF= cv::Mat::zeros(36,36,CV_32FC3);
for(j=0;j<imageF.cols;++j){
for(i=0;i<imageF.rows;++i){
intensity = imageF.at<Vec3f>(j, i);
intensity.val[2] = 0.789347;
intensity.val[1] = 0.772673;
intensity.val[0] = 0.692689;
imageF.at<Vec3f>(j, i) = intensity;
}}
imshow("Output", imageF);
imwrite("test.png", imageF);
waitKey(0);
return 0;
}
What changes need to be made to make it save as expected?
Berriel's Solution
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace std;
using namespace cv;
int main() {
int i = 0;
int j = 0;
Vec3f intensity;
cv::Mat imageF;
cv::Mat image;
imageF= cv::Mat::zeros(36,36,CV_32FC3);
for(j=0; j<imageF.cols; ++j) {
for(i=0; i<imageF.rows; ++i) {
intensity = imageF.at<Vec3f>(j, i);
intensity.val[2] = 0.789347;
intensity.val[1] = 0.772673;
intensity.val[0] = 0.692689;
imageF.at<Vec3f>(j, i) = intensity;
}
}
imshow("Output", imageF);
Mat3b imageF_8UC3;
imageF.convertTo(imageF_8UC3, CV_8UC3, 255);
imwrite("test.png", imageF_8UC3);
waitKey(0);
return 0;
}
As you can read in the documentation:
The function imwrite saves the image to the specified file. The image
format is chosen based on the filename extension (see imread() for the
list of extensions). Only 8-bit (or 16-bit unsigned (CV_16U) in case
of PNG, JPEG 2000, and TIFF) single-channel or 3-channel (with ‘BGR’
channel order) images can be saved using this function. If the format,
depth or channel order is different, use Mat::convertTo() , and
cvtColor() to convert it before saving.
You should use convertTo to convert from CV_32FC3 to CV_8UC3 to get the same result:
Mat3b imageF_8UC3;
imageF.convertTo(imageF_8UC3, CV_8UC3, 255);
imwrite("test.png", imageF_8UC3);
By the way, imshow() displays correctly because...
If the image is 8-bit unsigned, it is displayed as is.
If the image is 16-bit unsigned or 32-bit integer, the pixels are divided by 256. That is, the value range [0,255*256] is mapped to
[0,255].
If the image is 32-bit floating-point, the pixel values are multiplied by 255. That is, the value range [0,1] is mapped to
[0,255].
Basically, the same trick is what you need to do before writing.
I came to this question, because I also had a problem with black ".png" images. Eventually I realised, that my 32 bit image with channels (Red, Green, Blue, Alpha) had a zero-valued alpha channel (full transparency). Thus, programs that are aware of transparency just show the "black background behind the image". After changing transparency to "255" (no transparency) my saved png-image could be visualized just fine:
MyImage[:,:,3] = 255
You can check that behaviour by assigning a value of 127 and you'll get a pale/greyed version of your image.

Copy Mat in opencv

I try to copy a image to other image using opencv, but I got a problem. Two image is not the same, like this:
This is the code I used:
#include <opencv2\opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <cmath>
#include <iostream>
#include <opencv2\opencv.hpp>
int main()
{
cv::Mat inImg = cv::imread("C:\\Users\\DUY\\Desktop\\basic_shapes.png");
//Data point copy
unsigned char * pData = inImg.data;
int width = inImg.rows;
int height = inImg.cols;
cv::Mat outImg(width, height, CV_8UC1);
//data copy using memcpy function
memcpy(outImg.data, pData, sizeof(unsigned char)*width*height);
//processing and copy check
cv::namedWindow("Test");
imshow("Test", inImg);
cv::namedWindow("Test2");
imshow("Test2", outImg);
cvWaitKey(0);
}
Simply use .clone() function of cv::Mat:
cv::Mat source = cv::imread("basic_shapes.png");
cv::Mat dst = source.clone();
This will do the trick.
You are making an image with one channel only (which means only shades of gray are possible) with CV_8UC1, you could use CV_8UC3 or CV_8UC4 but for simply copying stick with the clone function.
You actually don't want to copy the data, since you start with a RGB CV_8UC3 image, and you want to work on a grayscale CV_8UC1 image.
You should use cvtColor, that will convert your RGB data into grayscale.
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
int main()
{
Mat inImg = cv::imread("C:\\Users\\DUY\\Desktop\\basic_shapes.png"); // inImg is CV_8UC3
Mat outImg;
cvtColor(inImg, outImg, COLOR_RGB2GRAY); // Now outImg is CV_8UC1
//processing and copy check
imshow("Test", inImg);
imshow("Test2", outImg);
waitKey();
}
With a simple memcopy you're copying a sequence of uchar like this:
BGR BGR BGR BGR ...
into an image that expects them to be (G for gray):
G G G G ...
and that's is causing your outImg to be uncorrect.
Your code will be correct if you define outImage like:
cv::Mat outImg(width, height, CV_8UC3); // Instead of CV_8UC1
the best way is to use the opencv clone method:
cv::Mat outImg = inImg.clone();
Your original image is in color. cv::Mat outImg(width, height, CV_8UC1); says that your new image is of data type CV_8UC1 which is an 8-bit grayscale image. So you know that is not correct. Then you try to copy the amount of data from the original image to the new image that corresponds to total pixels * 8-bits which is at best 1/3 of the actual image (assuming the original image was 3 color, 8-bits per color, aka a 24-bit image) and perhaps even 1/4 (if it had an alpha channel, making it 4 channels of 8-bits or a 32-bit image).
TLDR: you're matrices aren't the same type, and you are making assumptions about the size of the data to be copied off of an incorrect, and incorrectly sized type.
Here is a simple code to copy image.
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <cmath>
int main()
{
cv::Mat inImg = cv::imread("1.jpg");
cv::Mat outImg = inImg.clone();
cv::namedWindow("Test");
imshow("Test", inImg);
cv::namedWindow("Test2");
imshow("Test2", outImg);
cvWaitKey(0);
}
Mat source = imread("1.png", 0);
Mat dest;
source.copyTo(dest);

Output producing 4 images side by side for single image provided in gradient calculation

Following code is used to calculate the normalized gradient at all the pixels of image. But on using imshow on calculated gradient, instead of showing gradient for provided image its showing gradient of provided image 4 times (side by side).
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <opencv2/core/core.hpp>
#include<iostream>
#include<math.h>
using namespace cv;
using namespace std;
Mat mat2gray(const Mat& src)
{
Mat dst;
normalize(src, dst, 0.0, 1.0, NORM_MINMAX);
return dst;
}
Mat setImage(Mat srcImage){
//GaussianBlur(srcImage,srcImage,Size(3,3),0.5,0.5);
Mat avgImage = Mat::zeros(srcImage.rows,srcImage.cols,CV_32F);
Mat gradient = Mat::zeros(srcImage.rows,srcImage.cols,CV_32F);
Mat norMagnitude = Mat::zeros(srcImage.rows,srcImage.cols,CV_32F);
Mat orientation = Mat::zeros(srcImage.rows,srcImage.cols,CV_32F);
//Mat_<uchar> srcImagetemp = srcImage;
float dx,dy;
for(int i=0;i<srcImage.rows-1;i++){
for(int j=0;j<srcImage.cols-1;j++){
dx=srcImage.at<float>(i,j+1)-srcImage.at<float>(i,j);
dy=srcImage.at<float>(i+1,j)-srcImage.at<float>(i,j);
gradient.at<float>(i,j)=sqrt(dx*dx+dy*dy);
orientation.at<float>(i,j)=atan2(dy,dx);
//cout<<gradient.at<float>(i,j)<<endl;
}
}
GaussianBlur(gradient,avgImage,Size(7,7),3,3);
for(int i=0;i<srcImage.rows;i++){
for(int j=0;j<srcImage.cols;j++){
norMagnitude.at<float>(i,j)=gradient.at<float>(i,j)/max(avgImage.at<float>(i,j),float(4));
//cout<<norMagnitude.at<float>(i,j)<<endl;
}
}
imshow("b",(gradient));
waitKey();
return norMagnitude;
}
int main(int argc,char **argv){
Mat image=imread(argv[1]);
cvtColor( image,image, CV_BGR2GRAY );
Mat newImage=setImage(image);
imshow("a",(newImage));
waitKey();
}
Your incoming source image is of type CV_8UC1, and yet you read it as floats:
dx=srcImage.at<float>(i,j+1)-srcImage.at<float>(i,j);
dy=srcImage.at<float>(i+1,j)-srcImage.at<float>(i,j);
If running under the debugger, this should have thrown an assertion, which would have highlighted the problem.
Try changing those lines to use unsigned char as follows:
dx=(float)(srcImage.at<unsigned char>(i,j+1)-srcImage.at<unsigned char>(i,j));
dy=(float)(srcImage.at<unsigned char>(i+1,j)-srcImage.at<unsigned char>(i,j));

Convert uchar Mat to float Mat in OpenCV?

In OpenCV, if I have a Mat img that contains uchar data, how do I convert the data into float? Is there a function available? Thank you.
If you meant c++ then you have
#include<opencv2/opencv.hpp>
using namespace cv;
Mat img;
img.create(2,2,CV_8UC1);
Mat img2;
img.convertTo(img2, CV_32FC1); // or CV_32F works (too)
details in opencv2refman.pdf.
UPDATE:
CV_32FC1 is for 1-channel (C1, i.e. grey image) float valued (32F) pixels
CV_8UC1 is for 1-channel (C1, i.e. grey image) unsigned char (8UC) valued ones.
UPDATE 2:
According to Arthur Tacca, only CV_32F is correct (or presumably CV_8U), since convertTo should not change the number of channels. It sounds logical right? Nevertheless, when I have checked opencv reference manual, I could not find any info about this, but I agree with him.
Use cvConvert function. In Python:
import cv
m = cv.CreateMat(2, 2, cv.CV_8UC1)
m1 = cv.CreateMat(2, 2, cv.CV_32FC1)
cv.Convert(m, m1)

Convert RGB to Black & White in OpenCV

I would like to know how to convert an RGB image into a black & white (binary) image.
After conversion, how can I save the modified image to disk?
AFAIK, you have to convert it to grayscale and then threshold it to binary.
1. Read the image as a grayscale image
If you're reading the RGB image from disk, then you can directly read it as a grayscale image, like this:
// C
IplImage* im_gray = cvLoadImage("image.jpg",CV_LOAD_IMAGE_GRAYSCALE);
// C++ (OpenCV 2.0)
Mat im_gray = imread("image.jpg",CV_LOAD_IMAGE_GRAYSCALE);
2. Convert an RGB image im_rgb into a grayscale image: Otherwise, you'll have to convert the previously obtained RGB image into a grayscale image
// C
IplImage *im_rgb = cvLoadImage("image.jpg");
IplImage *im_gray = cvCreateImage(cvGetSize(im_rgb),IPL_DEPTH_8U,1);
cvCvtColor(im_rgb,im_gray,CV_RGB2GRAY);
// C++
Mat im_rgb = imread("image.jpg");
Mat im_gray;
cvtColor(im_rgb,im_gray,CV_RGB2GRAY);
3. Convert to binary
You can use adaptive thresholding or fixed-level thresholding to convert your grayscale image to a binary image.
E.g. in C you can do the following (you can also do the same in C++ with Mat and the corresponding functions):
// C
IplImage* im_bw = cvCreateImage(cvGetSize(im_gray),IPL_DEPTH_8U,1);
cvThreshold(im_gray, im_bw, 128, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
// C++
Mat img_bw = im_gray > 128;
In the above example, 128 is the threshold.
4. Save to disk
// C
cvSaveImage("image_bw.jpg",img_bw);
// C++
imwrite("image_bw.jpg", img_bw);
This seemed to have worked for me!
Mat a_image = imread(argv[1]);
cvtColor(a_image, a_image, CV_BGR2GRAY);
GaussianBlur(a_image, a_image, Size(7,7), 1.5, 1.5);
threshold(a_image, a_image, 100, 255, CV_THRESH_BINARY);
I do something similar in one of my blog postings. A simple C++ example is shown.
The aim was to use the open source cvBlobsLib library for the detection
of spot samples printed to microarray slides, but the images have to be
converted from colour -> grayscale -> black + white as you mentioned, in order to achieve this.
A simple way of "binarize" an image is to compare to a threshold:
For example you can compare all elements in a matrix against a value with opencv in c++
cv::Mat img = cv::imread("image.jpg", CV_LOAD_IMAGE_GRAYSCALE);
cv::Mat bw = img > 128;
In this way, all pixels in the matrix greater than 128 now are white, and these less than 128 or equals will be black
Optionally, and for me gave good results is to apply blur
cv::blur( bw, bw, cv::Size(3,3) );
Later you can save it as said before with:
cv::imwrite("image_bw.jpg", bw);
Simple binary threshold method is sufficient.
include
#include <string>
#include "opencv/highgui.h"
#include "opencv2/imgproc/imgproc.hpp"
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("./img.jpg",0);//loading gray scale image
threshold(img, img, 128, 255, CV_THRESH_BINARY);//threshold binary, you can change threshold 128 to your convenient threshold
imwrite("./black-white.jpg",img);
return 0;
}
You can use GaussianBlur to get a smooth black and white image.