Incorrect Pattern Image Generation in OpenCV - c++

Content: Image Processing in OpenCV C++.
The Requirement is to create tiles of Mat pattern of size 256 X 256 on an outer Mat Image. The user specifies the width and the height of the outer Mat Image.
To do this, I created the below OpenCV C++ function:
Mat GenerateDiagonalFade(int width, int height)
{
// Creating a Mat Image in user defined dimension
Mat image(height, width, CV_8UC1, Scalar(0));
//Looping through all rows and columns of the outer Image
for (int row = 0; row < image.rows; row ++)
{
for (int col = 0; col < image.cols; col ++)
{
//Here, I am giving the condition to access the pixel values
//The pattern should be 255 X 255 and they must fill in the entire image
if ((row % 256 + col % 256) <= 255)
{
image.at<uchar>(row, col) = (row % 256 + col % 256);
}
else
{
//Here is where I get error
image.at<uchar>(row, col) = abs(row % 256 - col % 256);
}
}
}
return image;
}
If you can see the else statement above, I tried to make the inverse of the first condition and make the value absolute.
The output I get is as seen below:
The Expected Output is the inverse of the first part of the diagonal. Darker to lighter shade towards the diagonal.
I tried replacing abs(row % 256 - col % 256); with many statements. I am struct with the output.
The changes should be made only in the else statement. Rest of my code is correct as half of my output( top diagonal) is right.
I appreciate any help from you in order to solve this. Trust me, it's quite interesting to work out all graphical[X-Y axis] and mathematical calculations[pixel access] to get the desired output.

I would begin by splitting the problem into two parts:
Generating a single tile containing the correct pattern
Using that tile (or algorithm) to generate the whole image
Generating a Tile
The goal is to generate a 256x256 grayscale image containing a gradient such that:
Top left corner is all black
Bottom right corner is all black
The diagonal going from bottom left to top right is all white
You got the part above the diagonal right, but let's inspect that anyway.
The coordinates of the top left corner are (0, 0) and we expect intensity of 0. --> row + col == 0
The coordinates of one end of the diagonal are (255, 0) and we expect intensity of 255. --> row + col == 255
The other end of the diagonal is at (0, 255) -> row + col == 255
Let's try another point on the diagonal, (254,1) --> again row + col == 255
OK, now a point just above the diagonal, (254,0) -> row + col == 254 -- slightly less white, as we would expect.
Next, let's try a point just below the diagonal, say (255, 1) --> row + col == 256. If we cast this to an 8 bit integer, we get a 0, yet we expect 254, just like in the previous case.
Finally, bottom right corner (255, 255) -> row + col == 510. If we cast this to an 8 bit integer, we get a 254, yet we expect 0.
Let's try something:
256 + 254 == 510
510 + 0 == 510
And we see an algorithm:
* If the sum of row + col is less than 256, then use the sum
* Otherwise subtract the sum from 510 and use the result
Sample code:
cv::Mat make_tile()
{
int32_t const TILE_SIZE(256);
cv::Mat image(TILE_SIZE, TILE_SIZE, CV_8UC1);
for (int32_t r(0); r < TILE_SIZE; ++r) {
for (int32_t c(0); c < TILE_SIZE; ++c) {
int32_t sum(r + c);
if (sum < TILE_SIZE) {
image.at<uint8_t>(r, c) = static_cast<uint8_t>(sum);
} else {
image.at<uint8_t>(r, c) = static_cast<uint8_t>(2 * (TILE_SIZE - 1) - sum);
}
}
}
return image;
}
Single tile:
Generating Image of Tiles
Now that we have a complete tile, we can simply generate the full image by iterating over tile-sized ROIs of the target image, and copying a tile ROI of identical size to them.
Sample code:
#include <opencv2/opencv.hpp>
#include <cstdint>
cv::Mat make_tile()
{
int32_t const TILE_SIZE(256);
cv::Mat image(TILE_SIZE, TILE_SIZE, CV_8UC1);
for (int32_t r(0); r < TILE_SIZE; ++r) {
for (int32_t c(0); c < TILE_SIZE; ++c) {
int32_t sum(r + c);
if (sum < TILE_SIZE) {
image.at<uint8_t>(r, c) = static_cast<uint8_t>(sum);
} else {
image.at<uint8_t>(r, c) = static_cast<uint8_t>(2 * (TILE_SIZE - 1) - sum);
}
}
}
return image;
}
int main()
{
cv::Mat tile(make_tile());
cv::Mat result(600, 800, CV_8UC1);
for (int32_t r(0); r < result.rows; r += tile.rows) {
for (int32_t c(0); c < result.cols; c += tile.cols) {
// Handle incomplete tiles
int32_t end_r(std::min(r + tile.rows, result.rows));
int32_t end_c(std::min(c + tile.cols, result.cols));
// Get current target tile ROI and source ROI of same size
cv::Mat target_roi(result(cv::Range(r, end_r), cv::Range(c, end_c)));
cv::Mat source_roi(tile(cv::Range(0, target_roi.rows), cv::Range(0, target_roi.cols)));
// Copy the tile
source_roi.copyTo(target_roi);
}
}
cv::imwrite("gradient.png", tile);
cv::imwrite("gradient_big.png", result);
}
Complete image:

