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;
}
Related
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.
I'm attempting to use opencv2 to preprocess and display images for classification but the pixels do not seem to be formatting properly, I'm not sure if formatting is the proper term. The image was originally 1080p and I used ffmpeg to crop and scale to 480X800. I was getting weird results so testing the program using the following code to overlay a simple checkerboard pattern where all squares should be the same size and square -
std::string image_path = samples::findFile("/home/pi/test.jpg");
Mat img = imread(image_path, IMREAD_COLOR);
cv::cvtColor(img, img, COLOR_BGR2RGB);
for (int i = 0; i < 15 ; i++ ) {
for (int j = 0; j < 25 ; j++ ) {
int x;
if ((i + j) % 2 == 0) x = 1;
else x = 0;
for (int a = i * 32; a < (i + 1) * 32 ; a++) {
for (int b = j * 32; b < (j + 1) * 32 ; b++) {
img.at<int>(a, b) = x * img.at<int>(a, b);
}
}
}
}
I get the following
checkerboard_test
the original image looks exactly like it should without any stretching or other issues. This is being displayed on a small touch screen attached to a raspberry pi. Any help would be greatly appreciated.
I figured it out, code should be -
img.at<Vec3b>(a, b) = x * img.at<Vec3b>(a, b);
instead of
img.at<int>(a, b) = x * img.at<int>(a, b);
I'm trying to get this output b&w positives and negatives and gray neutrals
But instead I'm getting this B&W only
I'm not sure what to change about my code.
Mat sobelX(const cv::Mat &m){
Mat im = m.clone();
int gx, mag;
for (int i = 0; i < m.rows; i++){
for (int j = 0; j < m.cols;
//find x gradient
gx = m.at<uchar>(i-1, j-1)
+ 2*m.at<uchar>(i, j-1)
+ m.at<uchar>(i+1, j-1)
- m.at<uchar>(i-1, j+1)
- 2*m.at<uchar>(i, j+1)
- m.at<uchar>(i+1, j+1);
mag = sqrt(gx*gx);
if (mag < 0) {
mag = 0; //set to black
}
else if (mag > 255) {
mag = 255; //set to white
}
im.at<uchar>(i, j) = mag;
}
}
return im;
}
Thanks.
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.
I am sort of a beginner when it comes to c++ and opencv.
I have this assignment where i have to successfully convert an image from RGB to HSI and then split the HSI image into the 3 channels Hue, Saturation and respectively Intensity, without using any library functions when implementing my algorithm, after that the 3 images have to be displayed.
I was able to do most of these but when converting from RGB to HSI i am completely lost. From what i saw on other posts the pixel values should be put in a matrix and then changed based on my algorithms, after that the new values should go into a new matrix (HSI).
My main problem (i think) is that i cannot seem to put the values into the new matrix, i tried different methods but the outcome was the same.
Any input is welcome.
Best Regards
Stefan
#include <opencv\highgui.h>
#include <opencv\cv.h>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat rgb;
rgb = imread("Flower.jpg", CV_LOAD_IMAGE_COLOR);
unsigned char *input = (unsigned char*)(rgb.data);
Mat hsi = rgb.clone();
double R,G,B,a,H,S,I;
int i,j;
const double PI= 3.14;
for(int i = 0;i < hsi.rows ;i++){
for(int j = 0;j < hsi.cols ;j++){
B = input[hsi.step * j + i ];
G = input[hsi.step * j + i + 1];
R = input[hsi.step * j + i + 2];
}
if (R < G && R < B)
a = R;
if (G < R && G < B)
a = G;
if (B < G && B < R)
a = B;
I = (R+G+B)/3.0;
S = 1 - 3.0/(R+G+B)*a;
if (S == 0.0)
{
H = 0.0;
}
else
{
if (B <= G)
H = acos((((R-G)+(R-B))/2.0)/(sqrt((R-G)*(R-G) + (R-B)*(G-B))));
else
{
if (B > G)
H = 2*PI - acos((((R-G)+(R-B))/2.0)/(sqrt((R-G)*(R-G) + (R-B)*(G-B))));
}
}
}
namedWindow("RGB", CV_WINDOW_AUTOSIZE);
imshow("RGB", rgb);
namedWindow("HSI", CV_WINDOW_AUTOSIZE);
imshow("HSI", hsi);
waitKey(0);
return 0;
}
Your code has two problems.
First, you perform calculation outside the second loop:
for(int i = 0;i < hsi.rows ;i++){
for(int j = 0;j < hsi.cols ;j++){
}
// HSI calculation
}
So, you work only with last pixel in each row.
Second, you don't write results to hsi matrix.
Use this code template:
Mat bgr = ...; // OpenCV uses BGR format
Mat hsi(bgr.size(), CV_8UC3);
for (int i = 0; i < bgr.rows; ++i)
{
const Vec3b* bgr_row = bgr.ptr<Vec3b>();
Vec3b* hsi_row = hsi.ptr<Vec3b>();
for (int j = 0; j < bgr.cols; ++j)
{
double B = bgr_row[j][0];
double G = bgr_row[j][1];
double R = bgr_row[j][2];
double H = ...;
double S = ...;
double I = ...;
hsi_row[j] = Vec3b(H, S, I);
}
}