Obtaining list of unique pixel values in OpenCV Mat - c++

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.

Related

Adding elements to std::vector in a repeated way

I want to copy values from one vector to other one that will be stored in a specific order and the second vector will contain more elements than the first one.
For example:
vector<int> temp;
temp.push_back(2);
temp.push_back(0);
temp.push_back(1);
int size1 = temp.size();
int size2 = 4;
vector<int> temp2(size1 * size2);
And now I would like to fill temp2 like that: {2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1}.
Is it possible to do this using only algorithms (e.g. fill)?
Yes, it is possible using std::generate_n algorithm:
int main() {
std::vector<int> base{1, 0, 2};
const int factor = 4;
std::vector<int> out{};
std::generate_n(std::back_inserter(out), base.size() * factor,
[&base, counter=0]() mutable {
return base[counter++ / factor];
});
for(const auto i : out) {
std::cout << i << ' ';
}
}
This code prints: 1 1 1 1 0 0 0 0 2 2 2 2
The key is the lambda used in std::generate_n. It operates on internal counter to know which values, based on base vector (and accessed depending on factor and counter values), to generate.
No, this is quite a specific use case, but you can trivially implement it yourself.
#include <vector>
#include <iostream>
std::vector<int> Elongate(const std::vector<int>& src, const size_t factor)
{
std::vector<int> result;
result.reserve(src.size() * factor);
for (const auto& el : src)
result.insert(result.end(), factor, el);
return result;
}
int main()
{
std::vector<int> temp{2, 0, 1};
std::vector<int> real = Elongate(temp, 4);
for (const auto& el : real)
std::cerr << el << ' ';
std::cerr << '\n';
}
(live demo)

Finding the median value of a vector using C++