Related

Iterate over regions and take mean pixel value in OpenCV?

So Im basically trying to divide up a gray scale image (in this case 32x32) by resizing the initial image.
Once the "regions" are divided up, I need to take the mean pixel value of each one and then add to a string a 1, 0, or X. For example: "Region (3, 19) has a mean value of 21 so that's a 1".
I think I have most of the logic down but shouldn't, in theory, the output recreate the image in the form of 1s, 0s, and Xs? I feel like my math is wrong on the for loops maybe? Remember, all Im trying to do is break the image up into an MxN table or grid and taking the mean, 0 channel value of each grid region.
Here is my code:
Mat image = imread("blackdot.jpg", IMREAD_GRAYSCALE); //Pass in image
imshow("Gray scaled image", image); //imshow("Passed in gray scale image", image);
Mat resizedImage; // New Mat for saving the blown up image
resize(image, resizedImage, Size(3200, 3200)); // Blow up image so it's divisible by 32 using passed in image
string bitStream; // Ternary bitstream
for (int y = 0; y<resizedImage.cols - 32; y += 32) {
for (int x = 0; x<resizedImage.rows - 32; x += 32) {
// get the average for the whole 32x32 block
Rect roi(x, y, 32, 32);
Scalar mean, dev;
meanStdDev(resizedImage(roi), mean, dev); // mean[0] is the mean of the first channel, gray scale value;
if (mean[0] >= 0 && mean[0] <= 25) {
if ((counter % 3200) == 2900) {
bitStream += "1\n";
counter = 0;
}
else {
bitStream += "1";
}
else if (mean[0] >= 77 && mean[0] <= 153) {
if ((counter % 3200) == 2900) {
bitStream += "X\n";
counter = 0;
}
else {
bitStream += "X";
}
else {
if ((counter % 3200) == 2900) {
bitStream += "0\n";
counter = 0;
}
else {
bitStream += "0";
}
}
}
cout << bitStream;
blackdot.jpg
The code and logic looks good, for each pixel distribution, add a corresponding character to the bitstream and repeat that for all pixels in the row and for every column in the image.
When adding characters to the bitstream, try appending \n to the bitstream when a newline is reached (ie. when a row has been completed), to account for, in the bitstream, the alignment of the image. This equates to this minor adjustment in your code:
for (int y = 0; y<resizedImage.cols - 32; y += 32) {
for (int x = 0; x<resizedImage.rows - 32; x += 32) {
// get the average for the whole 32x32 block
Rect roi(x, y, 32, 32);
Scalar mean, dev;
meanStdDev(resizedImage(roi), mean, dev); // mean[0] is the mean of the first channel, gray scale value;
if (mean[0] >= 0 && mean[0] <= 25) {
bitStream += "1";
}
else if (mean[0] >= 77 && mean[0] <= 153) {
bitStream += "X";
}
else {
bitStream += "0";
}
}
//after each row has been checked, add newline
bitStream += '\n';
}
Note: The output window may need maximizing to see correct results.

ImageMagick C++ Version 7 Modify Pixel Value in Blank Image

I have the following code that creates a blank black image and then attempts to write to that image by modifying each pixel to red.
Magick::Image image(Magick::Geometry(1024, 1024),
Magick::Color(std::uint8_t(0), std::uint8_t(0), std::uint8_t(0)));
assert(image.channels() == 3 && "Created wrong image format.");
image.type(Magick::TrueColorType);
image.fillColor("black");
std::size_t w = image.columns();
std::size_t h = image.rows();
assert(image.columns() == 1024 && image.rows() == 1024);
Magick::Quantum *mpixels = image.setPixels(0, 0, w, h);
for (int row = 0; row < h - 1; ++row) {
for (int col = 0; col < w - 1; ++col) {
std::size_t offset = (w * row + col);
std::size_t moffset = image.channels() * offset;
mpixels[moffset + 0] = 255;
mpixels[moffset + 1] = 0;
mpixels[moffset + 2] = 0;
}
}
image.syncPixels();
image.write(out.c_str());
However, after inspecting the image it is still all black after changing the pixel values. What do I need to change to modify the pixel values?
I suspect that you are using the Q16 version of ImageMagick which means that each pixel channel value will be in the range 0-65535 and you are using 255 for the red channel which is really close to black. I think the following will fix your issue:
mpixels[moffset + 0] = 65535;
You could also decide to switch to the Q8 version of ImageMagick if channels in the range 0-255 would be sufficient for you.

How to find the pixel value that corresponds to a specific number of pixels?

Assume that I have a grayscale image in OpenCV.
I want to find a value so that 5% of pixels in the images have a value greater than it.
I can iterate over pixels and find number of pixels with the same value and then from the result find the value that %5 of pixel are above my value, but I am looking for a faster way to do this. Is there any such technique in OpenCV?
I think histogram would help, but I am not sure how I can use it.
You need to:
Compute the cumulative histogram of your pixel values
Find the bin whose value is greater than 95% (100 - 5) of the total number of pixels.
Given an image uniformly random generated, you get an histogram like:
and the cumulative histogram like (you need to find the first bin whose value is over the blue line):
Then you need to find the proper bin. You can use std::lower_bound function to find the correct value, and std::distance to find the corresponding bin number (aka the value you want to find). (Please note that with lower_bound you'll find the element whose value is greater or equal to the given value. You can use upper_bound to find the element whose value is strictly greater then the given value)
In this case it results to be 242, which make sense for an uniform distribution from 0 to 255, since 255*0.95 = 242.25.
Check the full code:
#include <opencv2\opencv.hpp>
#include <vector>
#include <algorithm>
using namespace std;
using namespace cv;
void drawHist(const vector<int>& data, Mat3b& dst, int binSize = 3, int height = 0, int ref_value = -1)
{
int max_value = *max_element(data.begin(), data.end());
int rows = 0;
int cols = 0;
float scale = 1;
if (height == 0) {
rows = max_value + 10;
}
else {
rows = height;
scale = float(height) / (max_value + 10);
}
cols = data.size() * binSize;
dst = Mat3b(rows, cols, Vec3b(0, 0, 0));
for (int i = 0; i < data.size(); ++i)
{
int h = rows - int(scale * data[i]);
rectangle(dst, Point(i*binSize, h), Point((i + 1)*binSize - 1, rows), (i % 2) ? Scalar(0, 100, 255) : Scalar(0, 0, 255), CV_FILLED);
}
if (ref_value >= 0)
{
int h = rows - int(scale * ref_value);
line(dst, Point(0, h), Point(cols, h), Scalar(255,0,0));
}
}
int main()
{
Mat1b src(100, 100);
randu(src, Scalar(0), Scalar(255));
int percent = 5; // percent % of pixel values are above a val
int val; // I need to find this value
int n = src.rows * src.cols; // Total number of pixels
int th = cvRound((100 - percent) / 100.f * n); // Number of pixels below val
// Histogram
vector<int> hist(256, 0);
for (int r = 0; r < src.rows; ++r) {
for (int c = 0; c < src.cols; ++c) {
hist[src(r, c)]++;
}
}
// Cumulative histogram
vector<int> cum = hist;
for (int i = 1; i < hist.size(); ++i) {
cum[i] = cum[i - 1] + hist[i];
}
// lower_bound returns an iterator pointing to the first element
// that is not less than (i.e. greater or equal to) th.
val = distance(cum.begin(), lower_bound(cum.begin(), cum.end(), th));
// Plot histograms
Mat3b plotHist, plotCum;
drawHist(hist, plotHist, 3, 300);
drawHist(cum, plotCum, 3, 300, *lower_bound(cum.begin(), cum.end(), th));
cout << "Value: " << val;
imshow("Hist", plotHist);
imshow("Cum", plotCum);
waitKey();
return 0;
}
Note
The histogram drawing function is an upgrade from a former version I posted here
You can use calcHist to compute the histograms, but I personally find easier to use the aforementioned method for 1D histograms.
1) Determine the height and the width of the image, h and w.
2) Determine what 5% of the total number of pixels is (X)...
X = int(h * w * 0.05)
3) Start at the brightest bin in the histogram. Set total T = 0.
4) Add the number of pixels in this bin to your total T. If T is greater than X, you are finished and the value you want is the lower limit of the range of the current histogram bin.
3) Move to the next darker bin in your histogram. Goto 4.

