I wanted to binarize low quality images and found that the existing solutions or programs which are implementations of global and local binarization techniques such as Sauvola’s method, NiBlack's method etc are not off much use.
I did find a few good papers regarding much better methods like the ones given in the papers:
1) http://www.ski.org/sites/default/files/publications/wacv11-display-reader.pdf#cite.adap-binar
2) https://www.jstage.jst.go.jp/article/elex/1/16/1_16_501/_pdf
But I haven't worked on image processing much before and so I wanted to know how I could proceed to implement it and what knowledge I need to implement these algorithms
I implemented the binarization of the first paper in like 10 minutes (less time than processing the 2nd image) - no guarantee that it's correct, better have a look at the formulas yourself:
int main()
{
//cv::Mat input = cv::imread("../inputData/Lenna.png");
cv::Mat input = cv::imread("../inputData/LongLineColor.jpg");
cv::Mat gray;
cv::cvtColor(input,gray,CV_BGR2GRAY);
cv::Mat binaryImage = cv::Mat::zeros(gray.rows, gray.cols, CV_8UC1);
// binarization:
// TODO: adjust to your application:
int smallWindowSize = 17; // suggested by the paper
int bigWindowSize = 35; // suggested by the paper
// TODO: adjust to your application
double minTau = 10 ;
// create roi relative to (0,0)
cv::Rect roiTemplate1 = cv::Rect(-smallWindowSize/2,-smallWindowSize/2, smallWindowSize, smallWindowSize);
cv::Rect roiTemplate2 = cv::Rect(-bigWindowSize/2,-bigWindowSize/2, bigWindowSize, bigWindowSize);
cv::Rect imgROI = cv::Rect(0,0, gray.cols, gray.rows);
for(int y=0; y<gray.rows; ++y)
{
std::cout << y << std::endl;
for(int x=0; x<gray.cols; ++x)
{
double pixelThreshold = 255;
// small roi
cv::Rect cROIs = roiTemplate1 + cv::Point(x,y);
// test whether ROI is inside the image. Reduce otherwise:
cROIs = cROIs & imgROI;
if(cROIs.width == 0 || cROIs.height == 0)
continue; // ignore this pixel
// large roi
cv::Rect cROIl = roiTemplate2 + cv::Point(x,y);
cROIl = cROIl & imgROI;
if(cROIl.width == 0 || cROIl.height == 0)
continue; // ignore this pixel
cv::Mat subSmall = gray(cROIs);
cv::Mat subLarge = gray(cROIl);
// evaluate subimages:
// standard deviations
double stdDevS =0;
double stdDevL =0;
// mean value
double meanS =0;
double minL =DBL_MAX;
double meanL =0;
// mean of small region
for(int j=0; j<subSmall.rows; ++j)
for(int i=0; i<subSmall.cols; ++i)
{
meanS += subSmall.at<unsigned char>(j,i);
}
meanS = meanS/ (double)(subSmall.cols*subSmall.rows);
// stddev of small region
for(int j=0; j<subSmall.rows; ++j)
for(int i=0; i<subSmall.cols; ++i)
{
double diff = subSmall.at<unsigned char>(j,i) - meanS;
stdDevS += diff*diff;
}
stdDevS = sqrt(stdDevS/(double)(subSmall.cols*subSmall.rows));
// mean and min of large region
for(int j=0; j<subLarge.rows; ++j)
for(int i=0; i<subLarge.cols; ++i)
{
if(subLarge.at<unsigned char>(j,i) < minL)
{
minL = subLarge.at<unsigned char>(j,i);
meanL += subLarge.at<unsigned char>(j,i);
}
}
meanL = meanL/ (double)(subLarge.cols*subLarge.rows);
// stddef of large region
for(int j=0; j<subLarge.rows; ++j)
for(int i=0; i<subLarge.cols; ++i)
{
double diff = subLarge.at<unsigned char>(j,i) - meanL;
stdDevL += diff*diff;
}
stdDevL = sqrt(stdDevL/(double)(subLarge.cols*subLarge.rows));
// formula (2)
double tau = ((meanS - minL) * (1-stdDevS/stdDevL))/2.0;
// minimum
if(tau < minTau) tau = minTau;
// formula (1)
double Threshold = meanS - tau;
// for debugging:
/*
std::cout << " meanS:" << meanS << std::endl;
std::cout << " std S:" << stdDevS << std::endl;
std::cout << " min L:" << minL << std::endl;
std::cout << " meanL:" << meanL << std::endl;
std::cout << " std L:" << stdDevL << std::endl;
std::cout << " threshold: " << Threshold << std::endl;
*/
unsigned char pixelVal = gray.at<unsigned char>(y,x);
if(pixelVal >= Threshold)
binaryImage.at<unsigned char>(y,x) = 255;
else
binaryImage.at<unsigned char>(y,x) = 0;
}
}
cv::imshow("input", input);
cv::imshow("binary", binaryImage);
//cv::imwrite("../outputData/binaryCustom.png", binaryImage);
cv::waitKey(0);
return 0;
}
giving me these results:
and
It is very slow but not optimized or encapsulated at all ;)
And the results aren't sooo good imho. Probably you have to adjust the windowSizes to your application/task/objectSize
Related
I am trying to build a test project to compare the openCv solvePnP implementation with the openGv one.
the opencv is detailed here:
https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#solvepnp
and the openGv here:
https://laurentkneip.github.io/opengv/page_how_to_use.html
Using the opencv example code, I am finding a chessboard in an image, and constructing the matching 3d points. i run the cv pnp, then set up the Gv solver. the cv pnp runs fine, and prints the values:
//rotation
-0.003040771263293328, 0.9797142824436152, -0.2003763421317906;
0.0623096853748876, 0.2001735322445355, 0.977777101438374]
//translation
[-12.06549797067309;
-9.533070368412945;
37.6825295047483]
I test by reprojecting the 3d points, and it looks good.
The Gv Pnp, however, prints nan for all values. i have tried to follow the example code, but I must be making a mistake somewhere. The code is:
int main(int argc, char **argv) {
cv::Mat matImg = cv::imread("chess.jpg");
cv::Size boardSize(8, 6);
//Construct the chessboard model
double squareSize = 2.80;
std::vector<cv::Point3f> objectPoints;
for (int i = 0; i < boardSize.height; i++) {
for (int j = 0; j < boardSize.width; j++) {
objectPoints.push_back(
cv::Point3f(double(j * squareSize), float(i * squareSize), 0));
}
}
cv::Mat rvec, tvec;
cv::Mat cameraMatrix, distCoeffs;
cv::FileStorage fs("CalibrationData.xml", cv::FileStorage::READ);
fs["cameraMatrix"] >> cameraMatrix;
fs["dist_coeffs"] >> distCoeffs;
//Found chessboard corners
std::vector<cv::Point2f> imagePoints;
bool found = cv::findChessboardCorners(matImg, boardSize, imagePoints, cv::CALIB_CB_FAST_CHECK);
if (found) {
cv::drawChessboardCorners(matImg, boardSize, cv::Mat(imagePoints), found);
//SolvePnP
cv::solvePnP(objectPoints, imagePoints, cameraMatrix, distCoeffs, rvec, tvec);
drawAxis(matImg, cameraMatrix, distCoeffs, rvec, tvec, squareSize);
}
//cv to matrix
cv::Mat R;
cv::Rodrigues(rvec, R);
std::cout << "results from cv:" << R << tvec << std::endl;
//START OPEN GV
//vars
bearingVectors_t bearingVectors;
points_t points;
rotation_t rotation;
//add points to the gv type
for (int i = 0; i < objectPoints.size(); ++i)
{
point_t pnt;
pnt.x() = objectPoints[i].x;
pnt.y() = objectPoints[i].y;
pnt.z() = objectPoints[i].z;
points.push_back(pnt);
}
/*
K is the common 3x3 camera matrix that you can compose with cx, cy, fx, and fy.
You put the image point into homogeneous form (append a 1),
multiply it with the inverse of K from the left, which gives you a normalized image point (a spatial direction vector).
You normalize that to norm 1.
*/
//to homogeneous
std::vector<cv::Point3f> imagePointsH;
convertPointsToHomogeneous(imagePoints, imagePointsH);
//multiply by K.Inv
for (int i = 0; i < imagePointsH.size(); i++)
{
cv::Point3f pt = imagePointsH[i];
cv::Mat ptMat(3, 1, cameraMatrix.type());
ptMat.at<double>(0, 0) = pt.x;
ptMat.at<double>(1, 0) = pt.y;
ptMat.at<double>(2, 0) = pt.z;
cv::Mat dstMat = cameraMatrix.inv() * ptMat;
//store as bearing vector
bearingVector_t bvec;
bvec.x() = dstMat.at<double>(0, 0);
bvec.y() = dstMat.at<double>(1, 0);
bvec.z() = dstMat.at<double>(2, 0);
bvec.normalize();
bearingVectors.push_back(bvec);
}
//create a central absolute adapter
absolute_pose::CentralAbsoluteAdapter adapter(
bearingVectors,
points,
rotation);
size_t iterations = 50;
std::cout << "running epnp (all correspondences)" << std::endl;
transformation_t epnp_transformation;
for (size_t i = 0; i < iterations; i++)
epnp_transformation = absolute_pose::epnp(adapter);
std::cout << "results from epnp algorithm:" << std::endl;
std::cout << epnp_transformation << std::endl << std::endl;
return 0;
}
Where am i going wrong in setting up the openGv Pnp solver?
Years later, i had this same issue, and solved it. To convert openCv to openGV bearing vectors, you can do this:
bearingVectors_t bearingVectors;
std::vector<cv::Point2f> dd2;
const int N1 = static_cast<int>(dd2.size());
cv::Mat points1_mat = cv::Mat(dd2).reshape(1);
// first rectify points and construct homogeneous points
// construct homogeneous points
cv::Mat ones_col1 = cv::Mat::ones(N1, 1, CV_32F);
cv::hconcat(points1_mat, ones_col1, points1_mat);
// undistort points
cv::Mat points1_rect = points1_mat * cameraMatrix.inv();
// compute bearings
points2bearings3(points1_rect, &bearingVectors);
using this function for the final conversion:
// Convert a set of points to bearing
// points Matrix of size Nx3 with the set of points.
// bearings Vector of bearings.
void points2bearings3(const cv::Mat& points,
opengv::bearingVectors_t* bearings) {
double l;
cv::Vec3f p;
opengv::bearingVector_t bearing;
for (int i = 0; i < points.rows; ++i) {
p = cv::Vec3f(points.row(i));
l = std::sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * p[2]);
for (int j = 0; j < 3; ++j) bearing[j] = p[j] / l;
bearings->push_back(bearing);
}
}
Given a contour such as the one seen below, is there a way to get the X,Y coordinates of the top point in the contour? I'm using Python, but other language examples are fine.
Since every pixel needs to be checked, I'm afraid you will have to iterate linewise over the image and see which is the first white pixel.
You can iterate over the image until you encounter a pixel that isn't black.
I will write an example in C++.
cv::Mat image; // your binary image with type CV_8UC1 (8-bit 1-channel image)
int topRow(-1), topCol(-1);
for(int i = 0; i < image.rows; i++) {
uchar* ptr = image.ptr<uchar>(i);
for(int j = 0; j < image.cols; j++) {
if(ptr[j] != 0) {
topRow = i;
topCol = j;
std::cout << "Top point: " << i << ", " << j << std::endl;
break;
}
}
if(topRow != -1)
break;
}
I have this simple question.I want to create a normal 2D Matrix to use as bin to put integers, and increment some elements, but it doesn't work,why?it just prints some unknown symboes.
here is my code
Mat img = imread("img.jpg", 0);
Mat bin = Mat::zeros(img.size(),CV_8U);//also tried 8UC1
for (size_t i = 0; i < 100; i++)
{
bin.at<uchar>(i, 50) = 200;
cout << bin.at<uchar>(i, 50) << endl;
//(bin.at<uchar>(i,50))++;//if above statement works then I will use this incrementer
}
I'm new in OpenCV, and I want to thresholding the image by myself without using Threshold function in opencv, because the time spend on function threshold is to high for me.
Here is my code:
Mat src = imread("D:\\DataBox\\7.jpg", 0);
for (int i = 0; i < src.cols; i++) {
cout << i << endl;
for (int j = 0; j < src.rows; j++) {
if (src.at<uchar>(i, j) > 70) {
src.at<uchar>(i, j) = 0;
cout << j << endl;
}
else
src.at<uchar>(i, j) = 255;
}
}
but it still says:
"OpenCV Error: Assertion failed (dims <= 2 && data && (unsigned)i0 < (unsigned)size.p[0] && (unsigned)(i1 * DataType<_Tp>::channels) < (unsigned)(size.p[1] * channels()) && ((((sizeof(size_t)<<28)|0x8442211) >> ((DataType<_Tp>::depth) & ((1 << 3) - 1))*4) & 15) == elemSize1()) in cv::Mat::at, file C:\Program Files\opencv\build\include\opencv2/core/mat.inl.hpp, line 894"
I can print j from 0~719(since the size of the image is 720*960), but as long as the parameter i want to become 2 from 1, the error occurs.
You mixed up rows and cols:
Try this:
Mat src = imread("path_to_image", IMREAD_GRAYSCALE);
for (int i = 0; i < src.rows; i++)
{
//cout << i << endl;
for (int j = 0; j < src.cols; j++)
{
if (src.at<uchar>(i, j) > 70) {
src.at<uchar>(i, j) = 0;
//cout << j << endl;
}
else
src.at<uchar>(i, j) = 255;
}
}
This is, however very unlikely to perform better than OpenCV implementation. You can gain a little speed working on raw pointers, with a little trick to work on continuous data when possible:
#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
Mat src = imread("D:\\SO\\img\\nice.jpg", IMREAD_GRAYSCALE);
int rows = src.rows;
int cols = src.cols;
if (src.isContinuous())
{
cols = rows * cols;
rows = 1;
}
for (int i = 0; i < rows; i++)
{
uchar* pdata = src.ptr<uchar>(i);
int base = i*cols;
for (int j = 0; j < cols; j++)
{
if (pdata[base + j] > 70)
{
pdata[base + j] = 0;
}
else
{
pdata[base + j] = 255;
}
}
}
return 0;
}
Actually, on my PC my version is a little bit faster than OpenCV one:
Time #HenryChen (ms): 2.83266
Time #Miki (ms): 1.09597
Time #OpenCV (ms): 2.10727
You can test on your PC with the following code, since time depends on many factor, e.g. optimizations enabled in OpenCV:
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat1b src(720,960);
randu(src, 0, 256);
Mat1b src1 = src.clone();
Mat1b src2 = src.clone();
Mat1b src3 = src.clone();
double tic1 = double(getTickCount());
// Method #HenryChen (corrected)
for (int i = 0; i < src1.rows; i++)
{
//cout << i << endl;
for (int j = 0; j < src1.cols; j++)
{
if (src1.at<uchar>(i, j) > 70) {
src1.at<uchar>(i, j) = 0;
//cout << j << endl;
}
else
src1.at<uchar>(i, j) = 255;
}
}
double toc1 = (double(getTickCount()) - tic1) * 1000.0 / getTickFrequency();
cout << "Time #HenryChen (ms): \t" << toc1 << endl;
//-------------------------------------
double tic2 = double(getTickCount());
// Method #Miki
int rows = src2.rows;
int cols = src2.cols;
if (src2.isContinuous())
{
cols = rows * cols;
rows = 1;
}
for (int i = 0; i < rows; i++)
{
uchar* pdata = src2.ptr<uchar>(0);
int base = i*cols;
for (int j = 0; j < cols; j++)
{
pdata[base + j] = (pdata[base + j] > 70) ? uchar(0) : uchar(255);
}
}
double toc2 = (double(getTickCount()) - tic2) * 1000.0 / getTickFrequency();
cout << "Time #Miki (ms): \t" << toc2 << endl;
//-------------------------------------
double tic3 = double(getTickCount());
// Method #OpenCV
threshold(src3, src3, 70, 255, THRESH_BINARY_INV);
double toc3 = (double(getTickCount()) - tic3) * 1000.0 / getTickFrequency();
cout << "Time #OpenCV (ms): \t" << toc3 << endl;
getchar();
return 0;
}
Use test.at<uchar>(cv::Point(i, j)) instead. I always get lost when accessing cv::Mat directly - cv::Point clears it up a little bit.
Anyway, I agree with Miki - it is very unlikely to create a function that performs better that a library one.
I am trying to train my own detector based on HOG features and i trained a detector with CvSVM utility of opencv. Now to use this detector in HOGDescriptor.SetSVM(myDetector), i need to get trained detector in row-vector (primal) form to feed. For this i am using this code. my implementation is like given below:
vector<float>primal;
void LinearSVM::getSupportVector(std::vector<float>& support_vector) {
CvSVM svm;
svm.load("Classifier.xml");
cin.get();
int sv_count = svm.get_support_vector_count();
const CvSVMDecisionFunc* df = decision_func;
const double* alphas = df[0].alpha;
double rho = df[0].rho;
int var_count = svm.get_var_count();
support_vector.resize(var_count, 0);
for (unsigned int r = 0; r < (unsigned)sv_count; r++) {
float myalpha = alphas[r];
const float* v = svm.get_support_vector(r);
for (int j = 0; j < var_count; j++,v++) {
support_vector[j] += (-myalpha) * (*v);
}
}
support_vector.push_back(rho);
}
int main()
{
LinearSVM s;
s.getSupportVector(primal);
return 0;
}
When i use built-in CvSVM, it shows me SV as 3 bec i have only 3 SV in my saved file but since the decision_func is in protected mode, hence i can not access it. That's why i tried to use that wrapper but still of no use. Perhaps you guys can help me out here... Thanks alot!
Answer with a test harness. I put in new answer as it would add allot of clutter to the original answer, possibly making it a bit confusing.
//dummy features
std:: vector<float>
dummyDerReaderForOneDer(const vector<float> &pattern)
{
int i = std::rand() % pattern.size();
int j = std::rand() % pattern.size();
vector<float> patternPulNoise(pattern);
std::random_shuffle(patternPulNoise.begin()+std::min(i,j),patternPulNoise.begin()+std::max(i,j));
return patternPulNoise;
};
//extend CvSVM to get access to weights
class mySVM : public CvSVM
{
public:
vector<float>
getWeightVector(const int descriptorSize);
};
//get the weights
vector<float>
mySVM::getWeightVector(const int descriptorSize)
{
vector<float> svmWeightsVec(descriptorSize+1);
int numSupportVectors = get_support_vector_count();
//this is protected, but can access due to inheritance rules
const CvSVMDecisionFunc *dec = CvSVM::decision_func;
const float *supportVector;
float* svmWeight = &svmWeightsVec[0];
for (int i = 0; i < numSupportVectors; ++i)
{
float alpha = *(dec[0].alpha + i);
supportVector = get_support_vector(i);
for(int j=0;j<descriptorSize;j++)
{
*(svmWeight + j) += alpha * *(supportVector+j);
}
}
*(svmWeight + descriptorSize) = - dec[0].rho;
return svmWeightsVec;
}
// main harness entry point for detector test
int main (int argc, const char * argv[])
{
//dummy variables for example
int posFiles = 10;
int negFiles = 10;
int dims = 1000;
int randomFactor = 4;
//setup some dummy data
vector<float> dummyPosPattern;
dummyPosPattern.assign(int(dims/randomFactor),1.f);
dummyPosPattern.resize(dims );
random_shuffle(dummyPosPattern.begin(),dummyPosPattern.end());
vector<float> dummyNegPattern;
dummyNegPattern.assign(int(dims/randomFactor),1.f);
dummyNegPattern.resize(dims );
random_shuffle(dummyNegPattern.begin(),dummyNegPattern.end());
// the labels and lables mat
float posLabel = 1.f;
float negLabel = 2.f;
cv::Mat cSvmLabels;
//the data mat
cv::Mat cSvmTrainingData;
//dummy linear svm parmas
SVMParams cSvmParams;
cSvmParams.svm_type = cv::SVM::C_SVC;
cSvmParams.C = 0.0100;
cSvmParams.kernel_type = cv::SVM::LINEAR;
cSvmParams.term_crit = cv::TermCriteria(CV_TERMCRIT_ITER+CV_TERMCRIT_EPS, 1000000, FLT_EPSILON);
cout << "creating training data. please wait" << endl;
int i;
for(i=0;i<posFiles;i++)
{
//your feature for one box from file
vector<float> d = dummyDerReaderForOneDer(dummyPosPattern);
//push back a new mat made from the vectors data, with copy data flag on
//this shows the format of the mat for a single example, (1 (row) X dims(col) ), as training mat has each **row** as an example;
//the push_back works like vector add adds each example to the bottom of the matrix
cSvmTrainingData.push_back(cv::Mat(1,dims,CV_32FC1,d.data(),true));
//push back a pos label to the labels mat
cSvmLabels.push_back(posLabel);
}
//do same with neg files;
for(i=0;i<negFiles;i++)
{
float a = rand();
vector<float> d = dummyDerReaderForOneDer(dummyNegPattern);
cSvmTrainingData.push_back(cv::Mat(1,dims,CV_32FC1,d.data(),true));
cSvmLabels.push_back(negLabel);
}
//have a look
cv::Mat viz;
cSvmTrainingData.convertTo(viz,CV_8UC3);
viz = viz*255;
cv::imshow("svmData", viz);
cv::waitKey(10);
cout << "press any key to continue" << endl;
getchar();
viz.release();
//create the svm;
cout << "training, please wait" << endl;
mySVM svm;
svm.train(cSvmTrainingData,cSvmLabels,cv::Mat(),cv::Mat(),cSvmParams);
cout << "get weights" << endl;
vector<float> svmWeights = svm.getWeightVector(dims);
for(i=0; i<dims+1; i++)
{
cout << svmWeights[i] << ", ";
if(i==dims)
{
cout << endl << "bias: " << svmWeights[i] << endl;
}
}
cout << "press any key to continue" << endl;
getchar();
cout << "testing, please wait" << endl;
//test the svm with a large amount of new unseen fake one at a time
int totExamples = 10;
int k;
for(i=0;i<totExamples; i++)
{
cout << endl << endl;
vector<float> dPos = dummyDerReaderForOneDer(dummyPosPattern);
cv::Mat dMatPos(1,dims,CV_32FC1,dPos.data(),true);
float predScoreFromDual = svm.predict(dMatPos,true);
float predScoreBFromPrimal = svmWeights[dims];
for( k = 0; k <= dims - 4; k += 4 )
predScoreBFromPrimal += dPos[k]*svmWeights[k] + dPos[k+1]*svmWeights[k+1] +
dPos[k+2]*svmWeights[k+2] + dPos[k+3]*svmWeights[k+3];
for( ; k < dims; k++ )
predScoreBFromPrimal += dPos[k]*svmWeights[k];
cout << "Dual Score:\t" << predScoreFromDual << "\tPrimal Score:\t" << predScoreBFromPrimal << endl;
}
cout << "press any key to continue" << endl;
getchar();
return(0);
}
Hello again :) please extend the cvsm class rather than encapsulating it, as you need access to protected member.
//header
class mySVM : public CvSVM
{
public:
vector<float>
getWeightVector(const int descriptorSize);
};
//cpp
vector<float>
mySVM::getWeightVector(const int descriptorSize)
{
vector<float> svmWeightsVec(descriptorSize+1);
int numSupportVectors = get_support_vector_count();
//this is protected, but can access due to inheritance rules
const CvSVMDecisionFunc *dec = CvSVM::decision_func;
const float *supportVector;
float* svmWeight = &svmWeightsVec[0];
for (int i = 0; i < numSupportVectors; ++i)
{
float alpha = *(dec[0].alpha + i);
supportVector = get_support_vector(i);
for(int j=0;j<descriptorSize;j++)
{
*(svmWeight + j) += alpha * *(supportVector+j);
}
}
*(svmWeight + descriptorSize) = - dec[0].rho;
return svmWeightsVec;
}
something like that.
credits:
Obtaining weights in CvSVM, the SVM implementation of OpenCV