I'm a programming student, and for a project I'm working on, on of the things I have to do is compute the median value of a vector of int values and must be done by passing it through functions. Also the vector is initially generated randomly using the C++ random generator mt19937 which i have already written down in my code.I'm to do this using the sort function and vector member functions such as .begin(), .end(), and .size().
I'm supposed to make sure I find the median value of the vector and then output it
And I'm Stuck, below I have included my attempt. So where am I going wrong? I would appreciate if you would be willing to give me some pointers or resources to get going in the right direction.
Code:
#include<iostream>
#include<vector>
#include<cstdlib>
#include<ctime>
#include<random>
#include<vector>
#include<cstdlib>
#include<ctime>
#include<random>
using namespace std;
double find_median(vector<double>);
double find_median(vector<double> len)
{
{
int i;
double temp;
int n=len.size();
int mid;
double median;
bool swap;
do
{
swap = false;
for (i = 0; i< len.size()-1; i++)
{
if (len[i] > len[i + 1])
{
temp = len[i];
len[i] = len[i + 1];
len[i + 1] = temp;
swap = true;
}
}
}
while (swap);
for (i=0; i<len.size(); i++)
{
if (len[i]>len[i+1])
{
temp=len[i];
len[i]=len[i+1];
len[i+1]=temp;
}
mid=len.size()/2;
if (mid%2==0)
{
median= len[i]+len[i+1];
}
else
{
median= (len[i]+0.5);
}
}
return median;
}
}
int main()
{
int n,i;
cout<<"Input the vector size: "<<endl;
cin>>n;
vector <double> foo(n);
mt19937 rand_generator;
rand_generator.seed(time(0));
uniform_real_distribution<double> rand_distribution(0,0.8);
cout<<"original vector: "<<" ";
for (i=0; i<n; i++)
{
double rand_num=rand_distribution(rand_generator);
foo[i]=rand_num;
cout<<foo[i]<<" ";
}
double median;
median=find_median(foo);
cout<<endl;
cout<<"The median of the vector is: "<<" ";
cout<<median<<endl;
}
The median is given by
const auto median_it = len.begin() + len.size() / 2;
std::nth_element(len.begin(), median_it , len.end());
auto median = *median_it;
For even numbers (size of vector) you need to be a bit more precise. E.g., you can use
assert(!len.empty());
if (len.size() % 2 == 0) {
const auto median_it1 = len.begin() + len.size() / 2 - 1;
const auto median_it2 = len.begin() + len.size() / 2;
std::nth_element(len.begin(), median_it1 , len.end());
const auto e1 = *median_it1;
std::nth_element(len.begin(), median_it2 , len.end());
const auto e2 = *median_it2;
return (e1 + e2) / 2;
} else {
const auto median_it = len.begin() + len.size() / 2;
std::nth_element(len.begin(), median_it , len.end());
return *median_it;
}
There are of course many different ways how we can get element e1. We could also use max or whatever we want. But this line is important because nth_element only places the nth element correctly, the remaining elements are ordered before or after this element, depending on whether they are larger or smaller. This range is unsorted.
This code is guaranteed to have linear complexity on average, i.e., O(N), therefore it is asymptotically better than sort, which is O(N log N).
Regarding your code:
for (i=0; i<len.size(); i++){
if (len[i]>len[i+1])
This will not work, as you access len[len.size()] in the last iteration which does not exist.
std::sort(len.begin(), len.end());
double median = len[len.size() / 2];
will do it. You might need to take the average of the middle two elements if size() is even, depending on your requirements:
0.5 * (len[len.size() / 2 - 1] + len[len.size() / 2]);
Instead of trying to do everything at once, you should start with simple test cases and work upwards:
#include<vector>
double find_median(std::vector<double> len);
// Return the number of failures - shell interprets 0 as 'success',
// which suits us perfectly.
int main()
{
return find_median({0, 1, 1, 2}) != 1;
}
This already fails with your code (even after fixing i to be an unsigned type), so you could start debugging (even 'dry' debugging, where you trace the code through on paper; that's probably enough here).
I do note that with a smaller test case, such as {0, 1, 2}, I get a crash rather than merely failing the test, so there's something that really needs to be fixed.
Let's replace the implementation with one based on overseas's answer:
#include <algorithm>
#include <limits>
#include <vector>
double find_median(std::vector<double> len)
{
if (len.size() < 1)
return std::numeric_limits<double>::signaling_NaN();
const auto alpha = len.begin();
const auto omega = len.end();
// Find the two middle positions (they will be the same if size is odd)
const auto i1 = alpha + (len.size()-1) / 2;
const auto i2 = alpha + len.size() / 2;
// Partial sort to place the correct elements at those indexes (it's okay to modify the vector,
// as we've been given a copy; otherwise, we could use std::partial_sort_copy to populate a
// temporary vector).
std::nth_element(alpha, i1, omega);
std::nth_element(i1, i2, omega);
return 0.5 * (*i1 + *i2);
}
Now, our test passes. We can write a helper method to allow us to create more tests:
#include <iostream>
bool test_median(const std::vector<double>& v, double expected)
{
auto actual = find_median(v);
if (abs(expected - actual) > 0.01) {
std::cerr << actual << " - expected " << expected << std::endl;
return true;
} else {
std::cout << actual << std::endl;
return false;
}
}
int main()
{
return test_median({0, 1, 1, 2}, 1)
+ test_median({5}, 5)
+ test_median({5, 5, 5, 0, 0, 0, 1, 2}, 1.5);
}
Once you have the simple test cases working, you can manage more complex ones. Only then is it time to create a large array of random values to see how well it scales:
#include <ctime>
#include <functional>
#include <random>
int main(int argc, char **argv)
{
std::vector<double> foo;
const int n = argc > 1 ? std::stoi(argv[1]) : 10;
foo.reserve(n);
std::mt19937 rand_generator(std::time(0));
std::uniform_real_distribution<double> rand_distribution(0,0.8);
std::generate_n(std::back_inserter(foo), n, std::bind(rand_distribution, rand_generator));
std::cout << "Vector:";
for (auto v: foo)
std::cout << ' ' << v;
std::cout << "\nMedian = " << find_median(foo) << std::endl;
}
(I've taken the number of elements as a command-line argument; that's more convenient in my build than reading it from cin). Notice that instead of allocating n doubles in the vector, we simply reserve capacity for them, but don't create any until needed.
For fun and kicks, we can now make find_median() generic. I'll leave that as an exercise; I suggest you start with:
typename<class Iterator>
auto find_median(Iterator alpha, Iterator omega)
{
using value_type = typename Iterator::value_type;
if (alpha == omega)
return std::numeric_limits<value_type>::signaling_NaN();
}

Opencv - how does the filter2D() method actually work?

I did look for the source code to Filter2D but could not find it. Neither could Visual c++.
Are there any experts on the filter2D algorithm here? I know how it's supposed to work but not how it actually works. I made my own filter2d() function to test things, and the results are substantially different from opencvs filter2D(). Here's my code:
Mat myfilter2d(Mat input, Mat filter){
Mat dst = input.clone();
cout << " filter data successfully found. Rows:" << filter.rows << " cols:" << filter.cols << " channels:" << filter.channels() << "\n";
cout << " input data successfully found. Rows:" << input.rows << " cols:" << input.cols << " channels:" << input.channels() << "\n";
for (int i = 0-(filter.rows/2);i<input.rows-(filter.rows/2);i++){
for (int j = 0-(filter.cols/2);j<input.cols-(filter.cols/2);j++){ //adding k and l to i and j will make up the difference and allow us to process the whole image
float filtertotal = 0;
for (int k = 0; k < filter.rows;k++){
for (int l = 0; l < filter.rows;l++){
if(i+k >= 0 && i+k < input.rows && j+l >= 0 && j+l < input.cols){ //don't try to process pixels off the endge of the map
float a = input.at<uchar>(i+k,j+l);
float b = filter.at<float>(k,l);
float product = a * b;
filtertotal += product;
}
}
}
//filter all proccessed for this pixel, write it to dst
st.at<uchar>(i+(filter.rows/2),j+(filter.cols/2)) = filtertotal;
}
}
return dst;
}
Anybody see anything wrong with my implementation? (besides being slow)
Here is my execution:
cvtColor(src,src_grey,CV_BGR2GRAY);
Mat dst = myfilter2d(src_grey,filter);
imshow("myfilter2d",dst);
filter2D(src_grey,dst2,-1,filter);
imshow("filter2d",dst2);
Here is my kernel:
float megapixelarray[basesize][basesize] = {
{1,1,-1,1,1},
{1,1,-1,1,1},
{1,1,1,1,1},
{1,1,-1,1,1},
{1,1,-1,1,1}
};
And here are the (substantially different) results:
Thoughts, anyone?
EDIT: Thanks to Brians answer I added this code:
//normalize the kernel so its sum = 1
Scalar mysum = sum(dst);
dst = dst / mysum[0]; //make sure its not 0
dst = dst * -1; //show negetive
and filter2d worked better. Certain filters give an exact match, and other filters, like the Sobel, fail miserably.
I'm getting close to the actual algorithm, but not there yet. Anyone else with any ideas?
I think the issue is probably one of scale: if your input image is an 8-bit image, most of the time the convolution will produce a value that overflows the maximum value 255.
In your implementation it looks like you are getting the wrapped-around value, but most OpenCV functions handle overflow by capping to the maximum (or minimum) value. That explains why most of the output of OpenCV's function is white, and also why you are getting concentric shapes in your output too.
To account for this, normalize your megapixelarray filter by dividing every value by the entire sum of the filter (i.e. make sure that the sum of the filter values is 1):
For example, instead of this filter (sum = 10):
1 1 1
1 2 1
1 1 1
Try this filter (sum = 1):
0.1 0.1 0.1
0.1 0.2 0.1
0.1 0.1 0.1
Here is my solution for creating the filter2D manually:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
int main(int argc, const char * argv[]) {
Mat img;
Mat img_conv;
Mat my_kernel;
Mat my_conv;
// Controlling if the image is loaded correctly
img = imread("my_image.jpg",CV_LOAD_IMAGE_COLOR);
if(! img.data )
{
cout << "Could not open or find the image" << std::endl ;
return -1;
}
imshow("original image", img);
img.convertTo(img, CV_64FC3);
int kernel_size; // permitted sizes: 3, 5, 7, 9 etc
cout << "Select the size of kernel (it should be an odd number from 3 onwards): \n" << endl;
cin >> kernel_size;
// Defining the kernel here
int selection;
cout << "Select the type of kernel:\n" << "1. Identity Operator \n2. Mean Filter \n3. Spatial shift \n4. Sharpening\n-> ";
cin >> selection;
switch (selection){
case 1:
my_kernel = (Mat_<double>(kernel_size,kernel_size) << 0, 0, 0, 0, 1, 0, 0, 0, 0);
break;
case 2:
my_kernel = (Mat_<double>(kernel_size,kernel_size) << 1, 1, 1, 1, 1, 1, 1, 1, 1) / ( kernel_size * kernel_size);
break;
case 3:
my_kernel = (Mat_<double>(kernel_size,kernel_size) << 0, 0, 0, 0, 0, 1, 0, 0, 0);
break;
case 4:
my_kernel = (Mat_<double>(kernel_size,kernel_size) << -1, -1, -1, -1, 17, -1, -1, -1, -1) / ( kernel_size * kernel_size);
break;
default:
cerr << "Invalid selection";
return 1;
break;
}
cout << "my kernel:\n "<<my_kernel << endl;
// Adding the countour of nulls around the original image, to avoid border problems during convolution
img_conv = Mat::Mat(img.rows + my_kernel.rows - 1, img.cols + my_kernel.cols - 1, CV_64FC3, CV_RGB(0,0,0));
for (int x=0; x<img.rows; x++) {
for (int y=0; y<img.cols; y++) {
img_conv.at<Vec3d>(x+1,y+1)[0] = img.at<Vec3d>(x,y)[0];
img_conv.at<Vec3d>(x+1,y+1)[1] = img.at<Vec3d>(x,y)[1];
img_conv.at<Vec3d>(x+1,y+1)[2] = img.at<Vec3d>(x,y)[2];
}
}
//Performing the convolution
my_conv = Mat::Mat(img.rows, img.cols, CV_64FC3, CV_RGB(0,0,0));
for (int x=(my_kernel.rows-1)/2; x<img_conv.rows-((my_kernel.rows-1)/2); x++) {
for (int y=(my_kernel.cols-1)/2; y<img_conv.cols-((my_kernel.cols-1)/2); y++) {
double comp_1=0;
double comp_2=0;
double comp_3=0;
for (int u=-(my_kernel.rows-1)/2; u<=(my_kernel.rows-1)/2; u++) {
for (int v=-(my_kernel.cols-1)/2; v<=(my_kernel.cols-1)/2; v++) {
comp_1 = comp_1 + ( img_conv.at<Vec3d>(x+u,y+v)[0] * my_kernel.at<double>(u + ((my_kernel.rows-1)/2) ,v + ((my_kernel.cols-1)/2)));
comp_2 = comp_2 + ( img_conv.at<Vec3d>(x+u,y+v)[1] * my_kernel.at<double>(u + ((my_kernel.rows-1)/2),v + ((my_kernel.cols-1)/2)));
comp_3 = comp_3 + ( img_conv.at<Vec3d>(x+u,y+v)[2] * my_kernel.at<double>(u + ((my_kernel.rows-1)/2),v + ((my_kernel.cols-1)/2)));
}
}
my_conv.at<Vec3d>(x-((my_kernel.rows-1)/2),y-(my_kernel.cols-1)/2)[0] = comp_1;
my_conv.at<Vec3d>(x-((my_kernel.rows-1)/2),y-(my_kernel.cols-1)/2)[1] = comp_2;
my_conv.at<Vec3d>(x-((my_kernel.rows-1)/2),y-(my_kernel.cols-1)/2)[2] = comp_3;
}
}
my_conv.convertTo(my_conv, CV_8UC3);
imshow("convolution - manual", my_conv);
// Performing the filtering using the opencv funtions
Mat dst;
filter2D(img, dst, -1 , my_kernel, Point( -1, -1 ), 0, BORDER_DEFAULT );
dst.convertTo(dst, CV_8UC3);
imshow("convlution - opencv", dst);
waitKey();
return 0;
}

Having a matrix MxN of integers how to group them into polygons with boost geometry?

We have a matrix of given integers (any from 1 to INT_MAX) like
1 2 3
1 3 3
1 3 3
100 2 1
We want to create polygons with same colors for each unique int in matrix so our polygons would have coords/groupings like shown here.
and we could generate image like this:
Which *(because of vectirisation that was performed would scale to such size like):
(sorry for crappy drawings)
Is it possible and how to do such thing with boost geometry?
Update:
So #sehe sad: I'd simply let Boost Geometry do most of the work. so I created this pixel by pixel class aeria grower using purely Boost.Geometry, compiles, runs but I need it to run on clustered data.. and I have 1000 by 1800 files of uchars (each unique uchar == data belongs to that claster). Problem with this code: on 18th line it gets SO WARY SLOW that each point creation starts to take more than one second=(
code:
//Boost
#include <boost/assign.hpp>
#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/geometry/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/multi/geometries/multi_polygon.hpp>
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>
//and this is why we use Boost Geometry from Boost trunk
//#include <boost/geometry/extensions/io/svg/svg_mapper.hpp>
BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian)
void make_point(int x, int y, boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > & ring)
{
using namespace boost::assign;
boost::geometry::append( ring, boost::geometry::model::d2::point_xy<double>(x-1, y-1));
boost::geometry::append( ring, boost::geometry::model::d2::point_xy<double>(x, y-1));
boost::geometry::append( ring, boost::geometry::model::d2::point_xy<double>(x, y));
boost::geometry::append( ring, boost::geometry::model::d2::point_xy<double>(x-1, y));
boost::geometry::append( ring, boost::geometry::model::d2::point_xy<double>(x-1, y-1));
boost::geometry::correct(ring);
}
void create_point(int x, int y, boost::geometry::model::multi_polygon< boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > > & mp)
{
boost::geometry::model::multi_polygon< boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > > temp;
boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > ring;
make_point(x, y, ring);
boost::geometry::union_(mp, ring, temp);
boost::geometry::correct(temp);
mp=temp;
}
int main()
{
using namespace boost::assign;
boost::geometry::model::multi_polygon< boost::geometry::model::polygon < boost::geometry::model::d2::point_xy<double> > > pol, simpl;
//read image
std::ifstream in("1.mask", std::ios_base::in | std::ios_base::binary);
int sx, sy;
in.read(reinterpret_cast<char*>(&sy), sizeof(int));
in.read(reinterpret_cast<char*>(&sx), sizeof(int));
std::vector< std::vector<unsigned char> > image(sy);
for(int i =1; i <= sy; i++)
{
std::vector<unsigned char> row(sx);
in.read(reinterpret_cast<char*>(&row[0]), sx);
image[i-1] = row;
}
//
std::map<unsigned char, boost::geometry::model::multi_polygon < boost::geometry::model::polygon < boost::geometry::model::d2::point_xy<double> > > > layered_image;
for(int y=1; y <= sy; y++)
{
for(int x=1; x <= sx; x++)
{
if (image[y-1][x-1] != 1)
{
create_point(x, y, layered_image[image[y-1][x-1]]);
std::cout << x << " : " << y << std::endl;
}
}
}
}
So as you can see my code suks.. so I decided to create a renderer for #sehe code:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <set>
//Boost
#include <boost/assign.hpp>
#include <boost/array.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/geometry/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/multi/geometries/multi_polygon.hpp>
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/mersenne_twister.hpp>
//and this is why we use Boost Geometry from Boost trunk
#include <boost/geometry/extensions/io/svg/svg_mapper.hpp>
BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian)
namespace mxdetail
{
typedef size_t cell_id; // row * COLS + col
template <typename T> struct area
{
T value;
typedef std::vector<cell_id> cells_t;
cells_t cells;
};
template <typename T, size_t Rows, size_t Cols>
std::vector<area<T> > getareas(const boost::array<boost::array<T, Cols>, Rows>& matrix)
{
typedef boost::array<boost::array<T, Cols>, Rows> mtx;
std::vector<area<T> > areas;
struct visitor_t
{
const mtx& matrix;
std::set<cell_id> visited;
visitor_t(const mtx& mtx) : matrix(mtx) { }
area<T> start(const int row, const int col)
{
area<T> result;
visit(row, col, result);
return result;
}
void visit(const int row, const int col, area<T>& current)
{
const cell_id id = row*Cols+col;
if (visited.end() != visited.find(id))
return;
bool matches = current.cells.empty() || (matrix[row][col] == current.value);
if (matches)
{
visited.insert(id);
current.value = matrix[row][col];
current.cells.push_back(id);
// process neighbours
for (int nrow=std::max(0, row-1); nrow < std::min((int) Rows, row+2); nrow++)
for (int ncol=std::max(0, col-1); ncol < std::min((int) Cols, col+2); ncol++)
/* if (ncol!=col || nrow!=row) */
visit(nrow, ncol, current);
}
}
} visitor(matrix);
for (int r=0; r < (int) Rows; r++)
for (int c=0; c < (int) Cols; c++)
{
mxdetail::area<int> area = visitor.start(r,c);
if (!area.cells.empty()) // happens when startpoint already visited
areas.push_back(area);
}
return areas;
}
}
typedef boost::array<int, 4> row;
template <typename T, size_t N>
boost::array<T, N> make_array(const T (&a)[N])
{
boost::array<T, N> result;
std::copy(a, a+N, result.begin());
return result;
}
void make_point(int x, int y, boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > & ring)
{
using namespace boost::assign;
boost::geometry::append( ring, boost::geometry::model::d2::point_xy<double>(x-1, y-1));
boost::geometry::append( ring, boost::geometry::model::d2::point_xy<double>(x, y-1));
boost::geometry::append( ring, boost::geometry::model::d2::point_xy<double>(x, y));
boost::geometry::append( ring, boost::geometry::model::d2::point_xy<double>(x-1, y));
boost::geometry::append( ring, boost::geometry::model::d2::point_xy<double>(x-1, y-1));
boost::geometry::correct(ring);
}
void create_point(int x, int y, boost::geometry::model::multi_polygon< boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > > & mp)
{
boost::geometry::model::multi_polygon< boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > > temp;
boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > ring;
make_point(x, y, ring);
boost::geometry::union_(mp, ring, temp);
boost::geometry::correct(temp);
mp=temp;
}
boost::random::mt19937 rng;
boost::random::uniform_int_distribution<> color(10,255);
std::string fill_rule()
{
int red, green, blue;
red = color(rng);
green = color(rng);
blue = color(rng);
std::ostringstream rule;
rule << "fill-rule:nonzero;fill-opacity:0.5;fill:rgb("
<< red << "," << green << "," << blue
<< ");stroke:rgb("
<< (red - 5) << "," << (green - 5) << "," << (blue -5)
<< ");stroke-width:2";
return rule.str();
}
int main()
{
int sx = 4;
int sy = 5;
int row0[] = { 1 , 2, 3, 3, };
int row1[] = { 1 , 3, 3, 3,};
int row2[] = { 1 , 3, 3, 3, };
int row3[] = { 2 , 2, 1, 2, };
int row4[] = { 100, 2, 2, 2, };
boost::array<row, 5> matrix;
matrix[0] = make_array(row0);
matrix[1] = make_array(row1);
matrix[2] = make_array(row2);
matrix[3] = make_array(row3);
matrix[4] = make_array(row4);
typedef std::vector<mxdetail::area<int> > areas_t;
typedef areas_t::value_type::cells_t cells_t;
areas_t areas = mxdetail::getareas(matrix);
using namespace boost::assign;
typedef boost::geometry::model::polygon
<
boost::geometry::model::d2::point_xy<double>
> polygon;
typedef boost::geometry::model::multi_polygon<polygon> mp;
typedef boost::geometry::point_type<mp>::type point_type;
std::string filename = "draw.svg";
std::ofstream svg(filename.c_str());
boost::geometry::svg_mapper<point_type> mapper(svg, 400, 400);
for (areas_t::const_iterator it=areas.begin(); it!=areas.end(); ++it)
{
mp pol;
std::cout << "area of " << it->value << ": ";
for (cells_t::const_iterator pit=it->cells.begin(); pit!=it->cells.end(); ++pit)
{
int row = *pit / 3, col = *pit % 3;
std::cout << "(" << row << "," << col << "), ";
create_point( (row+1), (col+1), pol);
}
std::cout << std::endl;
mapper.add(pol);
mapper.map(pol, fill_rule());
}
std::cout << "areas detected: " << areas.size() << std::endl;
std::cin.get();
}
this code is compilable but it sucks (seems I did not get how to work with arrays after all...):
In short, if I got the question right, I'd simply let Boost Geometry do most of the work.
For a sample matrix of NxM, create NxM 'flyweight' rectangle polygons to correspond to each matrix cell visually.
Now, using an iterative deepening algorithm, find all groups:
* for each _unvisited_ cell in matrix
* start a new group
* [visit:]
- mark _visited_
- for each neighbour with equal value:
- add to curent group and
- recurse [visit:]
Note that the result of this algorithm could be distinct groups with the same values (representing disjunct polygons). E.g. the value 2 from the sample in the OP would result in two groups.
Now for each group, simply call Boost Geometry's Union_ algorithm to find the consolidated polygon to represent that group.
Sample implementation
Update Here is a non-optimized implementation in C++11:
Edit See here for C++03 version (using Boost)
The sample data used in the test corresponds to the matrix from the question.
#include <iostream>
#include <array>
#include <vector>
#include <set>
namespace mxdetail
{
typedef size_t cell_id; // row * COLS + col
template <typename T> struct area
{
T value;
std::vector<cell_id> cells;
};
template <typename T, size_t Rows, size_t Cols>
std::vector<area<T> > getareas(const std::array<std::array<T, Cols>, Rows>& matrix)
{
typedef std::array<std::array<T, Cols>, Rows> mtx;
std::vector<area<T> > areas;
struct visitor_t
{
const mtx& matrix;
std::set<cell_id> visited;
visitor_t(const mtx& mtx) : matrix(mtx) { }
area<T> start(const int row, const int col)
{
area<T> result;
visit(row, col, result);
return result;
}
void visit(const int row, const int col, area<T>& current)
{
const cell_id id = row*Cols+col;
if (visited.end() != visited.find(id))
return;
bool matches = current.cells.empty() || (matrix[row][col] == current.value);
if (matches)
{
visited.insert(id);
current.value = matrix[row][col];
current.cells.push_back(id);
// process neighbours
for (int nrow=std::max(0, row-1); nrow < std::min((int) Rows, row+2); nrow++)
for (int ncol=std::max(0, col-1); ncol < std::min((int) Cols, col+2); ncol++)
/* if (ncol!=col || nrow!=row) */
visit(nrow, ncol, current);
}
}
} visitor(matrix);
for (int r=0; r < Rows; r++)
for (int c=0; c < Cols; c++)
{
auto area = visitor.start(r,c);
if (!area.cells.empty()) // happens when startpoint already visited
areas.push_back(area);
}
return areas;
}
}
int main()
{
typedef std::array<int, 3> row;
std::array<row, 4> matrix = {
row { 1 , 2, 3, },
row { 1 , 3, 3, },
row { 1 , 3, 3, },
row { 100, 2, 1, },
};
auto areas = mxdetail::getareas(matrix);
std::cout << "areas detected: " << areas.size() << std::endl;
for (const auto& area : areas)
{
std::cout << "area of " << area.value << ": ";
for (auto pt : area.cells)
{
int row = pt / 3, col = pt % 3;
std::cout << "(" << row << "," << col << "), ";
}
std::cout << std::endl;
}
}
Compiled with gcc-4.6 -std=c++0x the output is:
areas detected: 6
area of 1: (0,0), (1,0), (2,0),
area of 2: (0,1),
area of 3: (0,2), (1,1), (1,2), (2,1), (2,2),
area of 100: (3,0),
area of 2: (3,1),
area of 1: (3,2),
When number of points is big (say, more than 1000x1000), the solution above would gobble a lot of memory. And this is exactly what happened to the topic-starter.
Below I show more scalable approach.
I would separate two problems here: one is to find the areas, another is to convert them into polygons.
The first problem is actually equivalent to finding the connected components of the grid graph where neighbors has edges if and only if they have equal "colors" attached to it. One can use a grid graph from boost-graph.
#include <boost/graph/grid_graph.hpp>
// Define dimension lengths, a MxN in this case
boost::array<int, 2> lengths = { { M, N } };
// Create a MxN two-dimensional, unwrapped grid graph
grid_graph<2> graph(lengths);
Next, we should convert a given matrix M into an edge filter: grid edges are present iff the "color" of the neighbors are the same.
template <class Matrix>
struct GridEdgeFilter
{
typedef grid_graph<2> grid;
GridEdgeFilter(const Matrix & m, const grid&g):_m(&m),_g(&g){}
/// \return true iff edge is present in the graph
bool operator()(grid::edge_descriptor e) const
{
grid::vertex_descriptor src = source(e,*_g), tgt = target(e,*_g);
//src[0] is x-coord of src, etc. The value (*m)[x,y] is the color of the point (x,y).
//Edge is preserved iff matrix values are equal
return (*_m)[src[0],src[1]] == (*_m)[tgt[0],tgt[1]];
}
const Matrix * _m;
const grid* _g;
};
Finally, we define a boost::filtered_graph of grid and EdgeFilter and call Boost.Graph algorithm for connected components.
Each connected component represents a set of points of a single color i.e. exactly the area we want to transform into a polygon.
Here we have another issue. Boost.Geometry only allows to merge polygons one by one. Hence it becomes very slow when number of polygons is big.
The better way is to use Boost.Polygon, namely its Property Merge functionality. One starts with empty property_merge object, and goes on by inserting rectangles of given color (you can set color as a property). Then one calls the method merge and gets a polygon_set as the output.

