How do I set all odd rows of a cv::Mat to a scalar value? - opencv

I would like to set all odd rows of an nxm cv::Mat to a scalar value. There are brute force approaches to this problem, but I would like to know if there is something more elegant.
Extending from this problem, I would like to set all even rows of a different channel to a scalar value.

There are no OpenCV built-in functions that do this, but this can be done easily in 3 lines of code.
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
// The new value for the rows
uchar some_value = 100;
// Your matrix (here random initialized)
Mat1b mat(5, 3, uchar(0));
randu(mat, 0, 10);
// Set all even rows to some_value
for (int r = 0; r < mat.rows; r += 2) {
for (int c = 0; c < mat.cols; ++c) {
mat(r, c) = some_value;
}
}
return 0;
}
Yes, this is probably you called "brute force", but this is the method with fewer accesses to the matrix.
It's also very fast, you can eventually implement it with pointers to be even faster (here an example with 3 channels):
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Vec3b some_value(100, 101, 102);
Mat3b mat(5, 3, Vec3b(0,0,0));
randu(mat, Scalar(0, 0, 0), Scalar(10, 10, 10));
for (int r = 0; r < mat.rows; r += 2) {
Vec3b* ptr = mat.ptr<Vec3b>(r);
for (int c = 0; c < mat.cols; ++c) {
ptr[c] = some_value;
}
}
return 0;
}
You can also create a mask with odd rows white (255), and even rows black (0), and use cv::setTo to set values according to the mask. This however is probably much slower, because you need to i) create the mask, and ii) access each pixel in the matrix (probably exploiting optimized code, though).

Not sure of speed but I guess OP referred to elegance that of MATLAB matrix operations, and sure some of them have been imported to OpenCV, e.g.
cv::Mat m(8,15,CV_32FC1,cv::Scalar(0));
for (int i=0;i<m.rows;i+=2)
m.row(i).setTo( 32 );
Likewise, you can construct a cv::Mat header for each column separately using cv::Mat::col(int i) function.

Related

Is there a built-in function to split a 3-channel Mat into three 3-channel Mat rather than into three 1-channel Mat?

As far as I know the built-in split will split one 3-channel Mat into three 1-channel Mat. As a result, those three Mat are just gray scale with some different intensities.
My intent is to get three 3-channel Mat as follows.
void splitTo8UC3(const Mat& input, vector<Mat>& output)
{
Mat blue = input.clone();
Mat green = input.clone();
Mat red = input.clone();
const uint N = input.rows * input.step;
for (uint i = 0; i < N; i += 3)
{
// blue.data[i]
green.data[i] = 0;
red.data[i] = 0;
blue.data[i + 1] = 0;
//green.data[i+1]
red.data[i + 1] = 0;
blue.data[i + 2] = 0;
green.data[i + 2] = 0;
//red.data[i+2]
}
output.push_back(blue);
output.push_back(green);
output.push_back(red);
}
It works but instead of reinventing the wheel, I am looking for the built-in if any.
Edit
The proposed solution must be faster than mine.
EDIT: I incorporated Dan's suggested improvements from his comment.
I can't think of a built-in function exactly doing this, and I also couldn't find one. But while doing some research, I came across the mixChannels function, which might improve your solution. At least, it avoids implementing a loop.
Here are my modifications to your code:
void splitTo8UC3(const cv::Mat& input, std::vector<cv::Mat>& output)
{
// Allocate outputs
cv::Mat b(cv::Mat::zeros(input.size(), input.type()));
cv::Mat g(cv::Mat::zeros(input.size(), input.type()));
cv::Mat r(cv::Mat::zeros(input.size(), input.type()));
// Collect outputs
cv::Mat out[] = { b, g, r };
// Set up index pairs
int from_to[] = { 0,0, 1,4, 2,8 };
cv::mixChannels(&input, 1, out, 3, from_to, 3);
output.assign(std::begin(out), std::end(out));
}
Let's have this test image colors.png:
And, let's have this test code:
cv::Mat img = cv::imread("images/colors.png");
std::vector<cv::Mat> bgr;
splitTo8UC3(img, bgr);
cv::imwrite("images/b.png", bgr[0]);
cv::imwrite("images/g.png", bgr[1]);
cv::imwrite("images/r.png", bgr[2]);
Then, we get the following outputs b.png, g.png, and r.png, which hopefully are the them as for your initial solution:
Hope that helps!

