Canny edge detection - grayscale images always coming up as 3-channel, unusable? - c++

I am working through the book "Learning OpenCV" from the O'Reilly series and am trying to perform a canny edge detection sample.
Any grayscale image I choose seems to come up as having 3 channels, and to the best of my knowledge, canny only works with single channel images, so this always fails. I am even using the images provided by OpenCV.
Here is my code..
IplImage* doCanny(IplImage* in, double lowThresh, double highThresh, double aperture)
{
if(in->nChannels != 1)
return(0); //canny only handles gray scale images
IplImage* out = cvCreateImage(cvSize(in->width, in->height), IPL_DEPTH_8U, 1);
cvCanny(in, out, lowThresh, highThresh, aperture);
return(out);
};
IplImage* img = cvLoadImage("someGrayscaleImage.jpg");
IplImage* out = doCanny(img, 10, 100, 3);
Why might this always give me 3-channel images? How can I solve this?

You can use this method with another parameter
IplImage* cvLoadImage(const char* filename, int iscolor=CV_LOAD_IMAGE_COLOR)
#define CV_LOAD_IMAGE_COLOR 1
#define CV_LOAD_IMAGE_GRAYSCALE 0
#define CV_LOAD_IMAGE_UNCHANGED -1
The default parameter is load image with color. What you have to do is to load it with grayscale
Here is an example
cvLoadImage("yourimage.jpg", CV_LOAD_IMAGE_GRAYSCALE);
Here is the detail explanation for that method. You can look at here for more details:
Open CV 2.0 References
scolor – Specific color type of the loaded image: if $ > 0 $, the loaded image is forced to be a 3-channel color image; if 0, the loaded image is forced to be grayscale; if $ < 0 $, the loaded image will be loaded as is (note that in the current implementation the alpha channel, if any, is stripped from the output image, e.g. 4-channel RGBA image will be loaded as RGB).

Related

OpenCV Object Detection (HOGDescriptor) on iOS

I'm trying to get the people detector provided by the OpenCV library running. So far I get decent performance on my iPhone 6 but the detection is super bad and almost never correct and I'm not really sure why this is since you can find example videos using the same default HOG descriptor with way better detection.
Here is the code:
- (void)processImage:(Mat&)image {
cv::Mat cvImg, result;
cvtColor(image, cvImg, COLOR_BGR2HSV);
cv::vector<cv::Rect> found, found_filtered;
hog.detectMultiScale(cvImg, found, 0, cv::Size(4,4), cv::Size(8,8), 1.5, 0);
size_t i;
for (i=0; i < found.size(); i++) {
cv::Rect r = found[i];
rectangle(image, r.tl(), r.br(), Scalar(0,255,0), 2);
}
}
The video input comes from the iPhone camera itself and "processImage:" is called for every frame. For the HOGDescriptor I use the default people detector:
_hog.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector());
I appreciate any help. :)
I'm new to openCV, so take this with a grain of salt:
The line cvtColor(image, cvImg, COLOR_BGR2HSV); converts the image from the BGR color space to the HSV color space. Essentially, it changes each pixel from being represented by how much blue, green, and red it has, to being represented by the components hue (color), saturation (how much color) and value (how bright). Clearly, the hogDescriptor acts on a BGR image, not an HSV image. You need to pass it a type CV_8UC3 image: An image with 3 channels per pixel (C3), ex. BGR, and an 8bit unsigned number for each channel (8U), This part is less important. What are you passing into the method processImage()? It should be one of those types. If not, you need to know the type and convert it to CV_8UC3 using the cvtColor() method

How to display PGM image using OpenCV

I'm trying to load and display a .PGM image using OpenCV(2.4.0) for C++.
void open(char* location, int flag, int windowFlag)
{
Mat image = imread(location, flag);
namedWindow("Image window", windowFlag);
imshow("Image window", image);
waitKey(0);
}
I'm calling open like this:
open("./img_00245_c1.pgm", IMREAD_UNCHANGED, CV_WINDOW_AUTOSIZE);
The problem is that the image shown when the window is opened is darker than if I'm opening the file with IrfanView.
Also if I'm trying to write this image to another file like this:
Mat imgWrite;
imgWrite = image;
imwrite("newImage.pgm", imgWrite)
I will get a different file content than the original one and IrfanView will display this as my function displays with imshow.
Is there a different flag in imread for .PGM files such that I can get the original file to be displayed and saved ?
EDIT: Image pgm file
EDIT 2 : Remarked that: IrfanView normalizes the image to a maximum pixel value of 255 .
In order to see the image clearly using OpenCV I should normalize the image also when loading in Mat. Is this possible directly with OpenCV functions without iterating through pixels and modifying their values ?
The problem is not in the way data are loaded, but in the way they are displayed.
Your image is a CV_16UC1, and both imshow and imwrite normalize the values from original range [0, 65535] to the range [0, 255] to fit the range of the type CV_8U.
Since your PGM image has max_value of 4096:
P2
1176 640 // width height
4096 // max_value
it should be normalized from range [0, 4096] instead of [0, 65535].
You can do this with:
Mat img = imread("path_to_image", IMREAD_UNCHANGED);
img.convertTo(img, CV_8U, 255.0 / 4096.0);
imshow("Image", img);
waitKey();
Please note that the values range in your image doesn't correspond to [0, 4096], but:
double minv, maxv;
minMaxLoc(img, &minv, &maxv);
// minv = 198
// maxv = 2414
So the straightforward normalization in [0,255] like:
normalize(img, img, 0, 255, NORM_MINMAX);
img.convertTo(img, CV_8U);
won't work, as it will produce an image brighter than it should be.
This means that to properly show your image you need to know the max_value (here 4096). If it changes every time, you can retrieve it parsing the .pgm file.
Again, it's just a problem with visualization. Data are correct.

