I am currently looking for a way to implement Local Binary Patterns using OpenCV and C++.
Currently I have found this: https://github.com/bytefish/opencv/tree/master/lbp
However, I need to compare 2 images or LBP Histograms with each other and give some similarity index.
Here is my modified code:
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include "lbp.hpp"
#include "histogram.hpp"
using namespace cv;
int main(int argc, const char *argv[]) {
int deviceId = 0;
if(argc > 1)
deviceId = atoi(argv[1]);
VideoCapture cap(deviceId);
if(!cap.isOpened()) {
cerr << "Capture Device ID " << deviceId << "cannot be opened." << endl;
return -1;
}
// initial values
int radius = 1;
int neighbors = 8;
// windows
namedWindow("original",CV_WINDOW_AUTOSIZE);
namedWindow("lbp",CV_WINDOW_AUTOSIZE);
// matrices used
Mat test;
Mat test1;
Mat frame; // always references the last frame
Mat dst; // image after preprocessing
Mat dst1;
Mat lbp; // lbp image
Mat lbp1;
// just to switch between possible lbp operators
vector<string> lbp_names;
lbp_names.push_back("Extended LBP"); // 0
lbp_names.push_back("Fixed Sampling LBP"); // 1
lbp_names.push_back("Variance-based LBP"); // 2
int lbp_operator=1;
bool running=true;
while(running) {
//cap >> frame;
dst = imread("Coin1.jpg", CV_LOAD_IMAGE_GRAYSCALE); //Known Image
dst1 = imread("Coin2.jpg", CV_LOAD_IMAGE_GRAYSCALE); //Compared to
switch(lbp_operator) {
case 0:
lbp::ELBP(test, lbp, radius, neighbors); // use the extended operator
break;
case 1:
lbp::OLBP(dst, lbp); // use the original operator
lbp::OLBP(dst1, lbp1); // use the original operator
break;
case 2:
lbp::VARLBP(dst, lbp, radius, neighbors);
break;
}
// now to show the patterns a normalization is necessary
// a simple min-max norm will do the job...
normalize(lbp, lbp, 0, 255, NORM_MINMAX, CV_8UC1);
Mat lbp_hist, lbp1_hist;
int histSize[] = {256};
float s_ranges[] = { 0, 256 };
const float* ranges[] = { s_ranges };
// Use the o-th and 1-st channels
int channels[] = { 0 };
calcHist( &lbp, 1, channels, Mat(), lbp_hist, 1, histSize, ranges, true, false );
normalize( lbp1_hist, lbp1_hist, 0, 1, NORM_MINMAX, -1, Mat() );
calcHist( &lbp1, 1, channels, Mat(), lbp1_hist, 1, histSize, ranges, true, false );
normalize( lbp_hist, lbp_hist, 0, 1, NORM_MINMAX, -1, Mat() );
double base_base = compareHist( lbp_hist, lbp1_hist, 0 );
printf("%f\n",base_base); //get a similarity
//imshow("original", lbp);
//imshow("lbp", lbp1);
imshow("1", lbp_hist);
imshow("2", lbp1_hist);
char key = (char) waitKey(0);;
}
return 0; // success
}
However I do not think it is working correctly. I am not getting an accurate histogram. So I can't compare.
Please help.
I remember having a similar problem when starting with OpenCV LBPH
Try this function for histogram
void lbp::histogram(const Mat& src, Mat& hist, int numPatterns) {
switch(src.type()) {
case CV_8SC1: histogram_<char>(src, hist, numPatterns); break;
case CV_8UC1: histogram_<unsigned char>(src, hist, numPatterns); break;
case CV_16SC1: histogram_<short int>(src, hist, numPatterns); break;
case CV_16UC1: histogram_<unsigned short>(src, hist, numPatterns); break;
case CV_32SC1: histogram_<int>(src, hist, numPatterns); break;
}
}
template <typename _Tp>
void lbp::histogram_(const Mat& src, Mat& hist, int numPatterns) {
hist = Mat::zeros(1, numPatterns, CV_32SC1);
for(int i = 0; i < src.rows; i++) {
for(int j = 0; j < src.cols; j++) {
int bin = src.at<_Tp>(i,j);
hist.at<int>(0,bin) += 1;
}
}
}
//Manual normalization
cv::Mat hist_norm=cv::Mat::zeros(1,hist.cols,CV_32F);
int sum=0;
for(int j=0;j<hist.cols;j++){sum+=hist.at<int>(0,j);}
for(int j=0;j<hist.cols;j++){hist_norm.at<float>(0,j)+= (float)hist.at<int>(0,j)/(float)sum;}
This works on my computer for basic LBPH. I used an implementation of LBP from another librairy, maybe it's the same as you.
Tell me if it's fine for you.
Related
This program shows sequential frames with images.
However, as you see, the worm image has a white background.
But I already cut the worm image's background, So the current worm images background is transparent.
I wants to process the worm image's background transparently and show the worm image not gray but colorful.
I tried to edit into cvtColor(image, srcBGR, CV_BGR2BGRA), however, occured error.
Here is the code.
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
#include<vector>
using namespace std;
using namespace cv;
int main(){
VideoCapture cap;
cap.open(0);
if(!cap.isOpened()){
cerr << "Error opening the webcam!" << endl;
return -1;
}
Mat image = imread("images/worm.png", 0);
cv::resize(image,image,Size(70, 120));
Mat frame;
while(1){
cap >> frame;
Mat newFrame = frame.clone();
int cx = (newFrame.cols - 70) / 2;
if (!image.empty()) {
// Get a BGR version of the face, since the output is BGR color
Mat srcBGR = Mat(image.size(), CV_8UC3);
cvtColor(image, srcBGR, CV_GRAY2BGR);
// Get the destination ROI (and make sure it is within the image)
Rect dstRC = Rect(cx, newFrame.rows/2, 70, 120);
Mat dstROI = newFrame(dstRC);
// Copy the pixels from src to dst.
srcBGR.copyTo(dstROI);
}
imshow("frame", newFrame);
char key = (char) waitKey(30);
// Exit this loop on escape:
if(key == 27)
break;
}
return 0;
}
Reading the image using
Mat image = imread("images/worm.png", 0);
will discard the transparency information and load it as an RGB image. Instead, you can use
Mat image = imread("images/worm.png", cv2.IMREAD_UNCHANGED);
The rest of the code should work now since you convert the captured image to a BGRA image before copying.
I try to demonstrate it in Python.
As the preview answer said, the cv2.imread(fname, 0) will discard the transparency information, that's the alpha channel.
To preserve the alpha channel, use cv2.imread(fname, -1) or equals to cv2.imread(fname, cv2.IMREAD_UNCHANGED) to read, then split the channels.
We can clearly find the alpha channel.
Then do mask-operation to blend, we will get this:
## read the images
## 读图(0:BGR, -1:保持不变)
wali = cv2.imread("wali.png")
worm = cv2.imread("worm.png", -1)
## split and merge channels
## 通道分离与合并
w,h = worm.shape[:2]
b,g,r,a = cv2.split(worm)
mask = np.dstack((a,a,a))
worm = np.dstack((b,g,r))
## mask operation
## 掩模操作
canvas = wali[100:100+h, 200:200+w]
imask = mask>0
canvas[imask] = worm[imask]
## display
## 显示
cv2.imshow("wali", wali)
cv2.waitKey()
Try this:
#include <windows.h>
#include <iostream>
#include <vector>
#include <stdio.h>
#include "fstream"
#include "iostream"
#include <algorithm>
#include <iterator>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
//-----------------------------------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------------------------------
int main(int argc, unsigned int** argv)
{
Mat img = imread("background.jpg", 1);
if (img.empty())
{
cout << "Can't read image." << endl;
return 0;
}
Mat overlay = imread("overlay.png", -1);
if (overlay.empty())
{
cout << "Can't read overlay image." << endl;
return 0;
}
Rect target_roi(0,0,img.cols,img.rows); // Set here, where to place overlay.
cv::resize(overlay, overlay, Size(target_roi.width, target_roi.height));
Mat mask;
if (overlay.channels() == 4)
{
vector<Mat> ch;
split(overlay, ch);
mask = 255-ch[3].clone();
mask.convertTo(mask, CV_32FC1, 1.0 / 255.0);
ch.erase(ch.begin()+3);
merge(ch, overlay);
}
else
{
if (overlay.channels() == 3)
{
cvtColor(overlay, overlay, COLOR_BGR2GRAY);
}
overlay.convertTo(mask, CV_32FC1, 1.0 / 255.0);
}
for (int i = 0; i < overlay.rows; ++i)
{
for (int j = 0; j < overlay.cols; ++j)
{
float blending_coeff = mask.at<float>(i, j);
Vec3b v1 = img.at<Vec3b>(i + target_roi.y, j + target_roi.x);
Vec3b v2;
if (overlay.channels() == 1)
{
int v = overlay.at<uchar>(i, j);
v2 = (v, v, v);
}
else
{
v2 = overlay.at<Vec3b>(i, j);
}
Vec3f v1f(v1[0], v1[1], v1[2]);
Vec3f v2f(v2[0], v2[1], v2[2]);
Vec3f r = v1f*blending_coeff + (1.0 - blending_coeff)*v2f;
img.at<Vec3b>(i + target_roi.y, j + target_roi.x) = r;
}
}
imshow("mask", img);
imwrite("result.png", img);
waitKey();
}
Basically, I'm trying to use less pixels to represent an image itself.
The steps are below:
Say I will input an image with size [1000*600], then I got 600_000 pixels(rgb), which could be [600_000, 3] vectors. K-Means is used to get its cluster centers.
The each pixel in the image will be placed with its nearest neighbor among the clusters found via K-Means.
The source is:
template <typename T>
void NN(Point3_<T>& pixel, const Mat& points)
{
vector<T> vt {pixel.x, pixel.x, pixel.z};
double min_dist = LDBL_MAX;
int min_index = -1;
for (int i = 0; i < points.rows; ++ i)
{
double dist = norm(vt, points.row(i), NORM_L2);
if (dist < min_dist)
{
min_dist = dist;
min_index = i;
}
}
// assert(min_index != -1);
pixel.x = points.at<T>(min_index, 0);
pixel.y = points.at<T>(min_index, 1);
pixel.z = points.at<T>(min_index, 2);
}
template <typename T>
void NN(Mat& img, const Mat& points)
{
timer::start("assign");
img.forEach<Point3_<T>>([&points](Point3_<T> &pixel, const int position[])
{
NN(pixel, points);
});
timer::stop<ms>();
}
Mat kmeans(const Mat& original_img, const int K)
{
Mat img;
original_img.reshape(3, original_img.rows * original_img.cols)
.convertTo(img, CV_32FC3);
timer::start("K-means cluster");
// Require img.type() == CV_32F
Mat clusters = BOWKMeansTrainer(K).cluster(img);
timer::stop<ms>();
// Type 5 -> Type 0: 32FC1 -> 8UC1
// K rows, 3 cols, 8UC1
clusters.convertTo(clusters, CV_8UC1);
Mat output_img = original_img;
NN<uchar>(output_img, clusters);
// assert won't fire, why?
assert(equal(original_img.begin<uchar>(), original_img.end<uchar>(),
output_img.begin<uchar>()));
return output_img;
}
int main(int argc, char* argv[])
{
vector<int> ks {2, 16};
string filename = "1";
string pathname = string("./img/") + filename + ".jpg";
Mat img = imread(pathname);
for (const int& K: ks)
{
imshow(int_to_string(K), kmeans(img, K));
// write_img(filename, "kmeans", K, kmeans(img, K));
}
std::cout << "Press enter to continue...";
cin.get();
}
The questions are:
The assert() in kmeans() won't fire. That is, the mat object original_img is identical to output_img. How could this happen?
The two imwrite() in main() will show two identical 2-value images. That is, the K-Means with K=2 works, while the following with K=16 does not. Note that if we output one image per execution, everything is fine.
The buggy output is below:
The original image and K-Means with K=16 could be seen below:
Thank god! I've found the cause.
In kmeans(), the below code will call Mat's copy constructor, which costs O(1) to assign original_img's header to output_img's.
Mat output_img = original_img;
This is the reason why the assert won't fire.
I'm trying to plot the centroid of a specific blob detected using contour techniques. I don't wish to loop through all the blob detected in an image - I only want to plot the centroid of one (i.e. contour[2]). Ideally I'd like to accomplish this using the most efficient / fastest method.
Here's my code:
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/opencv.hpp"
#include <iostream>
#define _USE_MATH_DEFINES
#include <math.h>
using namespace cv;
using namespace std;
int main(int argc, const char** argv)
{
cv::Mat src = cv::imread("frame-1.jpg");
if (src.empty())
return -1;
cv::Mat gray;
cv::cvtColor(~src, gray, CV_BGR2GRAY);
cv::threshold(gray, gray, 160, 255, cv::THRESH_BINARY);
// Find all contours
std::vector<std::vector<cv::Point> > contours;
cv::findContours(gray.clone(), contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
// Fill holes in each contour
cv::drawContours(gray, contours, -1, CV_RGB(255, 255, 255), -1);
cout << contours.size();
double avg_x(0), avg_y(0); // average of contour points
for (int j = 0; j < contours[2].size(); ++j)
{
avg_x += contours[2][j].x;
avg_y += contours[2][j].y;
}
avg_x /= contours[2].size();
avg_y /= contours[2].size();
cout << avg_x << " " << avg_y << endl;
cv::circle(gray, {avg_x, avg_y}, 5, CV_RGB(5, 100, 100), 5);
namedWindow("MyWindow", CV_WINDOW_AUTOSIZE);
imshow("MyWindow", gray);
waitKey(0);
destroyWindow("MyWindow");
return 0;
}
However, plotting the circle using the coordinates (avg_x, avg_y) results in a 'no instance of constructor "cv::Point_<Tp>::Point[with_Tp=int]" matches the argument list - argument types are: (double, double)' error.
Use min enclosing circle
float radius ;
Point2f center ;
minEnclosingCircle ( contours[i] , center , radius ) ;
cv::circle(gray, center, 5, CV_RGB(5, 100, 100), 5);
i'm learning c++ api of opencv, and for a simple approach i've started with try to downsample image (ok i know that there is pyrDown with gaussian resampling but it's for learning how to access element in Mat class)
this is my code:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#define original_window "original"
#define manual_window "manual"
using namespace cv;
using namespace std;
Mat img, manual;
void downsample(Mat src, Mat &dst, const Size& s) {
float factor = src.rows/(float)s.width;
Mat_<Vec3f> _dst = Mat(s, src.type());
Mat_<Vec3f> _src = src;
for(int i=0; i<src.cols; i+=factor) {
int _i = i/factor;
for(int j=0; j<src.rows; j+=factor) {
int _j = j/factor;
_dst (_j, _i) = _src(j,i);
}
}
cout << "downsample image size: " << _dst.rows << " " << _dst.cols << endl;
dst = Mat(_dst);
}
int main(int /*argc*/, char** /*argv*/) {
img = imread("lena.jpg");
cout << "original image size: " << img.rows << " " << img.cols << endl;
downsample(img, manual, Size(img.cols/2, img.rows/2));
namedWindow(original_window, CV_WINDOW_AUTOSIZE);
namedWindow(manual_window, CV_WINDOW_AUTOSIZE);
while( true )
{
char c = (char)waitKey(10);
if( c == 27 )
{ break; }
imshow( original_window, img );
imshow( manual_window, manual );
}
return 0;
}
now, i'm doing a downsampling in a fool way: i'm just deleting elements. and i'm try to use c++ api with Mat_.
in manual window i get a white window, and i don't understand why. event if i try to cout manual i'seeing different values.. what's wrong with this piece of code?
EDIT 1
i've found a solution:
dst.convertTo(dst, src.type()); // in this particular case: src.type() == CV_8UC3
at the end of downsample()
now my question is: why that? i declare Mat(s, src.type()); why it is modified?
EDIT 2
if i use #go4sri answer with this line
_dst (_j, _i) = src.at<Vec3f>(j, i);
i get this output:
i really does not understand why..
The way to access an element in OpenCV's Mat is as follows:
for a single channel matrix(tmp)
Matrix_Name.at<dataType>(row, col)
For a three channel matrix( as is the case for a color image), you will need to use the Vec3b/Vec3f type depending upon if yours is a unsigned char/float matrix.
As yours is a unsigned char 3Dimensional matrix:
you will have to access it as src.at<Vec3b>(i, j)
Your downsample should have been:
void downsample(const Mat& src, Mat &dst, const Size& s) {
float factor = src.rows/(float)s.height;
Mat _dst = Mat(s, src.type());
for(int i=0; i < src.cols; i += factor) {
int _i = i/factor;
for(int j=0; j<src.rows; j+=factor) {
int _j = j/factor;
_dst.at<Vec3b> (_j, _i) = src.at<Vec3b>(j, i);
}
}
cout << "downsample image size: " << _dst.rows << " " << _dst.cols << endl;
dst = Mat(_dst);
}
I'm using the program squares.c available in the samples of OpenCV libraries. It works well with every image, but I really can't figure it out why it doesn't recognize the square drawn in that image
http://desmond.imageshack.us/Himg12/scaled.php?server=12&filename=26725680.jpg&res=medium
After CANNY:
After DILATE:
The RESULT image (in red)
http://img267.imageshack.us/img267/8016/resultuq.jpg
As you can see, the square is NOT detected.
After the detection I need to extract the area contained in the square...How is it possible without a ROI?
The source code below presents a small variation of the Square Detector program. It's not perfect, but it illustrates one way to approach your problem.
You can diff this code to the original and check all the changes that were made, but the main ones are:
Decrease the number of threshold levels to 2.
In the beginning of findSquares(), dilate the image to detect the thin white square, and then blur the entire image so the algorithm doesn't detect the sea and the sky as individual squares.
Once compiled, run the application with the following syntax: ./app <image>
// The "Square Detector" program.
// It loads several images sequentially and tries to find squares in
// each image
#include "highgui.h"
#include "cv.h"
#include <iostream>
#include <math.h>
#include <string.h>
using namespace cv;
using namespace std;
void help()
{
cout <<
"\nA program using pyramid scaling, Canny, contours, contour simpification and\n"
"memory storage (it's got it all folks) to find\n"
"squares in a list of images pic1-6.png\n"
"Returns sequence of squares detected on the image.\n"
"the sequence is stored in the specified memory storage\n"
"Call:\n"
"./squares\n"
"Using OpenCV version %s\n" << CV_VERSION << "\n" << endl;
}
int thresh = 50, N = 2; // karlphillip: decreased N to 2, was 11.
const char* wndname = "Square Detection Demo";
// helper function:
// finds a cosine of angle between vectors
// from pt0->pt1 and from pt0->pt2
double angle( Point pt1, Point pt2, Point pt0 )
{
double dx1 = pt1.x - pt0.x;
double dy1 = pt1.y - pt0.y;
double dx2 = pt2.x - pt0.x;
double dy2 = pt2.y - pt0.y;
return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}
// returns sequence of squares detected on the image.
// the sequence is stored in the specified memory storage
void findSquares( const Mat& image, vector<vector<Point> >& squares )
{
squares.clear();
Mat pyr, timg, gray0(image.size(), CV_8U), gray;
// karlphillip: dilate the image so this technique can detect the white square,
Mat out(image);
dilate(out, out, Mat(), Point(-1,-1));
// then blur it so that the ocean/sea become one big segment to avoid detecting them as 2 big squares.
medianBlur(out, out, 7);
// down-scale and upscale the image to filter out the noise
pyrDown(out, pyr, Size(out.cols/2, out.rows/2));
pyrUp(pyr, timg, out.size());
vector<vector<Point> > contours;
// find squares in every color plane of the image
for( int c = 0; c < 3; c++ )
{
int ch[] = {c, 0};
mixChannels(&timg, 1, &gray0, 1, ch, 1);
// try several threshold levels
for( int l = 0; l < N; l++ )
{
// hack: use Canny instead of zero threshold level.
// Canny helps to catch squares with gradient shading
if( l == 0 )
{
// apply Canny. Take the upper threshold from slider
// and set the lower to 0 (which forces edges merging)
Canny(gray0, gray, 0, thresh, 5);
// dilate canny output to remove potential
// holes between edge segments
dilate(gray, gray, Mat(), Point(-1,-1));
}
else
{
// apply threshold if l!=0:
// tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
gray = gray0 >= (l+1)*255/N;
}
// find contours and store them all as a list
findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
vector<Point> approx;
// test each contour
for( size_t i = 0; i < contours.size(); i++ )
{
// approximate contour with accuracy proportional
// to the contour perimeter
approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);
// square contours should have 4 vertices after approximation
// relatively large area (to filter out noisy contours)
// and be convex.
// Note: absolute value of an area is used because
// area may be positive or negative - in accordance with the
// contour orientation
if( approx.size() == 4 &&
fabs(contourArea(Mat(approx))) > 1000 &&
isContourConvex(Mat(approx)) )
{
double maxCosine = 0;
for( int j = 2; j < 5; j++ )
{
// find the maximum cosine of the angle between joint edges
double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
maxCosine = MAX(maxCosine, cosine);
}
// if cosines of all angles are small
// (all angles are ~90 degree) then write quandrange
// vertices to resultant sequence
if( maxCosine < 0.3 )
squares.push_back(approx);
}
}
}
}
}
// the function draws all the squares in the image
void drawSquares( Mat& image, const vector<vector<Point> >& squares )
{
for( size_t i = 0; i < squares.size(); i++ )
{
const Point* p = &squares[i][0];
int n = (int)squares[i].size();
polylines(image, &p, &n, 1, true, Scalar(0,255,0), 3, CV_AA);
}
imshow(wndname, image);
}
int main(int argc, char** argv)
{
if (argc < 2)
{
cout << "Usage: ./program <file>" << endl;
return -1;
}
// static const char* names[] = { "pic1.png", "pic2.png", "pic3.png",
// "pic4.png", "pic5.png", "pic6.png", 0 };
static const char* names[] = { argv[1], 0 };
help();
namedWindow( wndname, 1 );
vector<vector<Point> > squares;
for( int i = 0; names[i] != 0; i++ )
{
Mat image = imread(names[i], 1);
if( image.empty() )
{
cout << "Couldn't load " << names[i] << endl;
continue;
}
findSquares(image, squares);
drawSquares(image, squares);
imwrite("out.jpg", image);
int c = waitKey();
if( (char)c == 27 )
break;
}
return 0;
}
Outputs:
I would suggest that your square in this image is too thin. The first step in squares.c is to scale the image down and back up to reduce noise before passing to the Canny edge detector.
The scaling convolves with a 5x5 kernel, so in your case this could result in losing any gradient in such a thin edge.
Try making your square's edges at least 5 pixels if you are going to overlay them on a continuous background.