How to implement Sobel operator

I have implemented Sobel operator in vertical direction. But the result which I am getting is very poor. I have attached my code below.
int mask_size= 3;
char mask [3][3]= {{-1,0,1},{-2,0,2},{-1,0,1}};
void sobel(Mat input_image)
{
/**Padding m-1 and n-1 zeroes to the result where m and n are mask_size**/
Mat result=Mat::zeros(input_image.rows+(mask_size - 1) * 2,input_image.cols+(mask_size - 1) * 2,CV_8UC1);
Mat result1=Mat::zeros(result.rows,result.cols,CV_8UC1);
int sum= 0;
/*For loop for copying original values to new padded image **/
for(int i=0;i<input_image.rows;i++)
for(int j=0;j<input_image.cols;j++)
result.at<uchar>(i+(mask_size-1),j+(mask_size-1))=input_image.at<uchar>(i,j);
GaussianBlur( result, result, Size(5,5), 0, 0, BORDER_DEFAULT );
/**For loop to implement the convolution **/
for(int i=0;i<result.rows-(mask_size - 1);i++)
for(int j=0;j<result.cols-(mask_size - 1);j++)
{
int counter=0;
int counterX=0,counterY=0;
sum= 0;
for(int k= i ; k < i + mask_size ; k++)
{
for(int l= j ; l< j + mask_size ; l++)
{
sum+=result.at<uchar>(k,l) * mask[counterX][counterY];
counterY++;
}
counterY=0;
counterX++;
}
result1.at<uchar>(i+mask_size/2,j+mask_size/2)=sum/(mask_size * mask_size);
}
/** Truncating all the extras rows and columns **/
result=Mat::zeros( result1.rows - (mask_size - 1) * 2, result1.cols - (mask_size - 1) * 2,CV_8UC1);
for(int i=0;i<result.rows;i++)
for(int j=0;j<result.cols;j++)
result.at<uchar>(i,j)=result1.at<uchar>(i+(mask_size - 1),j+(mask_size - 1));
imshow("Input",result);
imwrite("output2.tif",result);
}
My input to the algorithm is
My output is
I have also tried using Gaussian blur before actually convolving an image and the output I got is
The output which I am expecting is
The guide I am using is: https://www.tutorialspoint.com/dip/sobel_operator.htm
Your convolution looks ok although I only had a quick look.
Check your output type. It's unsigned char.
Now think about the values your output pixels may have if you have negative kernel values and if it is a good idea to store them in uchar directly.
If you store -1 in an unsigned char it will be wrapped around and your output is 255. In case you're wondering where all that excess white stuff is coming from. That's actually small negative gradients.
The desired result looks like the absolute of the Sobel output values.

OpenCV: fast matrix computation

I have an nxd matrix V=[v_1; v_2;...; v_n] (; means new row) where v_i are 1xd vectors.
I want to compute the following sum: v_1^T*v_1 + v_2^T*v_2 + ... + v_n^T*v_n, which is a dxd matrix (v_i^T is the transpose of v_i).
For the moment I use a for loop, as in the code below, which is not efficient when n is very large (I think so).
#include <iostream>
#include <opencv2/core.hpp>
using namespace cv;
using namespace std;
int main (int argc, char * argv[])
{
int n=5, d=3;
Mat V = Mat(n, d, CV_32F);
randu(V, Scalar::all(0), Scalar::all(10));
cout<<V<<endl<<endl;
Mat M = Mat::zeros(d, d, CV_32F);
for(int i=0; i<n; i++)
{
M = M + V.row(i).t()*V.row(i);
}
cout<<M<<endl<<endl;
return 0;
}
Hope that somebody can suggest a faster way. Thanks in advance.
You can just take V.t()*V
(It took me a minute to realize it too, but if you go through the matrix multiplication you'll see it's the same)

