I'm trying to implement in OpenCV an algorithm to bring out the details of a palm vein pattern. I've based myself on a paper called "A Contactless Biometric System Using Palm Print and Palm Vein Features" that I've found on the Internet. The part I'm interested in is the chapter 3.2 Pre-processing. The steps involved are shown there.
I'd like to do the implementation using OpenCV but until now I'm stuck hard. Especially they use a Laplacian filter on the response of a low-pass filter to isolate the principal veins but my result gets very noisy, no matter the parameters I try!
Any help would be greatly appreciated!
Ok finally I've figured out by myself how to do it. Here is my code :
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#define THRESHOLD 150
#define BRIGHT 0.7
#define DARK 0.2
using namespace std;
using namespace cv;
int main()
{
// Read source image in grayscale mode
Mat img = imread("roi.png", CV_LOAD_IMAGE_GRAYSCALE);
// Apply ??? algorithm from https://stackoverflow.com/a/14874992/2501769
Mat enhanced, float_gray, blur, num, den;
img.convertTo(float_gray, CV_32F, 1.0/255.0);
cv::GaussianBlur(float_gray, blur, Size(0,0), 10);
num = float_gray - blur;
cv::GaussianBlur(num.mul(num), blur, Size(0,0), 20);
cv::pow(blur, 0.5, den);
enhanced = num / den;
cv::normalize(enhanced, enhanced, 0.0, 255.0, NORM_MINMAX, -1);
enhanced.convertTo(enhanced, CV_8UC1);
// Low-pass filter
Mat gaussian;
cv::GaussianBlur(enhanced, gaussian, Size(0,0), 3);
// High-pass filter on computed low-pass image
Mat laplace;
Laplacian(gaussian, laplace, CV_32F, 19);
double lapmin, lapmax;
minMaxLoc(laplace, &lapmin, &lapmax);
double scale = 127/ max(-lapmin, lapmax);
laplace.convertTo(laplace, CV_8U, scale, 128);
// Thresholding using empirical value of 150 to create a vein mask
Mat mask;
cv::threshold(laplace, mask, THRESHOLD, 255, CV_THRESH_BINARY);
// Clean-up the mask using open morphological operation
morphologyEx(mask,mask,cv::MORPH_OPEN,
getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5,5)));
// Connect the neighboring areas using close morphological operation
Mat connected;
morphologyEx(mask,mask,cv::MORPH_CLOSE,
getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(11,11)));
// Blurry the mask for a smoother enhancement
cv::GaussianBlur(mask, mask, Size(15,15), 0);
// Blurry a little bit the image as well to remove noise
cv::GaussianBlur(enhanced, enhanced, Size(3,3), 0);
// The mask is used to amplify the veins
Mat result(enhanced);
ushort new_pixel;
double coeff;
for(int i=0;i<mask.rows;i++){
for(int j=0;j<mask.cols;j++){
coeff = (1.0-(mask.at<uchar>(i,j)/255.0))*BRIGHT + (1-DARK);
new_pixel = coeff * enhanced.at<uchar>(i,j);
result.at<uchar>(i,j) = (new_pixel>255) ? 255 : new_pixel;
}
}
// Show results
imshow("frame", img);
waitKey();
imshow("frame", result);
waitKey();
return 0;
}
So the main steps of the paper are followed here. For some parts I've inspired myself on code I've found. It's the case for the first processing I apply that I've found here. Also for the High-pass filter (laplacian) I've inspired myself on the code given in OpenCV 2 Computer Vision Application Programming Cookbook.
Finally I've done some little improvements by allowing to modify the brightness of the background and the darkness of the veins (see defines BRIGHT and DARK). I've also decided to blur a bit the mask to have a more "natural" enhancement.
Here the results (Source / Paper result / My result) :
Related
I'm trying to to add noise to an Image & then Denoise it to test my DeNoising algorithm! So for benchmark i'm referring this Online Test samples. I'm trying to replicate the Noise model.
With reference to this threads 1 , 2 I'm adding noise to image like this!
Mat mSource_Bgr;
mSource_Bgr= imread(FileName_S,1);
double m_NoiseStdDev=10;
Mat mNoise_Bgr = mSource_Bgr.clone();
Mat mGaussian_noise = Mat(mSource_Bgr.size(),CV_8UC3);
randn(mGaussian_noise,0,m_NoiseStdDev);
mNoise_Bgr += mGaussian_noise;
normalize(mNoise_Bgr,mNoise_Bgr,0, 255, CV_MINMAX, CV_8UC3);
imshow("Output Window",mNoise_Bgr);
//imshow("Gaussian Noise",mGaussian_noise);
My Input Image
Output Image with Noise
Problem:
Adding Noise to the image alters overall brightness of the Image which in turn alters my final results PSNR!
I want to get the results as much as closer to this one!
What i have tried so far!
I have tried to add the noise only in the color channel.
Convert the Input image into YUV Color space
Add the Noise only in the UV Color Channels & Keep the Y channel unaltered.
Results are very bad & the overall color of the image is getting altered! Will add the code if needed!
So any advice regarding this is much appreciated! May be give me some formulas for adding Noise to the image!
Thank you #Andrey Smorodov For your insights!
I got it working! Here is my updated code for adding Noise in a Color Image. Hope this will be useful for someone!
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
inline BYTE Clamp(int n)
{
n = n>255 ? 255 : n;
return n<0 ? 0 : n;
}
bool AddGaussianNoise(const Mat mSrc, Mat &mDst,double Mean=0.0, double StdDev=10.0)
{
if(mSrc.empty())
{
cout<<"[Error]! Input Image Empty!";
return 0;
}
Mat mGaussian_noise = Mat(mSrc.size(),CV_16SC3);
randn(mGaussian_noise,Scalar::all(Mean),Scalar::all(StdDev));
for (int Rows = 0; Rows < mSrc.rows; Rows++)
{
for (int Cols = 0; Cols < mSrc.cols; Cols++)
{
Vec3b Source_Pixel= mSrc.at<Vec3b>(Rows,Cols);
Vec3b &Des_Pixel= mDst.at<Vec3b>(Rows,Cols);
Vec3s Noise_Pixel= mGaussian_noise.at<Vec3s>(Rows,Cols);
for (int i = 0; i < 3; i++)
{
int Dest_Pixel= Source_Pixel.val[i] + Noise_Pixel.val[i];
Des_Pixel.val[i]= Clamp(Dest_Pixel);
}
}
}
return true;
}
bool AddGaussianNoise_Opencv(const Mat mSrc, Mat &mDst,double Mean=0.0, double StdDev=10.0)
{
if(mSrc.empty())
{
cout<<"[Error]! Input Image Empty!";
return 0;
}
Mat mSrc_16SC;
Mat mGaussian_noise = Mat(mSrc.size(),CV_16SC3);
randn(mGaussian_noise,Scalar::all(Mean), Scalar::all(StdDev));
mSrc.convertTo(mSrc_16SC,CV_16SC3);
addWeighted(mSrc_16SC, 1.0, mGaussian_noise, 1.0, 0.0, mSrc_16SC);
mSrc_16SC.convertTo(mDst,mSrc.type());
return true;
}
int main(int argc, const char* argv[])
{
Mat mSource= imread("input.png",1);
imshow("Source Image",mSource);
Mat mColorNoise(mSource.size(),mSource.type());
AddGaussianNoise(mSource,mColorNoise,0,10.0);
imshow("Source + Color Noise",mColorNoise);
AddGaussianNoise_Opencv(mSource,mColorNoise,0,10.0);//I recommend to use this way!
imshow("Source + Color Noise OpenCV",mColorNoise);
waitKey();
return 0;
}
Looks like your noise matrix can't get negative values as it have unsigned char element type. Try operate with real valued matrices, it should help.
There are mainly two methods to add say awgn noise (mean = 0, standard deviation = 30) to a colored image.
First: You can add the awgn noise of mean = 0, standard deviation = 30 to each of Red, Green, and Blue channels independently (or any other color model-HSI, YUV, Lab); and then combine the noisy channels to form the colored noisy image.
Second: To use the in-built function to add noise to the colored image directly. eg. imnoise() in Matlab.
I tried with both the methods (imnoise and independently), I got the same result.
You mentioned "I have tried to add the noise only in the color channel.
Convert the Input image into YUV Color space
Add the Noise only in the UV Color Channels & Keep the Y channel unaltered."
If you are using the YUV color model, I would suggest you do the opposite. Keep U, and V channel unaltered and add noise only to the Y channel only.
I have written a C++ program using OpenCV that can detect and highlight the edges of any object from a live video. But now I don't know how to extract the four corners of the cube from the many edges that are being detected in the video. So I am looking for some help here.
Here is the link of the paper that I am using as a guide for my this project.
http://www.cs.ubc.ca/~andrejk/525project/525report.pdf
You can find the program code for this paper in the link below. It's written in Python. (I am using C++ and I don't know Python)
http://www.cs.ubc.ca/~andrejk/525project/cubefinder.py
According to the paper the next step would be, 'edge segmentation with adaptive threshold.'
Which I don't really understand. And also I don't know how to extract the corners of the cube then.
The short summary of the method that I have used is as following.
1. Input from webcam
2. Apply Laplacian filter
3. Apply Hough Line Transform.
I get the following result.
Code
using namespace std;
using namespace cv;
Mat laplacianFilter(Mat image)
{
Mat hImage;
GaussianBlur(image,hImage,Size(3,3),0,0,BORDER_DEFAULT);
cvtColor(hImage,hImage,CV_RGB2GRAY);
Laplacian(hImage,hImage,CV_16SC1,3,1,0,BORDER_DEFAULT);
convertScaleAbs(hImage,hImage,1,0);
return hImage;
}
Mat hghTransform(Mat image, Mat &image2)
{
Mat lImage;
Canny(image,image,50,200,3);
cvtColor(image,lImage,CV_GRAY2BGR);
vector<Vec4i> lines;
HoughLinesP(image, lines, 1, CV_PI/180, 50, 50, 10 );
for( size_t i = 0; i < lines.size(); i++ )
{
Vec4i l = lines[i];
line( image2, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,255,0), 3, CV_AA);
}
return lImage;
}
int main()
{
int c;
VideoCapture cap(0);
Mat image;
Mat image2;
namedWindow("hghtransform");
namedWindow("laplacianfilter");
namedWindow("cannyOutput");
while(1)
{
cap>>image;
cap>>image2;
//Output
imshow("laplacianfilter",laplacianFilter(image));
imshow("cannyOutput",hghTransform(laplacianFilter(image),image2));
imshow("hghtransform",image2);
c=waitKey(33);
if(c==27)
return 0;
}
return 0;
}
Adaptive threshold will give you a clear line of edges which enables you to get 9 squares of a rubik side properly.
You can see a decent comparison of global and adaptive threshold here:
here: https://sites.google.com/site/qingzongtseng/adaptivethreshold
original image:
global threshold:
adaptive threshold:
For the corner, I am not sure whether it's stated in the paper, but I would do something like:
==> finding area like 1, 2, 3, 4 for upper-left, upper-right, lower-left, and lower-right corner respectively
==> with a template matching algorithm.
hope it helps.
note: you might want to have a background with less noise there. =)
I need to recognize some handwriting on text written with soft pen. Using OpenCV, different thresholding methods, bilateral filtering etc, I get quite good results extracting text from paper. But I also get artifacts from folding:
I cannot change way how paper is handled or photographed before it will be processed. After thresholding same paper looks like this:
I want to remove these artifacts. Biggest trouble for me is situation when some character like "T" happens to be on this line. Horizontal part of "T" may nicely fit to this line.
What I do now: I can detect if there is a standalone line. If something is few pixels tall and very wide, I eliminate it.
I have been reading a lot of information about shadow elimination (because I assume problem is shadow). But they all expect to work in other context - surveillance video feed or image with color background.
Any ideas?
UPDATE:
Was working on ideas based on similar works: http://ivrgwww.epfl.ch/alumni/fredemba/papers/FFICPR06.pdf
Test input
Output of test code:
Source code:
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int filt1_trackbar=13;
int filt2_trackbar=49;
int filt3_trackbar=6;
int main( int argc, char** argv ) {
Mat src, shadow;
src = imread( argv[1], 1 );
if( !src.data ) {
return -1;
}
Mat histImage1( src.rows, src.cols, CV_8UC3, Scalar(127,127,127) );
Mat histImage2( src.rows, src.cols, CV_8UC3, Scalar(127,127,127) );
int cn = src.channels();
uint8_t* pixelPtr = (uint8_t*)src.data;
for(int i=0 ; i< src.rows;i++) {
for(int j=0 ; j< src.cols;j++) {
Scalar_<uint8_t> bgrPixel;
bgrPixel.val[0] = pixelPtr[i*src.cols*cn + j*cn + 0]; // B
bgrPixel.val[1] = pixelPtr[i*src.cols*cn + j*cn + 1]; // G
bgrPixel.val[2] = pixelPtr[i*src.cols*cn + j*cn + 2]; // R
if(bgrPixel.val[2] !=0 ) { // avoid division by zero
float a= 100.0*(((float)bgrPixel.val[0] / (float)bgrPixel.val[2])); // B/R
float b= 100.0*(((float)bgrPixel.val[1] / (float)bgrPixel.val[2])); // G/R
if(!isinf(a) && !isinf(b)) {
histImage1.at<Vec3b>(i,j)=Vec3b(a,a,a);
histImage2.at<Vec3b>(i,j)=Vec3b(b,b,b);
}
}
}
}
addWeighted(histImage1, 2.0, histImage2, -1.0, 0, shadow);
Mat hsv1,hsv2;
cvtColor(shadow, hsv1, CV_BGR2HSV);
cvtColor(src, hsv2, CV_BGR2HSV);
vector<Mat> channels1;
vector<Mat> channels2;
split(hsv1, channels1);
split(hsv2, channels2);
addWeighted(channels1[2], 0.5, channels2[2], 0.5, 0, channels1[2]);
insertChannel(channels1[2],hsv2,2);
Mat unshadow;
cvtColor(hsv2,unshadow, CV_HSV2BGR);
namedWindow( "src", WINDOW_NORMAL);
namedWindow( "shadow", WINDOW_NORMAL);
namedWindow( "unshadow", WINDOW_NORMAL);
imshow("src", src);
imshow("shadow", shadow);
imshow("unshadow", unshadow);
imwrite("shadow.png", shadow);
imwrite("unshadow.png", unshadow);
waitKey(0);
return 0;
}
It did improve image but not good enough in my opinion. I was impressed it worked at all on such grayscale context. Maybe someone can spot something wrong?
I will write an "Answer" because it is too much for a comment:
Shadow removal is (in my experience) not easy, you might be interested in this Paper: "Fredembach and Finlayson - Simple Shadow Removal"
Another idea i got a while back while working on a similar problem (i haven't tried it myself):
You basically want to identify big (in comparison to the characters) regions on your image and treat them differently. If you would know the shadow regions you could for example make the pages more uniform by brightening up the darker regions. The Question is how you can obtain this large regions.
You could first colour the dark writing in the same clour as the surrounding paper. Afterwards you could use the Bilateral Filter of OpenCV to get large uniform colour patches. You could identify the borders with a contour detection and you'd know where the paper differs in colour (caused by the shadows).
Hopefully this post shines a new light on your problem and gives you some ideas.
can anyone help me how to count the number of non directional edge using opencv cannyedge detection? I have a cannyEdge image from opencv and I would like to have an histogram based on edge directions and there by i can count he number of directional and non directional edges.
I think you are confusing edge detection with gradient detection. Canny provides an edge map based on the gradient magnitude (normally using a Sobel operator, but it can use others) because Canny only returns the thresholded gradient magnitude information it cannot provide you with the orientation information.
EDIT : I should clarify that the Canny algorithm does use gradient orientation for the non-maximum suppression step. However, the OpenCV implementation of Canny hides this orientation information from you, and only returns an edge magnitude map.
The basic algorithm to get magnitude and orientation of the gradient is as follows:
Compute Sobel in the X direction (Sx).
Compute Sobel in the Y direction (Sy).
Compute the gradient magnitude sqrt(Sx*Sx + Sy*Sy).
Compute the gradient orientation with arctan(Sy / Sx).
This algorithm can be implemented using the following OpenCV functions: Sobel, magnitude, and phase.
Below is a sample that computes the gradient magnitude and phase as well as shows a coarse color mapping of the gradient orientations:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
Mat mat2gray(const cv::Mat& src)
{
Mat dst;
normalize(src, dst, 0.0, 255.0, cv::NORM_MINMAX, CV_8U);
return dst;
}
Mat orientationMap(const cv::Mat& mag, const cv::Mat& ori, double thresh = 1.0)
{
Mat oriMap = Mat::zeros(ori.size(), CV_8UC3);
Vec3b red(0, 0, 255);
Vec3b cyan(255, 255, 0);
Vec3b green(0, 255, 0);
Vec3b yellow(0, 255, 255);
for(int i = 0; i < mag.rows*mag.cols; i++)
{
float* magPixel = reinterpret_cast<float*>(mag.data + i*sizeof(float));
if(*magPixel > thresh)
{
float* oriPixel = reinterpret_cast<float*>(ori.data + i*sizeof(float));
Vec3b* mapPixel = reinterpret_cast<Vec3b*>(oriMap.data + i*3*sizeof(char));
if(*oriPixel < 90.0)
*mapPixel = red;
else if(*oriPixel >= 90.0 && *oriPixel < 180.0)
*mapPixel = cyan;
else if(*oriPixel >= 180.0 && *oriPixel < 270.0)
*mapPixel = green;
else if(*oriPixel >= 270.0 && *oriPixel < 360.0)
*mapPixel = yellow;
}
}
return oriMap;
}
int main(int argc, char* argv[])
{
Mat image = Mat::zeros(Size(320, 240), CV_8UC1);
circle(image, Point(160, 120), 80, Scalar(255, 255, 255), -1, CV_AA);
imshow("original", image);
Mat Sx;
Sobel(image, Sx, CV_32F, 1, 0, 3);
Mat Sy;
Sobel(image, Sy, CV_32F, 0, 1, 3);
Mat mag, ori;
magnitude(Sx, Sy, mag);
phase(Sx, Sy, ori, true);
Mat oriMap = orientationMap(mag, ori, 1.0);
imshow("magnitude", mat2gray(mag));
imshow("orientation", mat2gray(ori));
imshow("orientation map", oriMap);
waitKey();
return 0;
}
Using a circle image:
This results in the following magnitude and orientation images:
Finally, here is the gradient orientation map:
UPDATE : Abid actually asked a great question in the comments "what is meant by orientation here?", which I thought needed some further discussion. I am assuming that the phase function doesn't switch coordinate frames from the normal image processing standpoint of positive y-axis is down, and positive x-axis is right. Given this assumption that leads to following image showing the gradient orientation vectors around the circle:
This can be difficult to get used to since the axes are flipped from what we are normally used to in math class... So, gradient orientation is the angle made by the normal vector to the gradient surface in the direction of increasing change.
Hope you found that helpful!
I am doing some detection work using OpenCV, and I need to use the distance transform. Except the distance transform function in opencv gives me an image that is exactly the same as the image I use as source. Anyone know what I am doing wrong? Here is the portion of my code:
cvSetData(depthImage, m_rgbWk, depthImage->widthStep);
//gotten openCV image in "depthImage"
IplImage *single_channel_depthImage = cvCreateImage(cvSize(320, 240), 8, 1);
cvSplit(depthImage, single_channel_depthImage, NULL, NULL, NULL);
//smoothing
IplImage *smoothed_image = cvCreateImage(cvSize(320, 240), 8, 1);
cvSmooth(single_channel_depthImage, smoothed_image, CV_MEDIAN, 9, 9, 0, 0);
//do canny edge detector
IplImage *edges_image = cvCreateImage(cvSize(320, 240), 8, 1);
cvCanny(smoothed_image, edges_image, 100, 200);
//invert values
IplImage *inverted_edges_image = cvCreateImage(cvSize(320, 240), 8, 1);
cvNot(edges_image, inverted_edges_image);
//calculate the distance transform
IplImage *distance_image = cvCreateImage(cvSize(320, 240), IPL_DEPTH_32F, 1);
cvZero(distance_image);
cvDistTransform(inverted_edges_image, distance_image, CV_DIST_L2, CV_DIST_MASK_PRECISE, NULL, NULL);
In a nutshell, I grad the image from the kinect, turn it into a one channel image, smooth it, run the canny edge detector, invert the values, and then I do the distance transform. But the transformed image looks exactly the same as the input image. What's wrong?
Thanks!
I believe the key here is that they look the same. Here is a small program I wrote to show the difference:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat before = imread("qrcode.png", 0);
Mat dist;
distanceTransform(before, dist, CV_DIST_L2, 3);
imshow("before", before);
imshow("non-normalized", dist);
normalize(dist, dist, 0.0, 1.0, NORM_MINMAX);
imshow("normalized", dist);
waitKey();
return 0;
}
In the non-normalized image, you see this:
which doesn't really look like it changed anything, but the distance steps are very small compared to the overall range of values [0, 255] (due to imshow converting the image from 32-bit float to 8-bits for display), we can't see the differences, so let's normalize it...
Now we get this:
The values themselves should be correct, but when displayed you will need to normalize the image to see the difference.
EDIT :
Here is a small 10x10 sample from the upper-left corner of the dist matrix show that the values are in fact different:
[10.954346, 10.540054, 10.125763, 9.7114716, 9.2971802, 8.8828888, 8.4685974, 8.054306, 7.6400146, 7.6400146;
10.540054, 9.5850525, 9.1707611, 8.7564697, 8.3421783, 7.927887, 7.5135956, 7.0993042, 6.6850128, 6.6850128;
10.125763, 9.1707611, 8.2157593, 7.8014679, 7.3871765, 6.9728851, 6.5585938, 6.1443024, 5.730011, 5.730011;
9.7114716, 8.7564697, 7.8014679, 6.8464661, 6.4321747, 6.0178833, 5.6035919, 5.1893005, 4.7750092, 4.7750092;
9.2971802, 8.3421783, 7.3871765, 6.4321747, 5.4771729, 5.0628815, 4.6485901, 4.2342987, 3.8200073, 3.8200073;
8.8828888, 7.927887, 6.9728851, 6.0178833, 5.0628815, 4.1078796, 3.6935883, 3.2792969, 2.8650055, 2.8650055;
8.4685974, 7.5135956, 6.5585938, 5.6035919, 4.6485901, 3.6935883, 2.7385864, 2.324295, 1.9100037, 1.9100037;
8.054306, 7.0993042, 6.1443024, 5.1893005, 4.2342987, 3.2792969, 2.324295, 1.3692932, 0.95500183, 0.95500183;
7.6400146, 6.6850128, 5.730011, 4.7750092, 3.8200073, 2.8650055, 1.9100037, 0.95500183, 0, 0;
7.6400146, 6.6850128, 5.730011, 4.7750092, 3.8200073, 2.8650055, 1.9100037, 0.95500183, 0, 0]
I just figured this one out.
The OpenCV distanceTransform
Calculates the distance to the closest zero pixel for each pixel of
the source image.
and so it expects your edges image to be negative.
All you need to do is to negate your edges image:
edges = 255 - edges;
You can print this values using this code before normalize function:
for(int x=0; x<10;x++)
{
cout<<endl;
for(int y=0; y<10;y++)
cout<<std::setw(10)<<dist.at<float>(x, y);
}
Mat formats
Input: CV_8U
Dist: CV_32F
Normalized: CV_8U
normalize(Mat_dist, Mat_norm, 0, 255, NORM_MINMAX, CV_8U);
If you want to visualize the result, you need to scale the normalization to 0 ... 255 and not to 0 ... 1 or everything will seem black. Using imshow(); on a scaled to 0 ... 1 image will work but may cause problmes in the next processing steps. Al least it did in my case.