Related
OpenCV has a function to find connected components on a binary image:(cv::connectedComponents()), but it doesn't account for existed labels. What is the proper way to find connected components only within pixels with the same labels?
For example, I have the code:
Mat test = Mat::zeros(1, 4, DataType<int>::type);
test.at<int>(0, 0) = 1;
test.at<int>(0, 1) = 2;
test.at<int>(0, 2) = 0;
test.at<int>(0, 3) = 1;
test.convertTo(test, CV_8U);
connectedComponents(test, test);
std::cout << test << std::endl;
It has input matrix [1, 2, 0, 1], and labels it as [1, 1, 0, 2]. But I want to get [1, 2, 0, 3]. Is there a way to do it with OpenCV?
My solution for the problem:
Mat connected_components(const Mat &labels)
{
Mat res, input;
labels.convertTo(input, CV_8U);
connectedComponents(input, res);
res.convertTo(res, DataType<int>::type);
double n_labels;
minMaxLoc(res, nullptr, &n_labels);
res += labels * (n_labels + 1);
std::map<int, int> new_ids;
for (int row = 0; row < labels.rows; ++row)
{
auto row_res_data = res.ptr<int>(row);
for (int col = 0; col < labels.cols; ++col)
{
auto cur_lab = row_res_data[col];
if (cur_lab == 0)
continue;
auto iter = new_ids.emplace(cur_lab, new_ids.size() + 1);
row_res_data[col] = iter.first->second;
}
}
return res;
}
Cross post here
I have build two function with different name to drop the specfiy lines from difference Mat object, this is the code:
Mat drop_rows_int(Mat mat, vector<int> v) {
Mat mat_new = Mat::zeros(mat.rows - v.size(), mat.cols, CV_32SC1);
for (int i = 0, j = 0; i < mat.rows; i++) {
if (find(v.begin(), v.end(), i) != v.end())
{
continue;
}
else
{
int*pmat = mat.ptr<int>(i);
int*pmat_new = mat_new.ptr<int>(j);
for (int w = 0; w < mat.cols; w++) {
pmat_new[w] = pmat[w];
}
j++;
}
}
return mat_new;
}
Mat drop_rows_uchar(Mat mat, vector<int> v) {
Mat mat_new = Mat::zeros(mat.rows - v.size(), mat.cols, CV_8UC1);
for (int i = 0, j = 0; i < mat.rows; i++) {
if (find(v.begin(), v.end(), i) != v.end())
{
continue;
}
else
{
uchar*pmat = mat.ptr<uchar>(i);
uchar*pmat_new = mat_new.ptr<uchar>(j);
for (int w = 0; w < mat.cols; w++) {
pmat_new[w] = pmat[w];
}
j++;
}
}
return mat_new;
}
Then I can use it in my main() function like
int main()
{
Mat mat_uchar = (Mat_<uchar>(5, 4) << 5, 6, 0, 4, 0, 1, 9, 9, 100, 3, 5, 8, 200, 33, 1, 4, 8, 88, 23, 6);
Mat new_mat_uchar = drop_rows_uchar(mat_uchar, {2,4});
Mat mat_int = (Mat_<int>(5, 4) << 5, 6, 0, 4, 0, 1, 9, 9, 100, 3, 5, 8, 200, 33, 1, 4, 8, 88, 23, 6);
Mat new_mat_int = drop_rows_int(mat_int, { 2,4 });
return 0;
}
Yes, I made it. but as I know, the Mat can have 7 kinds of depth, such as CV_8U, CV_8S, CV_16U, CV_16S, CV_32S, CV_32F and CV_64F, So I have to build 7 functions with different name to do such thing?? Can anyone tell me how to use one function to implement it??
You cannot do that with cv::Mat. However, you can use cv::Mat_ and do some templating:
template<typename T>
cv::Mat_<T> drop_rows_int(cv::Mat_ mat, vector<int> v) {
...
}
And here you extract pointers of type T.
Just a piece of advice, for efficiency purposes I suggest sending the vector v as a const reference, if possible.
Here is the full solution:
#include "opencv/cv.h"
#include <vector>
#include <iostream>
template<typename T>
cv::Mat_<T> drop_rows(cv::Mat_<T> mat, const std::vector<int> &v) {
cv::Mat_<T> mat_new = cv::Mat_<T>::zeros(mat.rows - v.size(), mat.cols);
for (int i = 0, j = 0; i < mat.rows; i++) {
if (find(v.begin(), v.end(), i) != v.end())
continue;
else {
for (int w = 0; w < mat.cols; w++) {
mat_new(j, w) = mat(i, w);
}
j++;
}
}
return mat_new;
}
int main() {
cv::Mat_<uchar> mat = (cv::Mat_<uchar>(5, 4) << 5, 6, 0, 4, 0, 1, 9, 9, 100, 3, 5, 8, 200, 1, 2, 3, 4, 5, 6, 7);
auto result = drop_rows(mat, {2, 4});
std::cout << mat << std::endl;;
std::cout << result << std::endl;;
return 0;
}
Note that it works only for Mat_, not for Mat.
Kindly help me with the working of Distance transform and rectify the errors. I have tried Borgefors' method which has defined values for Eucledian measure. I get all zeros as output.
Below is the code which i have tried.
int _tmain(int argc, _TCHAR* argv[])
{
Mat v = imread("ref.png", 0);
imshow("input", v);
Mat forward = (Mat_<uchar>(5, 5) << 0, 11, 0, 11, 0, 11, 7, 5, 7, 11, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
Mat backward = (Mat_<uchar>(5, 5) << 0,0,0,0,0, 0,0,0,0,0, 0, 0, 0, 5, 0, 11, 7, 5, 7, 11, 0, 11, 0, 11, 0);
Mat op = cv::Mat::zeros(v.size(), CV_32FC1);
cout << forward;
cout << backward;
int r = v.rows;
int c = v.cols;
float min=100, x = 0;
int size = 3;
int lim = size / 2;
int a, b;
for (int i = lim; i <= r-1-lim; i++)
{
for (int j = lim; j <= c-1-lim; j++)
{
for (int k = -lim; k <= lim; k++)
{
for (int l = -lim; l <= lim; l++)
{
a = (v.at<uchar>(i + k, j + l));
b=(forward.at<uchar>(k + lim, l + lim));
x = a + b;
if (x>0 && min> x)
min = x;
}
}
op.at<float>(i, j) = min;
}
}
cout << min;
for (int i = (r-1-lim); i >lim; i--)
{
for (int j = (c-1-lim); j >lim; j--)
{
for (int k = -lim; k <= lim; k++)
{
for (int l = -lim; l <= lim; l++)
{
a = (v.at<uchar>(i + k, j + l));
b = (forward.at<uchar>(k + lim, l + lim));
x = a + b;
if (x >0 && min> x) min = x;
}
}
op.at<float>(i, j) = min;
}
}
cout << op;
Mat res = cv::Mat::ones(v.size(), CV_8UC1);
normalize(op, res, 0, 255, NORM_MINMAX);
imshow("output",res);
waitKey(0);
return 0;
}
Which is the best method and why it is the best way to implement Distance Transform?
Here is how to fix your code:
Apply the backward mask in the backward loop, you apply the same mask there as in the forward loop.
Use only the defined weights, the values in the mask where you wrote 0 are not part of the mask. Those pixels don't have a distance of 0!
As for your second question, it's probably out of scope for SO. But what the best method is depends very much on the goal. You have a fast and relatively accurate method here, there are other methods that are exact but more expensive.
I have 3-dimension matrix:
const int n_mat_size = 5;
const int n_mat_sz[] = { n_mat_size , n_mat_size, n_mat_size };
cv::Mat m1(3, n_mat_sz, CV_32FC1);
Now I'd like to iterate its planes and expect that it should be three two-dimensional matrices:
const cv::Mat* arrays[] = { &m1, 0 };
cv::Mat planes[3];
cv::NAryMatIterator it(arrays, planes);
std::cout << it.nplanes << ", " << it.planes[0].rows << ", " << it.planes[0].cols;
I expect to get output "3, 5, 5", but instead I get "1, 1, 125". Where is the slice of matrix?
Because the matrix m1 is continuous, there is only one plane (or slice).
Please refer to the documentation for NAryMatIterator:
It iterates through the slices (or planes), not the elements, where "slice" is a continuous part of the arrays.
For example, the matrix m2 in the following code is not continuous:
const int n_mat_size = 5;
const int n_mat_sz[] = { n_mat_size , n_mat_size, n_mat_size };
cv::Mat m1(3, n_mat_sz, CV_32FC1);
// Get plane 2 and 3 of m1
// and row 2, row 3 and row 4 of every selected plane
// m2 is not continuous
cv::Mat m2 = m1(cv::Range(2,4), cv::Range(2,5));
const cv::Mat* arrays[] = { &m2, 0 };
cv::Mat planes[3];
cv::NAryMatIterator it(arrays, planes);
std::cout << it.nplanes << ", " << it.planes[0].rows << ", " << it.planes[0].cols << std::end;
The output of the above code is: 2, 1, 15.
Note that the number of rows of every plane is always 1 and the number of columns is the number of elements contained in the plane.
There is a statement:
planes[i] = Mat(1, (int)size, A.type(), A.data);
in the function void NAryMatIterator::init, which can be found at https://github.com/opencv/opencv/blob/master/modules/core/src/matrix.cpp#L4596 .
The above statement sets the size of the plane.
To separate the matrix you gave into planes, you could use cv::InputArray::getMatVector.
The following code shows it usage.
int main()
{
const int n_mat_size = 3;
const int n_mat_sz[] = { n_mat_size , n_mat_size, n_mat_size };
cv::Mat m1(3, n_mat_sz, CV_8U);
cv::MatIterator_<uchar> it = m1.begin<uchar>();
cv::MatIterator_<uchar> end = m1.end<uchar>();
for (uchar i = 0; it != end; ++it, ++i)
{
*it = i;
}
cv::InputArray arr(m1);
std::vector<cv::Mat> planes;
arr.getMatVector(planes);
for (size_t i = 0; i < planes.size(); ++i)
{
std::cout << "-------" << std::endl
<< planes[i] << std::endl << "******" << std::endl;
}
}
Its output is as follows:
-------
[ 0, 1, 2;
3, 4, 5;
6, 7, 8]
******
-------
[ 9, 10, 11;
12, 13, 14;
15, 16, 17]
******
-------
[ 18, 19, 20;
21, 22, 23;
24, 25, 26]
******
Maybe the easiest one is to use the method cv::Mat::row(int). The corresponding code is:
int main()
{
const int n_mat_size = 3;
const int n_mat_sz[] = { n_mat_size , n_mat_size, n_mat_size };
cv::Mat m1(3, n_mat_sz, CV_8U);
cv::MatIterator_<uchar> it = m1.begin<uchar>();
cv::MatIterator_<uchar> end = m1.end<uchar>();
for (uchar i = 0; it != end; ++it, ++i)
{
*it = i;
}
int n = m1.size[0];
for (int i = 0; i < n; ++i)
{
cv::Mat three_d_plane = m1.row(i);
// three_d_plane has a size 1x3x3
// std::cout supports only 2-d matrix. Therefore, we change it to 2-d here
cv::Mat two_d_plane(three_d_plane.size[1], three_d_plane.size[2], three_d_plane.type(), three_d_plane.data);
std::cout << two_d_plane << std::endl << "----" << std::endl;
}
}
The output is
[ 0, 1, 2;
3, 4, 5;
6, 7, 8]
----
[ 9, 10, 11;
12, 13, 14;
15, 16, 17]
----
[ 18, 19, 20;
21, 22, 23;
24, 25, 26]
----
I'm trying a simple example to learn SVM in OpenCV, I'm not getting the right support vectors after training. Need some help in understanding the issue.
My code is :
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
using namespace cv;
using namespace std;
int main() {
Mat frame(Size(640,360), CV_8UC3, Scalar::all(255));
float train[15][2] = { {296, 296}, {296, 312}, {312, 8}, {312, 56}, {312, 88}, {328, 88}, {328, 104}, {328, 264}, {344, 8}, {344, 40}, {360, 8}, {360, 56}, {376, 8}, {376, 40}, {376, 56} };
Mat trainingDataMat(15, 2, CV_32FC1, train);
float labels[15] = { -1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1 };
Mat labelsMat(15, 1, CV_32FC1, labels);
CvSVMParams param;
param.svm_type = CvSVM::C_SVC;
param.C = 0.1;
param.kernel_type = SVM::LINEAR;
param.term_crit = TermCriteria(CV_TERMCRIT_ITER, 1000, 1e-6);
CvSVM SVM;
SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), param);
cout<< "Training Finished..." << endl;
for(int i = 0; i < frame.rows; ++i) {
for(int j = 0; j < frame.cols; ++j) {
Mat sampleMat = (Mat_<float>(1,2) << i,j);
float response = SVM.predict(sampleMat);
//cout << response << endl;
if(response == 1) {
frame.at<Vec3b>(i,j)[2] = 0;
} else {
frame.at<Vec3b>(i,j)[0] = 0;
}
}
}
for(int dis = 0; dis < trainingDataMat.rows; dis++) {
if(labels[dis] == 1) {
circle(frame, Point((int)train[dis][0], (int)train[dis][1]), 3, Scalar (0, 0, 0), -1);
} else {
circle(frame, Point((int)train[dis][0], (int)train[dis][1]), 3, Scalar (0, 255, 0), -1);
}
}
int n = SVM.get_support_vector_count();
for(int i = 0; i < n; i++) {
const float* v = SVM.get_support_vector(i);
cout << "support Vectors : " << v[0] << " " << v[1] <<endl;
circle(frame,Point((int)v[0], (int)v[1]), 6, Scalar(128, 128, 128), 2, 8);
}
imwrite("frame.jpg",frame);
imshow("output", frame);
waitKey(0);
return 0;
}
Output image is attached
The SVM line is not separating the two classes as I expect.
Result for Support Vector is
support Vectors : 0 0.0125
The SVM should be OK. I think the problem lies in your display. When you call your circle(frame, Point((int)train[dis][0], (int)train[dis][1]), 3, Scalar (0, 0, 0), -1);, OpenCV understands that you want a circle in row number train[dis][1] and column number train[dis][0]. This is not what you want because a specificity of OpenCV is that it uses different coordinate systems for matrices and points. image.at<float>(Point(i,j)) is equivalent to image.at<float>(j,i).
Try replacing your circle calls with this:
if(labels[dis] == 1) {
circle(frame, Point((int)train[dis][1], (int)train[dis][0]), 3, Scalar (0, 0, 0), -1);
} else {
circle(frame, Point((int)train[dis][1], (int)train[dis][0]), 3, Scalar (0, 255, 0), -1);
}