Counting black pixels

I am using older version of C because the book I am using is outdated :( Currently, I am working on a project to detect an object in an image. First I do Gaussian smoothing on the gray scale image, then erode it. After that, I apply threshold. Now I am trying to obtain how many black pixels there are for every width so that I can compare it with other row to determine the center. I am trying this in 'for' loop, however, I am keep getting the error:
term does not evaluate to a function taking 1 arguments
#include <highgui.h>
#include <cv.h>
#include <cxcore.h>
int main()
{
int total,
zero,
width,
blackpixel;
IplImage* in = cvLoadImage("Wallet.jpg", CV_LOAD_IMAGE_GRAYSCALE);
IplImage* gsmooth = cvCreateImage(cvGetSize(in), IPL_DEPTH_8U, 1);
IplImage* erode = cvCreateImage(cvGetSize(in), IPL_DEPTH_8U, 1);
IplImage* Iat = cvCreateImage(cvGetSize(in), IPL_DEPTH_8U, 1);
IplImage* bpixel = cvCreateImage(cvGetSize(in), IPL_DEPTH_8U, 1);
cvSmooth(in, gsmooth, CV_GAUSSIAN, 3, 0, 0, 0);
cvErode(gsmooth, erode, NULL, 2);
cvThreshold(erode, Iat, 100, 255, CV_THRESH_BINARY);
total = (Iat->height)*(Iat->width);
zero = total - cvCountNonZero(Iat);
printf("Total pixels: %d\nWhite pixels: %d\nBlack pixels: %d\n", total, cvCountNonZero(Iat), zero);
for(int i = 0; i < Iat->width; i++)
{
blackpixel = Iat->width(i);
}
cvNamedWindow("Original", 1);
cvNamedWindow("Gaussian Smoothing", 1);
cvNamedWindow("Erode", 1);
cvNamedWindow("Adaptive Threshold", 1);
cvShowImage("Original", in);
cvShowImage("Gaussian Smoothing", gsmooth);
cvShowImage("Erode", erode);
cvShowImage("Adaptive Threshold", Iat);
cvWaitKey(0);
cvReleaseImage(&in);
cvReleaseImage(&gsmooth);
cvReleaseImage(&erode);
cvReleaseImage(&Iat);
cvDestroyWindow("Original");
cvDestroyWindow("Gaussian Smoothing");
cvDestroyWindow("Erode");
cvDestroyWindow("Adaptive Threshold");
}
First of all, don't be afraid to use C++ API when using an outdated book like "Learining OpenCV", because the concepts are still relevant. Translating to C++ API is not hard if You understand the idea, and is a great exercise because You can't just copy-paste the code. I learned OpenCV this way, and I think it worked :).
With C++ API it would be as simple as
cv::Mat zeros = cv::Mat::zeros(Iat.size());
cv::Mat blackPixels = (Iat == zeros);
int blackPixelsCount = blackPixels.total();
The problem in the line
blackpixel = Iat->width(i);
is the wrong syntax.
Iat->width will give you the width of the image, an integer property.
I don't thing that the loop
for(int i = 0; i < Iat->height; i++)
{
blackpixel = Iat->width(i);
}
can calculate the number of black pixels in a given row. You might need something like
for(int i = 0; i < Iat->height; i++) // // every row
{
for(int j = 0; j < Iat->width; j++) // pixels in each row
{
// get count pixels here
}
// do things with the count for the current row
}
If you are using a cvMat data structure instead of IplImage, this should be faster.

Input matrix to opencv kmeans clustering

