Removing paper folding artifacts - opencv

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.

Related

OpenCV camera calibration with chessboard of different colours

A doubt came to my mind this morning: does the findChessboardCorners OpenCV function work with a chessboard of different colours, for example blue?
If it's not the case, do you think that a quite straightforward thresholding would do the trick?
You can't pass coloured images to the findChessboardCorners because it only takes a greyscale image as #api55 pointed out in his comment.
You might be worth taking a look at the checkchessboard code provided here
// does a fast check if a chessboard is in the input image. This is a workaround to
// a problem of cvFindChessboardCorners being slow on images with no chessboard
// - src: input binary image
// - size: chessboard size
// Returns 1 if a chessboard can be in this image and findChessboardCorners should be called,
// 0 if there is no chessboard, -1 in case of error
int checkChessboardBinary(const cv::Mat & img, const cv::Size & size)
{
CV_Assert(img.channels() == 1 && img.depth() == CV_8U);
Mat white = img.clone();
Mat black = img.clone();
int result = 0;
for ( int erosion_count = 0; erosion_count <= 3; erosion_count++ )
{
if ( 1 == result )
break;
if ( 0 != erosion_count ) // first iteration keeps original images
{
erode(white, white, Mat(), Point(-1, -1), 1);
dilate(black, black, Mat(), Point(-1, -1), 1);
}
vector<pair<float, int> > quads;
fillQuads(white, black, 128, 128, quads);
if (checkQuads(quads, size))
result = 1;
}
return result;
}
With the main loop being:
CV_IMPL
int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
CvPoint2D32f* out_corners, int* out_corner_count,
int flags )
is the main implementation of this method. In here they
Use cvCheckChessboard to determine if a chessboard is in the image
Convert to binary (B&W) and dilate to split the corners apart Use
icvGenerateQuads to find the squares.
So in answer to your question, as long as there is sufficient contrast in your image after you convert it to greyscale it will likely work, I would imagine a greyscaled blue and white image would be good enough, if it was a light aqua or yellow or something you might struggle without more processing

How to add Noise to Color Image - Opencv

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.

Reading colors of a rubik's cube using Opencv

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. =)

Using opencv matchtemplate for blister pack inspection