Accessing a matrix element in the "Mat" object (not the CvMat object) in OpenCV C++

How to access elements by row, col in OpenCV 2.0's new "Mat" class? The documentation is linked below, but I have not been able to make any sense of it.
http://opencv.willowgarage.com/documentation/cpp/basic_structures.html#mat
On the documentation:
http://docs.opencv.org/2.4/modules/core/doc/basic_structures.html#mat
It says:
(...) if you know the matrix element
type, e.g. it is float, then you can
use at<>() method
That is, you can use:
Mat M(100, 100, CV_64F);
cout << M.at<double>(0,0);
Maybe it is easier to use the Mat_ class. It is a template wrapper for Mat.
Mat_ has the operator() overloaded in order to access the elements.
The ideas provided above are good. For fast access (in case you would like to make a real time application) you could try the following:
//suppose you read an image from a file that is gray scale
Mat image = imread("Your path", CV_8UC1);
//...do some processing
uint8_t *myData = image.data;
int width = image.cols;
int height = image.rows;
int _stride = image.step;//in case cols != strides
for(int i = 0; i < height; i++)
{
for(int j = 0; j < width; j++)
{
uint8_t val = myData[ i * _stride + j];
//do whatever you want with your value
}
}
Pointer access is much faster than the Mat.at<> accessing. Hope it helps!
Based on what #J. Calleja said, you have two choices
Method 1 - Random access
If you want to random access the element of Mat, just simply use
Mat.at<data_Type>(row_num, col_num) = value;
Method 2 - Continuous access
If you want to continuous access, OpenCV provides Mat iterator compatible with STL iterator and it's more C++ style
MatIterator_<double> it, end;
for( it = I.begin<double>(), end = I.end<double>(); it != end; ++it)
{
//do something here
}
or
for(int row = 0; row < mat.rows; ++row) {
float* p = mat.ptr(row); //pointer p points to the first place of each row
for(int col = 0; col < mat.cols; ++col) {
*p++; // operation here
}
}
If you have any difficulty to understand how Method 2 works, I borrow the picture from a blog post in the article Dynamic Two-dimensioned Arrays in C, which is much more intuitive and comprehensible.
See the picture below.
OCV goes out of its way to make sure you can't do this without knowing the element type, but if you want an easily codable but not-very-efficient way to read it type-agnostically, you can use something like
double val=mean(someMat(Rect(x,y,1,1)))[channel];
To do it well, you do have to know the type though. The at<> method is the safe way, but direct access to the data pointer is generally faster if you do it correctly.
For cv::Mat_<T> mat just use mat(row, col)
Accessing elements of a matrix with specified type cv::Mat_< _Tp > is more comfortable, as you can skip the template specification. This is pointed out in the documentation as well.
code:
cv::Mat1d mat0 = cv::Mat1d::zeros(3, 4);
std::cout << "mat0:\n" << mat0 << std::endl;
std::cout << "element: " << mat0(2, 0) << std::endl;
std::cout << std::endl;
cv::Mat1d mat1 = (cv::Mat1d(3, 4) <<
1, NAN, 10.5, NAN,
NAN, -99, .5, NAN,
-70, NAN, -2, NAN);
std::cout << "mat1:\n" << mat1 << std::endl;
std::cout << "element: " << mat1(0, 2) << std::endl;
std::cout << std::endl;
cv::Mat mat2 = cv::Mat(3, 4, CV_32F, 0.0);
std::cout << "mat2:\n" << mat2 << std::endl;
std::cout << "element: " << mat2.at<float>(2, 0) << std::endl;
std::cout << std::endl;
output:
mat0:
[0, 0, 0, 0;
0, 0, 0, 0;
0, 0, 0, 0]
element: 0
mat1:
[1, nan, 10.5, nan;
nan, -99, 0.5, nan;
-70, nan, -2, nan]
element: 10.5
mat2:
[0, 0, 0, 0;
0, 0, 0, 0;
0, 0, 0, 0]
element: 0