How to evenly distribute numbers 0 to n into m different containers

I am trying to write an algorithm for a program to draw an even, vertical gradient across an image. I.e. I want change the pixel color from 0 to 255 along the m rows of an image, but cannot find a good generic algorithm to do so.
I've tried to implement something like this using opencv, but it does not seem to work
#include <opencv2/opencv.hpp>
int main(){
//Make the image white.
cv::Mat Img(w_height, w_width, CV_8U);
for (int y = 0; y < Img.rows; y += 1) {
for (int x = 0; x < Img.cols; x++) {
Img.at<uchar>(y, x) = 255;//White
}
}
// try to create an even, vertical gradient
for(int row = 0; row < Img.rows; row++ ){
for(int col = 0; col < Img.cols; col++){
Img.at<uchar>(row, col) = col % 256;
}
}
cv::imshow("Window", Img);
cv::waitKey(0);
return 0;
}
Solving this problem requires the knowledge of three simple tricks:
1. Interpolation:
The process of gradually changing from one value to another is called interpolation. There are multiple ways of interpolating color values: the simplest one is to interpolate each component linearly, i.e. in the form of:
interpolated = start * (1-t) + dest * t.
Where
start is the value you are interpolating from towards the value dest.
t denotes how close the interpolated value should be to the destination value dest on a scale of 0 to 1 with 0 being the pure start color and 1 being the pure dest color.
You will find that linear interpolation in the RGB color space doesn't produce natural color paths. As an advanced step, you could utilise the HSV color space instead. See this question for further information about color interpolation.
2. Discretisation:
Unfortunately, interpolation produces real numbers. Thus, we have to discretise them to be able to use them as integer color values. The best way to do this is to round to the nearest integer by using e.g. round() in C++.
3. Finding the interpolation point:
Now, we just need a real-valued interpolation point t at each row of our image. We can deduce a formula for this by analysing what output we want to see:
For the bottommost row (row 1) we want to have t == 0 since that is where we want our pure start color to appear.
For the topmost row (row m) we want to have t == 1 since that is where we want the pure destination color to appear.
For every other row we want t to scale linearly with the distance to the bottommost row.
A formula to achieve this result is:
t = rowIndex / m
The approach can readily be adapted to other gradient directions by changing this formula appropriately.
Sample code (using linear interpolation, C++):
#include <algorithm>
#include <cmath>
Color interpolateRGB(Color from, Color to, float t)
{
// Clamp __t__ to range [0,1]
t = std::max(std::min(0.f, t), 1.f);
// Interpolate each RGB component
uint8_t r = std::roundf(from.r * (1-t) + to.r * t);
uint8_t g = std::roundf(from.g * (1-t) + to.g * t);
uint8_t b = std::roundf(from.b * (1-t) + to.b * t);
return Color(r, g, b);
}
void fillWithGradient(Image& img, Color from, Color to)
{
for(size_t row = 0; row < img.numRows(); ++row)
{
Color value = interpolateRGB(from, to, row / (img.numRows()-1));
// Set all pixels of this row to __value__
for(size_t col = 0; col < img.numCols(); ++col)
{
img.setPixel(row, col, value);
}
}
}
The basic idea would be to use the remainder of the division r of n/(m-1) and adding it to n on each iteration:
#include <iostream>
#include <vector>
using namespace std;
vector<int> gradient( int n, int m ) {
div_t q { 0, 0 };
vector<int> grad(m);
for( int i=1 ; i<m ; ++i ) {
q = div( n + q.rem, m-1 );
grad[i] = grad[i-1] + q.quot;
}
return grad;
}
int main() {
for( int i : gradient(255,10) ) cout << i << ' ';
cout << '\n';
return 0;
}
Output:
0 28 56 85 113 141 170 198 226 255