I am doing a project in which I have to inspect pharmaceutical blister pack for missing tablets.
I am trying to use opencv's matchTemplate function. Let me show the code and then some results.
int match(string filename, string templatename)
{
Mat ref = cv::imread(filename + ".jpg");
Mat tpl = cv::imread(templatename + ".jpg");
if (ref.empty() || tpl.empty())
{
cout << "Error reading file(s)!" << endl;
return -1;
}
imshow("file", ref);
imshow("template", tpl);
Mat res_32f(ref.rows - tpl.rows + 1, ref.cols - tpl.cols + 1, CV_32FC1);
matchTemplate(ref, tpl, res_32f, CV_TM_CCOEFF_NORMED);
Mat res;
res_32f.convertTo(res, CV_8U, 255.0);
imshow("result", res);
int size = ((tpl.cols + tpl.rows) / 4) * 2 + 1; //force size to be odd
adaptiveThreshold(res, res, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, size, -128);
imshow("result_thresh", res);
while (true)
{
double minval, maxval, threshold = 0.8;
Point minloc, maxloc;
minMaxLoc(res, &minval, &maxval, &minloc, &maxloc);
if (maxval >= threshold)
{
rectangle(ref, maxloc, Point(maxloc.x + tpl.cols, maxloc.y + tpl.rows), CV_RGB(0,255,0), 2);
floodFill(res, maxloc, 0); //mark drawn blob
}
else
break;
}
imshow("final", ref);
waitKey(0);
return 0;
}
And here are some pictures.
The "sample" image of a good blister pack:
The template cropped from "sample" image:
Result with "sample" image:
Missing tablet from this pack is detected:
But here are the problems:
I currently don't have any idea why this happens. Any suggestion and/or help is appreciated.
The original code that I followed and modified is here: http://opencv-code.com/quick-tips/how-to-handle-template-matching-with-multiple-occurences/
I found a solution for my own question. I just need to apply Canny edge detector on both image and template before throwing them to matchTemplate function. The full working code:
int match(string filename, string templatename)
{
Mat ref = cv::imread(filename + ".jpg");
Mat tpl = cv::imread(templatename + ".jpg");
if(ref.empty() || tpl.empty())
{
cout << "Error reading file(s)!" << endl;
return -1;
}
Mat gref, gtpl;
cvtColor(ref, gref, CV_BGR2GRAY);
cvtColor(tpl, gtpl, CV_BGR2GRAY);
const int low_canny = 110;
Canny(gref, gref, low_canny, low_canny*3);
Canny(gtpl, gtpl, low_canny, low_canny*3);
imshow("file", gref);
imshow("template", gtpl);
Mat res_32f(ref.rows - tpl.rows + 1, ref.cols - tpl.cols + 1, CV_32FC1);
matchTemplate(gref, gtpl, res_32f, CV_TM_CCOEFF_NORMED);
Mat res;
res_32f.convertTo(res, CV_8U, 255.0);
imshow("result", res);
int size = ((tpl.cols + tpl.rows) / 4) * 2 + 1; //force size to be odd
adaptiveThreshold(res, res, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, size, -64);
imshow("result_thresh", res);
while(1)
{
double minval, maxval;
Point minloc, maxloc;
minMaxLoc(res, &minval, &maxval, &minloc, &maxloc);
if(maxval > 0)
{
rectangle(ref, maxloc, Point(maxloc.x + tpl.cols, maxloc.y + tpl.rows), Scalar(0,255,0), 2);
floodFill(res, maxloc, 0); //mark drawn blob
}
else
break;
}
imshow("final", ref);
waitKey(0);
return 0;
}
Any suggestion for improvement is appreciated. I am strongly concerned about performance and robustness of my code, so I am looking for all ideas.
There are 2 things that got my nerves now: the lower Canny threshold and the negative constant on adaptiveThreshold function.
Edit: Here is the result, as you asked :)
Template:
Test image, missing 2 tablets:
Canny results of template and test image:
matchTemplate result (converted to CV_8U):
After adaptiveThreshold:
Final result:
I don't think think the adaptive threshold is a good choice.
What you need to do here is called non-maximum suppression. You have an image with multiple local maxima, and you want to remove all pixels that are not local maxima.
cv::dilate(res_32f, res_dilated, null, 5);
cv::compare(res_32f, res_dilated, mask_local_maxima, cv::CMP_GE);
cv::set(res_32f, 0, mask_local_maxima)
Now all pixels in the res_32f image that are not local maxima are set to zero. All the maximum pixels are still at their original value, so you can adjust the threshold later in the line
double minval, maxval, threshold = 0.8;
All local maxima should also now be surrounded by enough zeroes that the floodfill will not extend too far.
Now I think you should be able to adjust the threshold to exclude all false positives.
If this is not enough, here is another suggestion:
Instead of just one template, I would run the search with multiple templates; your current template,and one with a tablet from the right side and the left side of the pack. Due to perspective these tablets look quite a bit different. Keep track of the found tablets so you do not detect the smae tablet multiple times.
With these multiple templates you can raise the threshold even higher.
One further refinement: if the detection is still too erratic, try blurring your template and search image with a Gaussian blur. This will remove fine details and noise that may throw of the matchTemplate function, while leaving the larger structures intact.
Using a canny filter instead seems unreliable to me: It seems to rely on the fact that a removed tablet region will have more edges at the center. But I am not sure if this will always be the case; and you discard a lot of information about color and brightness with the canny filter, so I would expect worse results.
(that said, if it works for you, it works)
Have you tried the Surf algorithm in order to get more detailed descriptors? You could try to collect descriptor for both the full and the empty sample image. And perform different action for each one of thr object detected.

palm veins enhancement with OpenCV

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) :

Resources