i'm try to get covariance matrix from an image stored in cv::Mat. i need it for calculate mahalanobis distance and attempt to some color segmentation.
this is my code:
Mat covar, selection, meanBGR;
selection = src(roi);
calcCovarMatrix(selection, covar, meanBGR, CV_COVAR_NORMAL|CV_COVAR_ROWS);
the Mat src is from webcam and standard BGR opencv format, so CV_32FC3. pixels are stored (i think) in row vector order (blue, green, red).. so i think my code is correct. but i recive this runtime error:
Assertion failed (src.channels() == 1) in mulTransposed
i tried to make a vector too in this way:
vector<Scalar> samples;
for(int i=0; i<selection.rows; i++) {
for(int j=0; j<selection.cols; j++) {
Scalar pixel = selection.at<Scalar>(i,j);
Scalar sample(pixel[0], pixel[1], pixel[2]);
samples.push_back(sample);
}
}
calcCovarMatrix(samples, covar, meanBGR, CV_COVAR_NORMAL|CV_COVAR_ROWS);
but i always get the same error. reading the manual doesn't make any kind of idea.
I don't think you can pass it a 3-channel matrix. Does this small sample work:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int /*argc*/, char** /*argv*/)
{
Mat_<float> samples = (Mat_<float>(3, 3) << 1.0, 2.0, 3.0,
4.0, 5.0, 6.0,
7.0, 8.0, 9.0);
Mat cov, mu;
cv::calcCovarMatrix(samples, cov, mu, CV_COVAR_NORMAL | CV_COVAR_ROWS);
cout << "cov: " << endl;
cout << cov << endl;
cout << "mu: " << endl;
cout << mu << endl;
return 0;
}
It should output:
cov:
[18, 18, 18;
18, 18, 18;
18, 18, 18]
mu:
[4, 5, 6]
Hope that helps!
I had the same problem. Unfortunately I think the only way to get the color co-variance is to do it manually. If you split the image and then rehape the resulting matrices into single rows, concat them back together (into a 3 row 1 channel mat) you can then just multiply by the transpose and divide by size - 1.
Related
I'm trying to use the mediapipe palm detection model in opencv c++.
I downloaded the model with pb format from this github repos, and i can successfully load and get he ouput values of the model. However, I am unable to use these outputs to draw the detection rectangles.
code :
cvtColor(frame,frame,COLOR_BGR2RGB);
Mat blob = dnn::blobFromImage(frame,1.0/255, cv::Size(256, 256), cv::Scalar(0, 0, 0));
net.setInput(blob);
cv::Mat outputs, classificators_outs;
net.forward(outputs,"regressors");
net.forward(classificators_outs,"classificators");
Mat reg = outputs.reshape(1, outputs.size[1]); //2d Mat, 2944 rows, 18 cols
Mat prob = classificators_outs.reshape(1, classificators_outs.size[1]); //2d Mat, 2944 rows, 1 col
float dw = float(frame.cols) / 256;
float dh = float(frame.rows) / 256;
vector<Rect> boxes;
std::vector<float> confidences;
for (int i=0; i<reg.rows; i++) {
if (prob.at<float>(i,0) < 0.7)
continue;
cout << prob.at<float>(i,0) << endl;
Mat_<float> row = reg.row(i);
Mat_<float> pro = prob.row(i);
if(i==1)
{
cout << row << endl;
}
// scale to orig. image coords:
Rect b(row(0,0) * dw, row(0,1)*dh, row(0,2)*dw, row(0,3)*dh);
boxes.push_back(b);
cout << b << endl;
}
result of cout << row << endl; :
[3.6905072, 5.4042335, 32.863857, 32.863861, 6.3343191, 5.0829744, 7.8449326, 4.7407198, -0.25462124, 1.8523651, -6.654418, 0.3679803, -11.397835, 0.078130387, 13.685674, 8.8667402, 15.410878, 11.636487]
results of cout << b << endl; :
[13129 x 9847 from (-552, -668)]
I also noticed that if I let the code run further more, the rectangles get bigger and bigger.
Can anyone help ?
thanks in advance.
I have some difficulties converting a Mat OpenCV matrix (2D) into a 1D array in OpenCV. I am implementing my code in C++ with Visual Studio and my environment is Windows 10.
This is my code
include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include "opencv2/opencv.hpp"
#include <iostream>
#include <vector>
using namespace std;
using namespace cv;
int countt = 1;
int main()
{
std::cout << "The program starts!\n";
// Create a VideoCapture object and open the input file
// If the input is the web camera, pass 0 instead of the video file name
VideoCapture cap("Control1.avi");
// Check if camera opened successfully
if (!cap.isOpened()) {
cout << "Error opening video stream or file" << endl;
return -1;
}
// Size of the video
Size capSize = Size((int)cap.get(CAP_PROP_FRAME_WIDTH),
(int)cap.get(CAP_PROP_FRAME_HEIGHT));
cout << "Frame Size: " << capSize << endl;
cout << "Number of Frames: " << cap.get(CAP_PROP_FRAME_COUNT) << endl;
while (1) {
Mat frame;
// Capture frame-by-frame
cap >> frame;
// If the frame is empty, break immediately
if (frame.empty())
break;
//converting Mat frame into 1D array
int* testData1D = (int*)frame.data;
cout << "1Darray: " << testData1D[22] << endl;
// Display the resulting frame
imshow("Frame", frame);
cout << "Count = " << countt << endl;
countt = countt + 1;
// Press ESC on keyboard to exit
char c = (char)waitKey(25);
if (c == 27)
break;
}
// When everything done, release the video capture object
cap.release();
// Closes all the frames
destroyAllWindows();
return 0;
}
This code was unsuccessful. I am not able to print a reasonable data from the array. This is a kind of thing I get when I run the code: 1Darray: 134742016. This suppose to be the intensity of a pixel (something between 0 and 255).
I also replaced int* testData1D = (int*)frame.data; with the below code in order to convert the matrix content one by one into the array:
int numCols = frame.cols;
int numRows = frame.rows;
const int frameSize = frame.cols * frame.rows;
double frameArray[201*204];
for (int x = 0; x < numCols; x++) { // x-axis, cols
for (int y = 0; y < numRows; y++) { // y-axis rows
double intensity = frame.at<uchar>(Point(x, y));
frameArray[x * frame.cols + y] = intensity;
}
}
But I end up with an infinite for loop that never ends. (The program runs for ever)
I checked bunch of other codes on Stackoverflow such as c++ OpenCV Turn a Mat into a 1 Dimensional Array and Convert Mat to Array/Vector in OpenCV
but they are not helpful. For the latter, the array size is not correct. I don't know if it really build the right array. I get array lenght: 124236, but it should be 204*203 = 41412
I would appreciate it if you show me how I can simply convert a Mat openCV matrix into a normal 1D array in C++.
Thanks.
Try looping through the matrix using pointer arithmetic. First, we create a random BGR matrix of size 9, to test the procedure. The data type stored in the mat are BGR pixels represented as cv::Scalars:
//Create random test mat:
cv::Mat3b randomMat(3,3);
cv::randu( randomMat, cv::Scalar(0,0,0), cv::Scalar(256,256,256) );
cv::Mat testMat = randomMat;
//Get mat total elements:
int matElements = testMat.cols * testMat.rows;
//Prepare output array:
cv::Scalar matArray[ matElements ];
This is the test matrix:
The raw values will be stored in the matArray container. Loop through the matrix using pointer arithmetic and store each pixel in the array:
//Loop trhough the mat, insert into array:
cv::MatIterator_<cv::Vec3b> it, end;
int i = 0;
for ( it = testMat.begin<cv::Vec3b>(), end = testMat.end<cv::Vec3b>(); it != end; ++it ) {
//get current bgr pixels:
uchar &r = (*it)[2];
uchar &g = (*it)[1];
uchar &b = (*it)[0];
//Store them into array, as a cv::Scalar:
matArray[i] = cv::Scalar(b,g,r);
i++;
}
We can check the data stored in the array like this:
for ( int i = 0; i < matElements; i++ ){
std::cout<<"i: "<<i<<" - "<<matArray[i]<<std::endl;
}
This yields:
i: 0 - [246, 156, 192, 0]
i: 1 - [7, 165, 166, 0]
i: 2 - [2, 179, 231, 0]
i: 3 - [212, 171, 230, 0]
i: 4 - [93, 138, 123, 0]
i: 5 - [80, 105, 242, 0]
i: 6 - [231, 239, 174, 0]
i: 7 - [174, 176, 191, 0]
i: 8 - [134, 25, 124, 0]
I've written C++ code to essentially take video feed and divide it up into frames, run HSV segmentation, find its contours, and then perform a PCA analysis on it which yields two eigen vectors and their corresponding eigen values.
I then found the largest of the two eigen values and took the eigen vector that corresponds with it and placed it in a vector of type vector<Point2d>. It all runs great, but I can't seem to publish the vector to a ROS topic.
My question is how do I publish this vector of type vector<Point2d> to a ROS topic? Since I do all the calculations in my cpp file I don't think I can make a msg file for the vector.
The ROS code will be in the main().
#include<opencv2/highgui/highgui.hpp>
#include "opencv2/core/core_c.h"
#include "opencv2/core/core.hpp"
#include "opencv2/flann/miniflann.hpp"
#include "opencv2/imgproc/imgproc_c.h"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/video/video.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/ml/ml.hpp"
#include "opencv2/highgui/highgui_c.h"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/contrib/contrib.hpp"
#include "opencv2/opencv.hpp"
#include <unistd.h>
#include <iostream>
#include <algorithm>
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <vector>
using namespace cv;
using namespace std;
vector<Point2d> getOrientation(vector<Point> &pts, Mat &img) //Taking address of pointers from main() as arugments and storing them
{
//if (pts.size() == 0) return false;
//First the data need to be arranged in a matrix with size n x 2, where n is the number of data points we have. Then we can perform that PCA analysis. The calculated mean (i.e. center of mass) is stored in the "pos" variable and the eigenvectors and eigenvalues are stored in the corresponding std::vector’s.
//Construct a buffer called data_pts used by the pca analysis
Mat data_pts = Mat(pts.size(), 2, CV_64FC1); //pts.size() rows, 2 columns, matrix type will be CV_64FC1(Type to hold inputs of "double")
for (int i = 0; i < data_pts.rows; ++i)
{
data_pts.at<double>(i, 0) = pts[i].x;
data_pts.at<double>(i, 1) = pts[i].y;
}
//Perform PCA analysis. Principal Component Analysis allows us to find the direction along which our data varies the most. In fact, the result of running PCA on the set of points in the diagram consist of 2 vectors called eigenvectors which are the principal components of the data set.
PCA pca_analysis(data_pts, Mat(), CV_PCA_DATA_AS_ROW);
//Store the position of the object
Point pos = Point(pca_analysis.mean.at<double>(0, 0),
pca_analysis.mean.at<double>(0, 1));
//Store the eigenvalues and eigenvectors
vector<Point2d> eigen_vecs(2);
vector<double> eigen_val(2);
for (int i = 0; i < 2; ++i)
{
eigen_vecs[i] = Point2d(pca_analysis.eigenvectors.at<double>(i, 0), pca_analysis.eigenvectors.at<double>(i, 1));
eigen_val[i] = pca_analysis.eigenvalues.at<double>(0, i);
cout << "Eigen Vector: "<< eigen_vecs[i] << endl;
cout << "Eigen Value: " << eigen_val[i] << endl;
}
// Find a way to acquire highest Eigen Value and the Vector Associated with it and find a way to pass it on to the motor controller(The Pic 24)
double valueMAX = *max_element(eigen_val.begin(), eigen_val.end());
double index = find(eigen_val.begin(), eigen_val.end(), valueMAX) - eigen_val.begin();
cout << "\nMax value is: " << valueMAX << endl;
cout << "\nThe index of the largest value is: " << index << endl;
vector<Point2d> correctVector(2);
for( int i = 0; i < 2; i++)
{
if(i == index)
{
correctVector[0] = eigen_vecs[i];
}
}
cout <<" \nThe vector that corresponds with the value is: " << correctVector[0] << endl;
float degrees = ((atan2(eigen_vecs[0].y, eigen_vecs[0].x) * 180) / 3.14159265);
cout <<" \nThe degrees using ArcTangent of the vector(x,y) is: " << degrees << endl;
//ros::Publisher vector_pub = node.advertise<std_msgs::String>("vector", 1000);
// Draw the principal components, each eigenvector is multiplied by its eigenvalue and translated to the mean position.
circle(img, pos, 3, CV_RGB(255, 0, 255), 2);
line(img, pos, pos + 0.02 * Point(eigen_vecs[0].x * eigen_val[0], eigen_vecs[0].y * eigen_val[0]) , CV_RGB(255, 255, 0));
line(img, pos, pos + 0.02 * Point(eigen_vecs[1].x * eigen_val[1], eigen_vecs[1].y * eigen_val[1]) , CV_RGB(0, 255, 255));
//return degrees;
return correctVector;
}
int main(int argc, char **argv)
{
VideoCapture cap(0); //capture the video from web cam/USB cam. (0) for webcam, (1) for USB.
if ( !cap.isOpened() ) // if not success, exit program
{
cout << "Cannot open the web cam" << endl;
return -1;
}
//Creating values for Hue, Saturation, and Value. Found that the color Red will be near 110-180 Hue, 60-255 Saturation, and 0-255 Value. This combination seems to pick up any red object i give it, as well as a few pink ones too.
int count = 0;
int iLowH = 113;
int iHighH = 179;
int iLowS = 60;
int iHighS = 255;
int iLowV = 0;
int iHighV = 255;
/**
* The ros::init() function needs to see argc and argv so that it can perform
* any ROS arguments and name remapping that were provided at the command line.
* For programmatic remappings you can use a different version of init() which takes
* remappings directly, but for most command-line programs, passing argc and argv is
* the easiest way to do it. The third argument to init() is the name of the node.
*
* You must call one of the versions of ros::init() before using any other
* part of the ROS system.
*/
// Initiate a new ROS node named "talker"
ros::init(argc, argv,"OpenCV");
/**
* NodeHandle is the main access point to communications with the ROS system.
* The first NodeHandle constructed will fully initialize this node, and the last
* NodeHandle destructed will close down the node.
*/
// creating a node handle: It is the reference assigned to a new node. EVERY NODE MUST HAVE A REFERENCE!
ros::NodeHandle node;
while (true)
{
Mat frame;
bool bSuccess = cap.read(frame); // read a new frame from video
if (!bSuccess) //if not success, break loop
{
cout << "Cannot read a frame from video stream" << endl;
break;
}
Mat imgHSV;
cvtColor(frame, imgHSV, COLOR_BGR2HSV); //Convert the captured frame from RBG to HSV
Mat imgThresholded;
inRange(imgHSV, Scalar(iLowH, iLowS, iLowV), Scalar(iHighH, iHighS, iHighV), imgThresholded); //Threshold the image
//morphological opening (remove small objects from the foreground)
erode(imgThresholded, imgThresholded, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)) );
dilate( imgThresholded, imgThresholded, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)) );
//morphological closing (fill small holes in the foreground)
dilate( imgThresholded, imgThresholded, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)) );
erode(imgThresholded, imgThresholded, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)) );
// Find all objects of interest, find all contours in the threshold image
vector<vector<Point> > contours; //vector of vector points
vector<Vec4i> hierarchy; // Vector of 4 interges
vector<Point2d> result;
//findContours essentially is tracing all continuous points caputured by the thresholding, I feel this gives accuracy to the upcoming Eigen analysis and also helps in detecting what is actually an object versus what is not. The arguments for findingContours() is the following( binary image, contour retrieval mode, contour approximation method)
findContours(imgThresholded, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
ros::Publisher vector_pub = node.advertise<std_msgs::UInt8MultiArray("vector", 1000); // THIS IS THE ERROR IN MY CODE, HOW DO I PUBLISH THE VECTOR OF TYPE vector<Point2d>!?!?
// For each object
for (size_t i = 0; i < contours.size(); ++i)
{
// Calculate its area of each countour
double area = contourArea(contours[i]);
// Ignore if too small or too large
if (area < 1e2 || 1e5 < area) continue;
// Draw the contour for visualisation purposes
drawContours(frame, contours, i, CV_RGB(255, 0, 0), 2, 8, hierarchy, 0);
count++;
cout << "This is frame: " << count <<endl;
//usleep(500000);
result = getOrientation(contours[i], frame);
cout<< "THE VECTOR BEING PASSED TO THE TOPIC IS: " << result << endl;
}
imshow("Thresholded Image", imgThresholded); //show the thresholded image with Contours.
imshow("Original", frame); //show the original image
if (waitKey(30) == 27) //wait for 'esc' key press for 30ms. If 'esc' key is pressed, break loop
{
cout << "esc key is pressed by user" << endl;
break;
}
}
return 0;
}
I am reading OpenCV 2 Computer Vision Application Programming Cookbook and implementing examples in it.
In chapter 4, color histogram example doesn't work unfortunately.
The code is below. But this code does not give me histogram or any error. Also, it says that color images histogram is three dimensional. I don't understand why it is thee and it is not two.
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(){
Mat image = imread("waves.jpg");
int histSize[3];
float hranges[2];
const float* ranges[3];
int channels[3];
// Prepare arguments for a color histogram
histSize[0] = histSize[1] = histSize[2] = 256;
hranges[0] = 0.0; // BRG range
hranges[1] = 255.0;
ranges[0] = hranges; // all channels have the same range
ranges[1] = hranges;
ranges[2] = hranges;
channels[0] = 0; // the three channels
channels[1] = 1;
channels[2] = 2;
Mat hist;
// Compute histogram
calcHist(&image,
1, // histogram of 1 image only
channels, // the channel used
cv::Mat(), // no mask is used
hist, // the resulting histogram
3, // it is a 3D histogram
histSize, // number of bins
ranges // pixel value range
);
cout << hist.at<int>(100, 100, 0) << endl;
cout << hist.at<int>(100, 100, 1) << endl;
cout << hist.at<int>(100, 100, 2) << endl;
return 0;
}
This code above DOES give you a 3D histogram. I don't quite understand why you think it does not.
Why is it three-dimensional? Because argument int dim in method calcHist() has value 3. If you want 2D histogram then it would be 2.
You want to print values of 3D histogram with cout << hist.at<int>(x, y, z) << endl; where x, y and z are coordinates for 3D histogram.
Is there an equivalent of np.unique() or bincount() for an OpenCV Mat? I am working with C++ so can't just convert to a numpy array.
No, there is not! You can code your own, though:
std::vector<float> unique(const cv::Mat& input, bool sort = false)
Find the unique elements of a single channel cv::Mat.
Parameters:
input: It will be treated as if it was 1-D.
sort: Sorts the unique values (optional).
The implementation of such function is pretty straight forward, however, the following only works with single channel CV_32F:
#include <algorithm>
#include <vector>
std::vector<float> unique(const cv::Mat& input, bool sort = false)
{
if (input.channels() > 1 || input.type() != CV_32F)
{
std::cerr << "unique !!! Only works with CV_32F 1-channel Mat" << std::endl;
return std::vector<float>();
}
std::vector<float> out;
for (int y = 0; y < input.rows; ++y)
{
const float* row_ptr = input.ptr<float>(y);
for (int x = 0; x < input.cols; ++x)
{
float value = row_ptr[x];
if ( std::find(out.begin(), out.end(), value) == out.end() )
out.push_back(value);
}
}
if (sort)
std::sort(out.begin(), out.end());
return out;
}
Example:
float data[][3] = {
{ 9.0, 3.0, 7.0 },
{ 3.0, 9.0, 3.0 },
{ 1.0, 3.0, 5.0 },
{ 90.0, 30.0, 70.0 },
{ 30.0, 90.0, 50.0 }
};
cv::Mat mat(3, 5, CV_32F, &data);
std::vector<float> unik = unique(mat, true);
for (unsigned int i = 0; i < unik.size(); i++)
std::cout << unik[i] << " ";
std::cout << std::endl;
Outputs:
1 3 5 7 9 30 50 70 90
You could try to build a histogram with number of bins equal to number of possible pixel values.
Here is another suggestion for an implementation using the standard library.
opencv-unique.cpp
#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
#include <algorithm>
#include <cstdint>
/**
* #brief Find unique elements of an OpenCV image
*
* #tparam type is the C++ type to access the image elements.
* #param in is the OpenCV single-channel image to find the unique values. Note: This
* modifies the image. Make a copy with .clone(), if you need the image afterwards.
*
* #returns vector of unique elements
*/
template<typename type>
std::vector<type> unique(cv::Mat in) {
assert(in.channels() == 1 && "This implementation is only for single-channel images");
auto begin = in.begin<type>(), end = in.end<type>();
auto last = std::unique(begin, end); // remove adjacent duplicates to reduce size
std::sort(begin, last); // sort remaining elements
last = std::unique(begin, last); // remove duplicates
return std::vector<type>(begin, last);
}
int main() {
cv::Mat img = (cv::Mat_<uint16_t>(3, 4) << 1, 5, 3, 4, 3, 1, 5, 5, 1, 3, 4, 3);
std::cout << "unique values: ";
auto u = unique<uint16_t>(img);
for (auto v : u)
std::cout << v << " ";
std::cout << std::endl;
return 0;
}
Compile and execute yields:
$ g++ -Wall opencv-unique.cpp -o unique -lopencv_core -I /usr/include/opencv4 && ./unique
unique values: 1 3 4 5
The version above is for single-channel images. You can extend this to multi-channel images (to get unique colors), like this.