This question is specific to opencv:
The kmeans example given in the opencv documentation has a 2-channel matrix - one channel for each dimension of the feature vector. But, some of the other example seem to say that it should be a one channel matrix with features along the columns with one row for each sample. Which of these is right?
if I have a 5 dimensional feature vector, what should be the input matrix that I use:
This one:
cv::Mat inputSamples(numSamples, 1, CV32FC(numFeatures))
or this one:
cv::Mat inputSamples(numSamples, numFeatures, CV_32F)
The correct answer is cv::Mat inputSamples(numSamples, numFeatures, CV_32F).
The OpenCV Documentation about kmeans says:
samples – Floating-point matrix of input samples, one row per sample
So it is not a Floating-point vector of n-Dimensional floats as in the other option. Which examples suggested such a behaviour?
Here is also a small example by me that shows how kmeans can be used. It clusters the pixels of an image and displays the result:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace cv;
int main( int argc, char** argv )
{
Mat src = imread( argv[1], 1 );
Mat samples(src.rows * src.cols, 3, CV_32F);
for( int y = 0; y < src.rows; y++ )
for( int x = 0; x < src.cols; x++ )
for( int z = 0; z < 3; z++)
samples.at<float>(y + x*src.rows, z) = src.at<Vec3b>(y,x)[z];
int clusterCount = 15;
Mat labels;
int attempts = 5;
Mat centers;
kmeans(samples, clusterCount, labels, TermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 10000, 0.0001), attempts, KMEANS_PP_CENTERS, centers );
Mat new_image( src.size(), src.type() );
for( int y = 0; y < src.rows; y++ )
for( int x = 0; x < src.cols; x++ )
{
int cluster_idx = labels.at<int>(y + x*src.rows,0);
new_image.at<Vec3b>(y,x)[0] = centers.at<float>(cluster_idx, 0);
new_image.at<Vec3b>(y,x)[1] = centers.at<float>(cluster_idx, 1);
new_image.at<Vec3b>(y,x)[2] = centers.at<float>(cluster_idx, 2);
}
imshow( "clustered image", new_image );
waitKey( 0 );
}
As alternative to reshaping the input matrix manually, you can use OpenCV reshape function to achieve similar result with less code. Here is my working implementation of reducing colors count with K-Means method (in Java):
private final static int MAX_ITER = 10;
private final static int CLUSTERS = 16;
public static Mat colorMapKMeans(Mat img, int K, int maxIterations) {
Mat m = img.reshape(1, img.rows() * img.cols());
m.convertTo(m, CvType.CV_32F);
Mat bestLabels = new Mat(m.rows(), 1, CvType.CV_8U);
Mat centroids = new Mat(K, 1, CvType.CV_32F);
Core.kmeans(m, K, bestLabels,
new TermCriteria(TermCriteria.COUNT | TermCriteria.EPS, maxIterations, 1E-5),
1, Core.KMEANS_RANDOM_CENTERS, centroids);
List<Integer> idx = new ArrayList<>(m.rows());
Converters.Mat_to_vector_int(bestLabels, idx);
Mat imgMapped = new Mat(m.size(), m.type());
for(int i = 0; i < idx.size(); i++) {
Mat row = imgMapped.row(i);
centroids.row(idx.get(i)).copyTo(row);
}
return imgMapped.reshape(3, img.rows());
}
public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
Highgui.imwrite("result.png",
colorMapKMeans(Highgui.imread(args[0], Highgui.CV_LOAD_IMAGE_COLOR),
CLUSTERS, MAX_ITER));
}
OpenCV reads image into 2 dimensional, 3 channel matrix. First call to reshape - img.reshape(1, img.rows() * img.cols()); - essentially unrolls 3 channels into columns. In resulting matrix one row corresponds to one pixel of the input image, and 3 columns corresponds to RGB components.
After K-Means algorithm finished its work, and color mapping has been applied, we call reshape again - imgMapped.reshape(3, img.rows()), but now rolling columns back into channels, and reducing row numbers to the original image row number, thus getting back the original matrix format, but only with reduced colors.

Resources