How to detect white blobs using OpenCV

I paint a picture to test:
And I want to know how much blobs I have in the black circle and what is the size of each blobs (all blobs are ~white).
For example, in this case I have 12 spots:
I know how to found white pixels and it easy to verify sequence from left:
int whitePixels = 0;
for (int i = 0; i < height; ++i)
{
uchar * pixel = image.ptr<uchar>(i);
for (int j = 0; j < width; ++j)
{
if (j>0 && pixel[j-1]==0) // to group pixels for one spot
whitePixels++;
}
}
but it's clear that this code is not good enough (blobs can be diagonally, etc.).
So, the bottom line, I need help: how can I define the blobs?
Thank you
Following code finds bounding rects (blobs) for all white spots.
Remark: if we can assume white spots are really white (namely have values 255 in grayscaled image), you can use this snippet. Consider putting it in some class to avoid passing uncecessary params to function Traverse. Although it works. The idea is based on DFS. Apart from the gryscaled image, we have ids matrix to assign and remember which pixel belongs to which blob (all pixels having the same id belong to the same blob).
void Traverse(int xs, int ys, cv::Mat &ids,cv::Mat &image, int blobID, cv::Point &leftTop, cv::Point &rightBottom) {
std::stack<cv::Point> S;
S.push(cv::Point(xs,ys));
while (!S.empty()) {
cv::Point u = S.top();
S.pop();
int x = u.x;
int y = u.y;
if (image.at<unsigned char>(y,x) == 0 || ids.at<unsigned char>(y,x) > 0)
continue;
ids.at<unsigned char>(y,x) = blobID;
if (x < leftTop.x)
leftTop.x = x;
if (x > rightBottom.x)
rightBottom.x = x;
if (y < leftTop.y)
leftTop.y = y;
if (y > rightBottom.y)
rightBottom.y = y;
if (x > 0)
S.push(cv::Point(x-1,y));
if (x < ids.cols-1)
S.push(cv::Point(x+1,y));
if (y > 0)
S.push(cv::Point(x,y-1));
if (y < ids.rows-1)
S.push(cv::Point(x,y+1));
}
}
int FindBlobs(cv::Mat &image, std::vector<cv::Rect> &out, float minArea) {
cv::Mat ids = cv::Mat::zeros(image.rows, image.cols,CV_8UC1);
cv::Mat thresholded;
cv::cvtColor(image, thresholded, CV_RGB2GRAY);
const int thresholdLevel = 130;
cv::threshold(thresholded, thresholded, thresholdLevel, 255, CV_THRESH_BINARY);
int blobId = 1;
for (int x = 0;x<ids.cols;x++)
for (int y=0;y<ids.rows;y++){
if (thresholded.at<unsigned char>(y,x) > 0 && ids.at<unsigned char>(y,x) == 0) {
cv::Point leftTop(ids.cols-1, ids.rows-1), rightBottom(0,0);
Traverse(x,y,ids, thresholded,blobId++, leftTop, rightBottom);
cv::Rect r(leftTop, rightBottom);
if (r.area() > minArea)
out.push_back(r);
}
}
return blobId;
}
EDIT: I fixed a bug, lowered threshold level and now the output is given below. I think it is a good start point.
EDIT2: I get rid of recursion in Traverse(). In bigger images recursion caused Stackoverflow.