I have to dividing the irregular shape into decreasing rectangles, one the largest and then decreasing rectangles. I just wondering if such problem is known in the coding world? How to do it?
The drawing is showing what I would like to achieve:
First rectangle can be found with this piece of code taken from here
Rect findMinRect(const Mat1b& src)
{
Mat1f W(src.rows, src.cols, float(0));
Mat1f H(src.rows, src.cols, float(0));
Rect maxRect(0, 0, 0, 0);
float maxArea = 0.f;
for (int r = 0; r < src.rows; ++r)
{
for (int c = 0; c < src.cols; ++c)
{
if (src(r, c) == 0)
{
H(r, c) = 1.f + ((r > 0) ? H(r - 1, c) : 0);
W(r, c) = 1.f + ((c > 0) ? W(r, c - 1) : 0);
}
float minw = W(r, c);
for (int h = 0; h < H(r, c); ++h)
{
minw = min(minw, W(r - h, c));
float area = (h + 1) * minw;
if (area > maxArea)
{
maxArea = area;
maxRect = Rect(Point(c - minw + 1, r - h), Point(c + 1, r + 1));
}
}
}
}
return maxRect;
}
Usage is simple
Mat src, src_gray, src_bin;
cv::cvtColor(src, src_gray, COLOR_BGR2GRAY);
src_bin = (src_gray > 1) * 255;
Rect box = findMinRect(~src_bin);
rectangle(src, box, Scalar(0, 0, 255), 2);
cv::imshow("with_rectangle", src);
cv::waitKey();
But it's only first iteration. In next iteration we have to deal with at least 3 original image pieces, then with 9 pieces, and so on. I thing so... I will be grateful for any further suggestions.
I took Paul Gray's answer and wrote an iterative process that consumes the source to find all the areas from largest to smallest in pure c++, the filled portions of the matrix do not need to be a contiguous shape.
This is the rect class that I used, I found out after that there is already a RECT class in windef.h but whatever I had already made my own at that point.
class rect
{
public:
uint32_t rowStart;
uint32_t colStart;
uint32_t height;
uint32_t width;
rect(uint32_t _rowStart, uint32_t _colStart, uint32_t _height, uint32_t _width);
rect();
};
this is the algorithm Paul Gray posted in their answer redone without using openCV
bool findMinRect(std::vector<std::vector<bool>> *src, rect *retItem)
{
bool contFlg = false;
uint32_t srcRows = src->size();
uint32_t srcCols = (*src)[0].size();
std::vector<std::vector<float>> W;
W.resize(srcRows);
for (int row = 0; row < srcRows; row++)
{
for (int col = 0; col < srcCols; col++)
{
W[row].push_back(0.f);
}
}
std::vector<std::vector<float>> H;
H.resize(srcRows);
for (int row = 0; row < srcRows; row++)
{
for (int col = 0; col < srcCols; col++)
{
H[row].push_back(0.f);
}
}
rect maxRect(0, 0, 0, 0);
float maxArea = 0.f;
for (int r = 0; r < srcRows; ++r)
{
for (int c = 0; c < srcCols; ++c)
{
if ((*src)[r][c] == true)
{
H[r][c] = 1.f + ((r > 0) ? H[r-1][c] : 0);
W[r][c] = 1.f + ((c > 0) ? W[r][c-1] : 0);
}
float minw = W[r][c];
for (int h = 0; h < H[r][c]; ++h)
{
minw = min(minw, W[r - h][c]);
float area = (h + 1) * minw;
if (area > maxArea)
{
maxArea = area;
maxRect = rect(r - h, c - minw + 1, (r + 1 - (r - h)), (c + 1 - (c - minw + 1)));
contFlg = true;
}
}
}
}
*retItem = maxRect;
return contFlg;
}
The shapeChk method below is a sanity check for the source matrix to make sure is rectangular, if not it identifies where the issue is.
int shapeChk(std::vector<std::vector<bool>> *src)
{
uint32_t srcRows = src->size();
if (srcRows == 0)
{
return -1;
}
uint32_t srcCols = (*src)[0].size();
if (srcCols == 0)
{
return -2;
}
for (int row = 0; row < srcRows; row++)
{
if ((*src)[row].size() != srcCols)
{
return (-4 - (row + 1));
}
}
return 0;
}
The following method removes the area found from the source matrix in order to rerun the findMinRect method.
void clearclump(std::vector<std::vector<bool>> *src, rect *area)
{
for (int r = (area->rowStart); r < (area->rowStart + area->height); r++)
{
for (int c = (area->colStart); c < (area->colStart + area->width); c++)
{
(*src)[r][c] = false;
}
}
}
And here is the main method that puts it all together.
int areaClump(std::vector<std::vector<bool>> *src, std::vector<rect> *areas)
{
rect retItem(1, 0, 0, 0);
int err = shapeChk(src);
if (err != 0)
{
return err;
}
while (findMinRect(src, &retItem))
{
areas->push_back(retItem);
clearclump(src, &retItem);
}
return 0;
}
When you run areaClump the source matrix will be entirely consumed and will have all flase entries, while the areas vector contains all the found areas.
Not sure if this works for your purposes or if it's even the best way to handle it, but it worked for me so I thought I'd share the results, may be it'll help someone.
Related
I want to implement region growing algorithm for components Cr and Cb (YCbCr) (separate and combined) with manually chosen seed point (mouse click).
At the moment I have two functions that implement region growing for the H component in the HSV color space.
bool isOk(int new_x, int new_y, int width, int height)
{
if (new_x < 0 || new_y < 0 || new_x >= width || new_y >= height)
return false;
return true;
}
void lab04_MouseCallback(int event, int x, int y, int flags, void* param)
{
Mat* src = (Mat*)param;
int height = (*src).rows;
int width = (*src).cols;
if (event == CV_EVENT_LBUTTONDOWN)
{
printf("Seed point(x,y): %d,%d\n", x, y);
Mat labels = Mat::zeros((*src).size(), CV_16UC1);
int w = 3,
hue_avg = 0,
inf_x, sup_x,
inf_y, sup_y,
cnt = 0;
inf_x = (x - w < 0) ? 0 : x - w;
inf_y = (y - w < 0) ? 0 : y - w;
sup_x = (x + w >= width) ? (width - 1) : x + w;
sup_y = (y + w >= height) ? (height - 1) : y + w;
printf("inf x: %d sup x: %d --- inf y: %d sup y: %d\n", inf_x, sup_x, inf_y, sup_y);
for (int i = inf_y; i <= sup_y; ++i)
{
for (int j = inf_x; j <= sup_x; ++j)
{
hue_avg += (*src).data[i * width + j];
//printf("H at <%d, %d> is %d\n", i, j, (*src).data[i * width + j]);
}
}
hue_avg /= (sup_x - inf_x + 1) * (sup_y - inf_y + 1);
printf("Hue average: %d\n\n", hue_avg);
int k = 1, N = 1, hue_std = 10;
int konst = 3;
int T = konst * (float)hue_std;
queue<Point> Q;
Q.push(Point(x, y));
while (!Q.empty())
{
int dx[8] = { -1, 0, 1, 1, 1, 0, -1, -1 };
int dy[8] = { -1, -1, -1, 0, 1, 1, 1, 0 };
Point temp = Q.front();
Q.pop();
for (int dir = 0; dir < 8; ++dir)
{
int new_x = temp.x + dx[dir];
int new_y = temp.y + dy[dir];
if (isOk(new_x, new_y, width, height))
{
//printf("(%d, %d)\n", new_x, new_y);
if (labels.at<ushort>(new_y, new_x) == 0)
{
//printf("labels(%d, %d) = %hu\n", new_x, new_y, labels.at<ushort>(new_y, new_x));
if (abs((*src).at<uchar>(new_y, new_x) - hue_avg) < T)
{
//printf("this one\n");
Q.push(Point(new_x, new_y));
labels.at<ushort>(new_y, new_x) = k;
hue_avg = ((N * hue_avg) + (*src).at<uchar>(new_y, new_x)) / (N + 1);
++N;
}
}
}
}
}
Mat dst = (*src).clone();
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
if (labels.at<ushort>(i, j) == 1)
{
dst.at<uchar>(i, j) = 255;
}
else
{
dst.at<uchar>(i, j) = 0;
}
}
}
imshow("dst", dst);
}
}
void lab04_MouseClick()
{
Mat src;
Mat hsv;
// Read image from file
char fname[MAX_PATH];
while (openFileDlg(fname))
{
src = imread(fname);
int height = src.rows;
int width = src.cols;
//Create a window
namedWindow("My Window", 1);
// Aplicare FTJ gaussian pt. eliminare zgomote: essential sa il aplicati
GaussianBlur(src, src, Size(5, 5), 0, 0);
// Componenta de culoare Hue a modelului HSV
Mat H = Mat(height, width, CV_8UC1);
// definire pointeri la matricea (8 biti/pixeli) folosita la stocarea
// componentei individuale H
uchar* lpH = H.data;
cvtColor(src, hsv, CV_BGR2HSV); // conversie RGB -> HSV
// definire pointer la matricea (24 biti/pixeli) a imaginii HSV
uchar* hsvDataPtr = hsv.data;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
// index in matricea hsv (24 biti/pixel)
int hi = i * width * 3 + j * 3;
int gi = i * width + j; // index in matricea H (8 biti/pixel)
lpH[gi] = hsvDataPtr[hi] * 510 / 360; // lpH = 0 .. 255
}
}
//set the callback function for any mouse event
setMouseCallback("My Window", lab04_MouseCallback, &H);
//show the image
imshow("My Window", src);
// Wait until user press some key
waitKey(0);
}
}
How can I change this code to be for components Cr and Cb?
I am wanting to move through an image and take a 5x5 grid centered around each pixel in the image. I then want to sum that grid and compare it to a threshold.
int main()
{
Mat element = getStructuringElement(MORPH_RECT, Size(7, 7));
Mat im = imread("blob.png", IMREAD_GRAYSCALE);
bool fromCenter = false;
namedWindow("Crop frame", WINDOW_NORMAL);
Rect2d r = selectROI("Crop frame", im, fromCenter);
im = im(r);
erode(im, im, element);
Mat clone = im;
int sectionSize = 4;
int width = im.cols - sectionSize/2;
int height = im.rows - sectionSize/2;
int sum = 0;
int counter = 0;
for (int i = sectionSize/2; i < width; i++) {
for (int j = sectionSize/2; j < height; j++) {
Rect rect = Rect(i, j, sectionSize, sectionSize);
rect -= Point(rect.width / 2, rect.height / 2);
Mat temp = im(rect);
for (int x = 0; x < temp.cols; x++) {
for (int y = 0; y < temp.rows; y++) {
int pixelValue = (int)temp.at<uchar>(y, x);
sum += pixelValue;
}
}
cout << sum << endl;
if (sum > 3800) {
clone.at<uchar>(j, i) = 255;
}
else {
clone.at<uchar>(j, i) = 0;
}
namedWindow("erode", WINDOW_NORMAL);
imshow("erode", clone);
waitKey(1);
sum = 0;
}
}
}
I am getting fluctuations in the pixel sum based on where I select my ROI in the image even when both over white space Also, my pixel sum is changing when I change the value of the clone pixel in this section of the code which I do not understand at all:
if (sum > 3800) {
clone.at<uchar>(j, i) = 255;
}
else {
clone.at<uchar>(j, i) = 0;
}
I'd like to convert this existing color detection from red to a gray color. I grabbed the code from this project (Flame Detection System)
I have tried to implement my own algorithm but I think I'm no where near to what I'm trying to achieve. I get the algo from this link
Below is the original code fragment with slight modification:
void TargetExtractor::colorDetect(int redThreshold, double saturationThreshold) {
Mat temp;
GaussianBlur(mFrame, temp, Size(3, 3), 0);
uchar grayThreshold = 80;
for (int i = 0; i < temp.rows; i++) {
for (int j = 0; j < temp.cols; j++) {
if (mMask.at<uchar>(i, j) == 255) {
Vec3b& v = temp.at<Vec3b>(i, j);
uchar b = v[0];
uchar g = v[1];
uchar r = v[2];
//if (abs(r - g) < grayThreshold) {
// mMask.at<uchar>(i, j) = 0;
//}
double s = 1 - 3.0 * min(b, min(g, r)) / (b + g + r);
if (!(r > redThreshold && r >= g && g > b &&
s >= ((255 - r) * saturationThreshold / redThreshold))) {
mMask.at<uchar>(i, j) = 0;
}
}
}
}
}
The commented part is my attempt to detect gray regions but it certainly not working for me.
Detecting moving red objects from the original code:
Detecting moving gray objects:
Gray color has property of all 3 components being around about the same valued. You can check that all differences between all pairs of 3 color components are below the threshold:
if (abs(r - g) < grayThreshold && abs(r - b) < grayThreshold && abs(b - g) < grayThreshold) {
mMask.at<uchar>(i, j) = 0;
}
I have a OpenCV C++ application.
I have segmented an image with pyrMeanShiftFiltering function.
Now I need to count the pixel in a segment and the number of pixel having the most frequent value in the same segment in order to compute a ratio between them. How could I do that?
I am using Tsukuba image and the code is.
Mat image, segmented;
image = imread("TsukubaL.jpg", 1 );
pyrMeanShiftFiltering(image, segmented, 16, 32);
The segmented image is:
If I consider a pixel in a single segment, the part where I count the pixel in that segment is:
int cont=0;
Vec3b x = segmented.at<Vec3b>(160, 136);
for(int i = 160; i < segmented.rows; ++i) { //check right-down
for(int j = 136; j < segmented.cols; ++j) {
if(segmented.at<Vec3b>(i, j) == x)
cont++;
else
continue;
}
}
for(int i = 160; i > 0; --i) { //check right-up
for(int j = 136; j < segmented.cols; ++j) {
if(segmented.at<Vec3b>(i, j) == x)
cont++;
else
continue;
}
}
for(int i = 160; i < segmented.rows; ++i) { //check down-left
for(int j = 136; j > 0; --j) {
if(segmented.at<Vec3b>(i, j) == x)
cont++;
else
continue;
}
}
for(int i = 160; i > 0; --i) { //check up-left
for(int j = 136; j > 0; --j) {
if(segmented.at<Vec3b>(i, j) == x)
cont++;
else
continue;
}
}
cout<<"Pixel "<<x<<"cont = "<<cont<<endl;
In this example, I consider a white pixel in position (160, 136) and count the same pixel to the central one in the four direction starting from it, and the output is:
Pixel [206, 222, 240]cont = 127
Could it be a possible good way to do it?
First you need to define a mask with pixels having the same color of your initial point (called seed here). You can use inRange with a given tolerance. Assuming a seed on the head, you'll get something like:
Now you need to find the connected component that contains your seed. You can do this in many ways. Here I modified a generative labeling algorithm (the can be found here). You get the list of points of the blob that contains the seed. You can then make a mask with these points:
Now that you have all points it's trivial to find the number of points in the segment. To find the most frequent color you can make an histogram with the BGR values contained in the segment. Since an histogram with all RGB values will have 256*256*256 bins, it's more practical to use a map. I modified the code found here to make an histogram with a given mask.
Now you just need to find the color value with higher frequency.
For this example, I got:
# points in segment: 2860
Most frequent color: [209, 226, 244] #: 168
Take a look at the code:
#include <opencv2/opencv.hpp>
#include <vector>
#include <stack>
#include <map>
using namespace cv;
using namespace std;
vector<Point> connected_components(const Mat1b& img, Point seed)
{
Mat1b src = img > 0;
int label = 0;
int w = src.cols;
int h = src.rows;
int i;
cv::Point point;
// Start from seed
std::stack<int, std::vector<int>> stack2;
i = seed.x + seed.y*w;
stack2.push(i);
// Current component
std::vector<cv::Point> comp;
while (!stack2.empty())
{
i = stack2.top();
stack2.pop();
int x2 = i%w;
int y2 = i / w;
src(y2, x2) = 0;
point.x = x2;
point.y = y2;
comp.push_back(point);
// 4 connected
if (x2 > 0 && (src(y2, x2 - 1) != 0))
{
stack2.push(i - 1);
src(y2, x2 - 1) = 0;
}
if (y2 > 0 && (src(y2 - 1, x2) != 0))
{
stack2.push(i - w);
src(y2 - 1, x2) = 0;
}
if (y2 < h - 1 && (src(y2 + 1, x2) != 0))
{
stack2.push(i + w);
src(y2 + 1, x2) = 0;
}
if (x2 < w - 1 && (src(y2, x2 + 1) != 0))
{
stack2.push(i + 1);
src(y2, x2 + 1) = 0;
}
// 8 connected
if (x2 > 0 && y2 > 0 && (src(y2 - 1, x2 - 1) != 0))
{
stack2.push(i - w - 1);
src(y2 - 1, x2 - 1) = 0;
}
if (x2 > 0 && y2 < h - 1 && (src(y2 + 1, x2 - 1) != 0))
{
stack2.push(i + w - 1);
src(y2 + 1, x2 - 1) = 0;
}
if (x2 < w - 1 && y2>0 && (src(y2 - 1, x2 + 1) != 0))
{
stack2.push(i - w + 1);
src(y2 - 1, x2 + 1) = 0;
}
if (x2 < w - 1 && y2 < h - 1 && (src(y2 + 1, x2 + 1) != 0))
{
stack2.push(i + w + 1);
src(y2 + 1, x2 + 1) = 0;
}
}
return comp;
}
struct lessVec3b
{
bool operator()(const Vec3b& lhs, const Vec3b& rhs) {
return (lhs[0] != rhs[0]) ? (lhs[0] < rhs[0]) : ((lhs[1] != rhs[1]) ? (lhs[1] < rhs[1]) : (lhs[2] < rhs[2]));
}
};
map<Vec3b, int, lessVec3b> getPalette(const Mat3b& src, const Mat1b& mask)
{
map<Vec3b, int, lessVec3b> palette;
for (int r = 0; r < src.rows; ++r)
{
for (int c = 0; c < src.cols; ++c)
{
if (mask(r, c))
{
Vec3b color = src(r, c);
if (palette.count(color) == 0)
{
palette[color] = 1;
}
else
{
palette[color] = palette[color] + 1;
}
}
}
}
return palette;
}
int main()
{
// Read the image
Mat3b image = imread("tsukuba.jpg");
// Segment
Mat3b segmented;
pyrMeanShiftFiltering(image, segmented, 16, 32);
// Seed
Point seed(140, 160);
// Define a tolerance
Vec3b tol(10,10,10);
// Extract mask of pixels with same value as seed
Mat1b mask;
inRange(segmented, segmented(seed) - tol, segmented(seed) + tol, mask);
// Find the connected component containing the seed
vector<Point> pts = connected_components(mask, seed);
// Number of pixels in the segment
int n_of_pixels_in_segment = pts.size();
Mat1b mask_segment(image.rows, image.cols, uchar(0));
for (const auto& pt : pts)
{
mask_segment(pt) = uchar(255);
}
// Get palette
map<Vec3b, int, lessVec3b> palette = getPalette(segmented, mask_segment);
// Get most frequent color
Vec3b most_frequent_color;
int freq = 0;
for (const auto& pal : palette)
{
if (pal.second > freq)
{
most_frequent_color = pal.first;
freq = pal.second;
}
}
cout << "# points in segment: " << n_of_pixels_in_segment << endl;
cout << "Most frequent color: " << most_frequent_color << " \t#: " << freq << endl;
return 0;
}
After creating the required mask as shown in previous answer or by any other means, you can create a contour around the mask image. This will give allow you to directly count the number of pixels within segment by using contourArea function.
You can segment out the selected area into a new submat and calculate histogram on it get most frequent values. If you are concerned with color values only and not the intensity values, you should also convert your image into HSV, LAB, or YCbCr color space as per requirement.
So I have an image that I want to overlay with a checkerboard pattern.
This is what I have come up with so far:
for ( uint_8 nRow = 0; nRow < image.width(); ++nRow)
for (uint_8 nCol = 0; nCol < image.height(); ++nCol)
if(((nRow/20 + nCol/20) % 2) == 0)
memset(&image.data[nCol + nRow], 0, 1);
Produces a white image unfortunately. I dont think this is very performant because memset is called for every single pixel in the image instead of multiple.
Why does this code not produce a chckerboard pattern? How would you improve it?
For better performance, don't treat the image as a 2-dimensional entity. Instead, look at it as a 1D array of continuous data, where all lines of the image are arranged one after the other.
With this approach, you can write the pattern in one go with a single loop, where in every iteration you memset() multiple adjacent pixels and increase the index by twice the amount of pixels you set:
int data_size = image.width() * image.height();
for (auto it = image.data; it < image.data + data_size; it += 20) {
memset(it, 0, 20);
if (((it - data) + 40) % (20 * 400) == 0) {
it += 40;
} else if (((it - data) + 20) % (20 * 400) != 0) {
it += 20;
}
}
(Replace auto with the type of image.data if you're not using C++11; I suspect it's unsigned char*.)
This is quite friendly for the CPU cache prefetch. It's also friendly for the compiler, which can potentially vectorize and/or perform loop unrolling.
If you have an image's dimensions which are multiple of the checker square size :
(I coded in C but it is fairly easy to transpose to C++)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define uint unsigned int
#define WIDTH 40
#define HEIGHT 40
#define BLOCK_SIZE 5
void create_checker_row(uint* row, uint size_block, uint nb_col, uint offset )
{
uint ic;
for (ic = size_block*offset ; ic < nb_col; ic+= 2*size_block )
{
memset( (row + ic) , 0, size_block*sizeof(uint) );
}
}
int main()
{
uint ir,ic;
// image creation
uint* pixels = (uint*) malloc(WIDTH*HEIGHT*sizeof(uint));
for (ir = 0; ir < WIDTH; ir++)
{
for ( ic = 0; ic < HEIGHT; ic++)
{
// arbitrary numbers
pixels[ir*WIDTH + ic] = (ir*WIDTH + ic) % 57 ;
printf("%d,", pixels[ir*WIDTH + ic] );
}
printf("\n");
}
for (ir = 0; ir < WIDTH; ir++)
{
create_checker_row( pixels + ir*WIDTH , // pointer at the beggining of n-th row
BLOCK_SIZE , // horizontal length for square
WIDTH , // image width
(ir/BLOCK_SIZE) % 2 // offset to create the checker pattern
);
}
// validation
printf("\n");
printf("Validation \n");
printf("\n");
for (ir = 0; ir < WIDTH; ir++)
{
for ( ic = 0; ic < HEIGHT; ic++)
{
printf("%d,", pixels[ir*WIDTH + ic] );
}
printf("\n");
}
return 0;
}
Seems pretty checkered for me : http://ideone.com/gp9so6
I use this and stb_image_write.h
#include <stdlib.h>
#include <stb_image_write.h>
int main(int argc, char *argv[])
{
const int w = 256, h = 256, ch = 4, segments = 8, box_sz = w / segments;
unsigned char rgba_fg[4] = {255, 255, 0, 255}; //yellow
unsigned char rgba_bg[4] = {255, 0, 0, 255}; //red
unsigned char* data = calloc(w * h * ch, sizeof(unsigned char));
int swap = 0;
int fill = 0; /* set to 1 to fill fg first*/
unsigned char* col = NULL;
for(int i = 0; i < w * h; i++)
{
if(i % (w * box_sz) == 0 && i != 0)
swap = !swap;
if(i % box_sz == 0 && i != 0)
fill = !fill;
if(fill)
{
if(swap)
col = rgba_bg;
else
col = rgba_fg;
}else
{
if(swap)
col = rgba_fg;
else
col = rgba_bg;
}
for(int j = 0; j < ch; j++)
{
data[i*ch + j] = col[j];
}
}
stbi_write_png("checker.png", w, h, ch, data, 0);
free(data);
return 0;
}
Its a bit slow with large images but gets the job done if you cache them