Create a transparent image in opencv

I am trying to rotate an image in x, y and z axis as in this.
The image should not be cropped while rotating So I am doing this
Mat src = imread("path");
int diagonal = (int)sqrt(src.cols*src.cols+src.rows*src.rows);
int newWidth = diagonal;
int newHeight =diagonal;
Mat targetMat(newWidth, newHeight, src.type());
I am creating a bigger image targetMat. The input image is a png image.
But I want this image as a transparent image. So I tried this
Mat targetMat(newWidth, newHeight, src.type(), cv::Scalar(0,0,0,0));
But the output image was
What I need is (Transparent image is here)
So what change do I have to do?
The problem is, that your input image is type CV_8UC3 but you need CV_8UC4 to use the alpha channel. So try Mat targetMat(newHeight, newWidth, CV_8UC4, cv::Scalar(0,0,0,0)); or cvtColor of src before creation of new mat
To use your original image, there are two possibilities:
use cv::cvtColor(src, src, CV_BGR2BGRA) (and adjust later code to use a 4 channel matrix - cv::Vec4b instead of cv::Vec3b etc)
if your input file is a .png with alpha channel you can use the CV_LOAD_IMAGE_ANYDEPTH (hope this is the right one) flag to load it as a CV_xxC4 image (might be 16 bit too) and to use the original alpha values.

OpenCV image conversion goes wrong

I have an algorithm that does some stuff. Among them, there is a conversion that works fine if I'm working on a CV_8UC3 image but goes wrong if the file type is C_16UC3.
This is some code:
//new image is created
Mat3w img(100,100,Vec3w(1000,0,0));
//Image Conversion - ERROR!
cv::Mat inputSource;
//saving the image here will work
img.convertTo(inputSource, CV_64FC3);
//saving the image here will not work -> black image
The problem is that the CV_16UC3 image's processing result is an image of the right dimensions but fully black.
The problem is in the conversion because saving the image right before will give a legit one while saving it right after will give an almost completely white one.
EDIT:
I made some changes: cut off some useless code and added the inputSource declaration.
Now, while I was trying stuff, I arrived at the conclusion that either I haven't understood the CV Types, or something strange is happening.
I always thought that the number in the type was indicating the number of bits per channel. So, in my head, CV_16UC3 is a 3 channel with 16bits per channel. That idea is strengthened by the fact that the image I save during as tests (before the img.convertTo) actually had matching bits per channel number. The strange thing, is that the saved inputSource (type CV_64FC3) is an 8bpc image.
What's am I missing?
You get confused with the way imwrite and imread work in OpenCV. From the OpenCV documentation
imwrite
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. Or, use the universal FileStorage I/O functions to save the image to XML or YAML format.
imread
The function imread loads an image from the specified file and returns it. Possible flags are:
IMREAD_UNCHANGED : If set, return the loaded image as is (with alpha channel, otherwise it gets cropped).
IMREAD_GRAYSCALE : If set, always convert image to the single channel grayscale image.
IMREAD_COLOR : If set, always convert image to the 3 channel BGR color image.
IMREAD_ANYDEPTH : If set, return 16-bit/32-bit image when the input has the corresponding depth, otherwise convert it to 8-bit.
IMREAD_ANYCOLOR : If set, the image is read in any possible color format.
So for your case, CV_16U are saved without conversion, while CV_64F is converted and saved as CV_8U. If you want to store double data, you should use FileStorage.
You should also take care to use imread the image with the appropriate flag.
This example should clarify:
#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
// Create a 16-bit 3 channel image
Mat3w img16UC3(100, 200, Vec3w(1000, 0, 0));
img16UC3(Rect(0, 0, 20, 50)) = Vec3w(0, 2000, 0);
// Convert to 64-bit (double) 3 channel image
Mat3d img64FC3;
img16UC3.convertTo(img64FC3, CV_64FC3);
// Save to disk
imwrite("16UC3.png", img16UC3); // No conversion
imwrite("64FC3.png", img64FC3); // Converted to CV_8UC3
FileStorage fout("64FC3.yml", FileStorage::WRITE);
fout << "img" << img64FC3; // No conversion
fout.release();
Mat img_maybe16UC3_a = imread("16UC3.png" /*, IMREAD_COLOR*/); // Will be CV_8UC3
Mat img_maybe16UC3_b = imread("16UC3.png", IMREAD_ANYDEPTH); // Will be CV_16UC1
Mat img_maybe16UC3_c = imread("16UC3.png", IMREAD_UNCHANGED); // Will be CV_16UC3
Mat img_maybe64FC3_a = imread("64FC3.png" /*, IMREAD_COLOR*/); // Will be CV_8UC3
Mat img_maybe64FC3_b = imread("64FC3.png", IMREAD_ANYDEPTH); // Will be CV_8UC1
Mat img_maybe64FC3_c = imread("64FC3.png", IMREAD_UNCHANGED); // Will be CV_8UC3
Mat img_mustbe64FC3;
FileStorage fin("64FC3.yml", FileStorage::READ);
fin["img"] >> img_mustbe64FC3; // Will be CV_64FC3
fin.release();
return 0;
}

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.