I used the code below to extract each HSV value from any image and to print each value on the screen.
Mat image_HSV;
cvtColor(ori_image, image_HSV, CV_BGR2HSV);
Mat mask;
inRange(image_HSV, Scalar(100, 0, 0), Scalar(100, 255, 255), mask);
image_HSV.setTo(Scalar(0, 0, 0), mask);
int h = 0;
int s = 0;
int v = 0;
int col = image_HSV.cols;
int row = image_HSV.rows;
int corow = col * row; // image's full pixel number
for (int i = 0; i < image_HSV.cols; i++) { // image row pixel
for (int j = 0; j < image_HSV.rows; j++) { // image col pixel
Vec3b hsv = image_HSV.at<Vec3b>(i,j);
h += hsv.val[0];
s += hsv.val[1];
v += hsv.val[2];
if (hsv[0] != 100) {
hsv[0] = 0;
hsv[1] = 0;
hsv[2] = 0;
}
}
}
cout << "H: " << h / corow << "% \n";
cout << "S: " << s / corow << "% \n";
cout << "V: " << v / corow << "% \n";
waitKey(0);
return 0;
I used all red color image for this time, which RGB values were 255, 0, 0.
However, I have some strange results from this code.
As I know, each H,S,V value range is covered by 0-360, 0-100, and 0-100, respectively.
Further, I also followed the post linked below but I still have a trouble to get right values.
OpenCV (C++) - Set HSV values of a pixel
But, I still don't know how to fix it.
Any help would be greatly appreciated! Thanks!
Related
Im using a C++ openCV program for first principles Algorithm development for HDL(Verilog) image object detection. I've finally managed to get HDL version up to the point of canny detection. In order to validate the two, both need to have identical output. I have found their are subtle differences that I thing are being contributed to by the openCV imread colour to grayscale conversion biasing green. The smoothed image is overall brighter in the openCV C++ method. From looking at the rgb2gray method it appears openCV used a bias ie (RX+GY+B*Z)/3 while in HDL I have been using (R+G+B)/3 as I require it to complete Gaussian, Sobel and Canny filters. Human visualisation is secondary and multiplication by a non-int is undesirable.
Is there a standard linear grayscale conversion for conversion or a means to override the existing method?
...
int main()
{
int thold = 15;
clock_t start;
double duration;
const int sobelX[3][3] = { {-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1} }; //Where origionally floats in python
const int sobelY[3][3] = { {-1, -2, -1}, {0, 0, 0}, {1, 2, 1} }; //Where origionally floats in python
const int kernel[5][5] = { {1,6,12,6,1},
{6,42,79,42,6},
{12,79,148,79,12},
{6,42,79,42,6},
{1,6,12,6,1} };// 1/732
// Above normalised kernal for smoothing, see origional python script for method
start = std::clock();
int height, width, intPixel, tSx, tSy, tS, dirE, dirEE, maxDir, curPoint, contDirection, cannyImgPix, nd, tl, tm, tr, mr, br, bm, bl, ml = 0;
int contNum = 128;
int contPixCount = 0;
int curContNum = 0;
int contPlace = 0;
int oldContPlace = 0;
int g = 0;
bool maxPoint;
struct pixel {
int number;
int h;
int w;
};
std::vector<pixel> contourList;
//double floatPixel = 0.0;
int kernalCumulator = 0;
const int mp = 3;
// Scalar color(0, 0, 255);
// duration = ((clock()) - start) / (double)CLOCKS_PER_SEC;
// start = clock();
// cout << "Start image in" << duration << '\n';
// Mat dst;
Mat rawImg = imread("C:\\Users\\&&&\\Documents\\pycode\\paddedGS.png",0);
printf("%d",rawImg.type());
// Mat rawImg = imread("C:\\Users\\&&&\\Documents\\openCV_Master\\openCVexample\\openCVexample\\brace200.jpg ", 0);
height = rawImg.rows;
width = rawImg.cols;
cout << "Height of image " << height << '\n';
cout << "Width of image " << width << '\n';
Mat filteredImg = Mat::zeros(height, width, CV_8U);
printf("%d", filteredImg.type());
Mat sobelImg = Mat::zeros(height, width, CV_8U);
Mat directionImg = Mat::zeros(height, width, CV_8U);
Mat cannyImg = Mat::zeros(height, width, CV_8U);
Mat contourImg = Mat::zeros(height, width, CV_16U);
// rawImg.convertTo(rawImg, CV_8UC1);
duration = ((clock()) - start) / (double)CLOCKS_PER_SEC;
start = clock();
cout << "Start image in" << duration << '\n';
// Loop to threshold already grayscaled image
/*
for (int h = 0; h < (height); h++)
{
for (int w = 0; w < (width); w++)
{
g = (int)rawImg.at<uchar>(h, w,0);
cout << g << "g";
g+= (int)rawImg.at<uchar>(h, w, 1);
cout << g << "g";
g+= (int)rawImg.at<uchar>(h, w, 2);
cout << g << "g";
g = g/3;
rawGImg.at<uchar>(h,w) = g;
}
}
*/
// imshow("thresholded Image", rawImg);
// waitKey();
// Loop to smooth using Gausian 5 x 5 kernal
// imshow("raw Image", rawImg);
for (int h = 3; h < (height - 3); h++)
{
for (int w = 3; w < (width - 3); w++)
{
if (rawImg.at<uchar>(h, w) >=6 )//Thresholding included
{
for (int xk = 0; xk < 5; xk++)
{
for (int yk = 0; yk < 5; yk++)
{
intPixel = rawImg.at<uchar>((h + (xk - mp)), (w + (yk - mp)));
kernalCumulator += intPixel*(kernel[xk][yk]);//Mutiplier required as rounding is making number go above 255, better solution?
}
}
}
else
kernalCumulator = 0;
kernalCumulator = kernalCumulator / 732;
if (kernalCumulator < 0 || kernalCumulator > 255)
{
// cout << "kernal Value: " << kernalCumulator;
// cout << " intPixel:" << intPixel << '\n';
}
filteredImg.at<uchar>(h, w) = (uchar)kernalCumulator;
kernalCumulator = 0;
}
}
Our vision does not perceive linearly the brightness, so it makes sense for usual applications to use some sort of transformation that tries to mimic the human perception.
For your application, you have 2 options: either use a similar transformation in HDL (which might not be easy or desired), or make a custom rgb to grayscale for OpenCV which uses the same transformation you use.
A short snippet (more like pseudocode, you'll have to figure out the details) for this would be something like:
cv::Mat linearRgbToGray(const cv::Mat &color) {
cv::Mat gray(color.size(), CV_8UC1);
for (int i = 0; i < color.rows; i++)
for (int j = 0; j < color.cols; j++)
gray.at(i, j) = (color.at(i, j)[0] + color.at(i, j)[1] + color.at(i, j)[2]) / 3;
}
As per Paul92's advice above
cv::Mat linearRgbToGray(const cv::Mat &color) {
cv::Mat gray(color.size(), CV_8UC1);
for (int i = 0; i < color.rows; i++)
for (int j = 0; j < color.cols; j++)
gray.at<uchar>(i, j) = ((color.at<cv::Vec3b>(i, j)[0] + color.at<cv::Vec3b>(i, j)[1] + color.at<cv::Vec3b>(i, j)[2]) / 3);
return gray;
}
The above code worked and overcame out of bounds errors I experienced earlier. Thank you, Rob.
how can I calculate percentage of white pixels inside of cv::RotatedRect? I mean, how to access single pixel inside of my cv::RotatedRect. If i'd reach that, i'd know what to do later. Thanks
I've tried solution from this thread, but I've had exceptions. https://stackoverflow.com/a/28780359
std::vector<cv::RotatedRect> minRect(count.size());
for (int i = 0; i < count.size(); i++)
{
minRect[i] = cv::minAreaRect(cv::Mat(count[i]));
}
for (size_t i = 0; i < count.size(); i++){
if (cv::contourArea(count[i]) > 200) {
cv::Point2f rect_points[4];
minRect[i].points(rect_points);
// Now I'd like to calculate percentage of white pixels inside of RotatedRect, and if value returned by func would be smaller than 30%,continue;
for (int j = 0; j < 4; j++) {
cv::line(mask, rect_points[j], rect_points[(j + 1) % 4], (0, 255, 0), 1, 8);
}
}
}
You can:
Work on the sub-image defined by cv::boundingRect
create the mask where all points inside the rotated rect are white with cv::fillConvexPoly
logical AND with the original image
count the number of white pixels with cv::countNonZero
The method proposed by John Henkel works, but in my (very quick) tests it something between 10 and 40 times slower.
Below the code with both methods. You'll find small differences in the result, because the white pixels on the border of the rotated rect are handled differently.
#include <opencv2\opencv.hpp>
#include <chrono>
int main()
{
// Create binary image with random pixels b/W
cv::Mat1b img(5000, 5000);
cv::randu(img, cv::Scalar(0), cv::Scalar(256));
img = img > 127;
// Define a rotated rect
cv::Point2f center(2000, 2000);
cv::Size2f sz(1000, 500);
float angle = 30.f;
cv::RotatedRect rr(center, sz, angle);
// Get points
std::vector<cv::Point2f> points(4);
rr.points(points.data());
// Work on ROI
cv::Rect roi = rr.boundingRect();
// Area
float area = rr.size.width * rr.size.height;
//// DEBUG, Show rect
//cv::Mat3b out;
//cv::cvtColor(img, out, cv::COLOR_GRAY2BGR);
//for (int i = 0; i < 4; ++i) {
// cv::line(out, points[i], points[(i + 1) % 4], cv::Scalar(0, 0, 255));
//}
{
// --------------------
// Method #Miki
// --------------------
auto tic = std::chrono::high_resolution_clock::now();
cv::Mat1b sub_img = img(roi);
// Create rotated rect mask
cv::Mat1b mask(roi.size(), uchar(0));
std::vector<cv::Point> points_in_sub_image(4);
for (int i = 0; i < 4; ++i) {
points_in_sub_image[i] = cv::Point(points[i]) - roi.tl();
}
cv::fillConvexPoly(mask, points_in_sub_image, cv::Scalar(255));
// AND sub image with mask
cv::Mat1b inside_roi = sub_img & mask;
//// DEBUG, Draw green points
//for (int r = 0; r < sub_img.rows; ++r) {
// for (int c = 0; c < sub_img.cols; ++c) {
// if (inside_roi(r, c) > 0)
// {
// out(r + roi.y, c + roi.x) = cv::Vec3b(0, 255, 0);
// }
// }
//}
// Get actual count
int cnz = cv::countNonZero(inside_roi);
auto toc = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(toc - tic);
float percent_white_pixels = cnz / area;
std::cout << "percent_white_pixels: " << percent_white_pixels << " in " << elapsed.count() << " us" << std::endl;
}
{
// --------------------
// Method #John Henkel
// --------------------
auto tic = std::chrono::high_resolution_clock::now();
int cnz = 0;
for (int y = roi.y; y < roi.y + roi.height; ++y) {
for (int x = roi.x; x < roi.x + roi.width; ++x) {
if (
(img(y, x) > 0) &&
(cv::pointPolygonTest(points, cv::Point2f(x, y), false) >= 0.0)
)
{
// DEBUG, Draw blue points
//out(y, x) = cv::Vec3b(255, 0, 0);
++cnz;
}
}
}
auto toc = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(toc - tic);
float percent_white_pixels = cnz / area;
std::cout << "percent_white_pixels: " << percent_white_pixels << " in " << elapsed.count() << " us" << std::endl;
}
getchar();
return 0;
}
The best way I can think of to get the individual pixels would be to first obtain the bounding box of your rotated rectangle and then iterate through each of the pixels inside the box to see if they are in the rotated rectangle with pointPolygonTest. I'm not sure if there's a more efficient way to do it, but this should give you the results you're looking for.
I'm just looking for an explanation behind OpenCV's coordinate system and its pixel intensity system. I am using a for loop to find the most intense pixel on the screen (I am aware that I could use minMaxLoc to find it, however, my code will be changed to find the leftmost pixel as well so this for loop is needed).
for (int i = 0; i < imgHight; i++) //Going through the height or Y axis.
{ // The height and width are equal the image is a square
for (int j = 0; j < imgWidth; j++) // Going through the width or X axis.
{
newInten = grayImg.at<uchar>(j, i); // This uses the X and Y to find a new point
HeadInten = grayImg.at<uchar>(Head); // This uses the stored XY as a comparassion
newIntenVal = newInten.val[0]; // This finds the intensity of the pixel at this point
if (newIntenVal > LowerBounds) //Compaired to lower bounds (80% of max pixel intensity)
{
if (newIntenVal > HeadIntenVal) // If the new intensity is higher than old then change head to new point and run again.
{
//cout << newInten << " " << HeadInten << " " << i << " " << j << endl;
Head = { j, i};
HeadIntenVal = HeadInten.val[0]; // Finds the intensity of pixel at stored head.
}
}
}
}
I then draw a circle around Head to show its position on the picture. The issue is, that currently, this draws in a random place, but when Head = {i, j} (X and Y are reversed) then this draws in the expected place. Is there any suggestion as to why this might happen?
Incorrect circle :
Correct circle:
The problem feeds into trying to find the intensity of that pixel again using the point values of Head, this then gives me a different result and cannot be used for comparison later on.
Many Thanks for any help!
EDIT: Full code - Sorry its a bit of a mess
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
double IBKG, IMAX, TC, LowerBounds; // IBKG is Image Background Level
Intensity, IMAX is Maximum Image Intensity, TC is comet theshold
Point IBKG_LOC, IMAX_LOC; // These are the respective locations on the
image, head is to find the head of the comet
Mat img = imread("Test1.png");
Mat grayImg;
int imgHight = img.rows;
int imgWidth = img.cols;
cout << imgHight << " " << imgWidth << endl;
cvtColor(img, grayImg, CV_RGB2GRAY);
minMaxLoc(grayImg, &IBKG, &IMAX, &IBKG_LOC, &IMAX_LOC);
cout << IMAX_LOC << endl;
TC = (IBKG + IMAX) / 2;
LowerBounds = IMAX * 0.8;
cout << LowerBounds << endl;
uint8_t newInten;
int maxX = 0, maxY = 0;
uint8_t grayMax = grayImg.at<uint8_t>(maxY, maxX);
for (int i = 0; i < imgHight; i++) //Going through the height or Y axis.
{ // The height and width are equal the image is a square
for (int j = 0; j < imgWidth; j++) // Going through the width or X axis.
{
uint8_t newInten = grayImg.at<uchar>(i,j); // This uses the X and Y to find a new point
if (newInten > LowerBounds) //Compaired to lower bounds (80% of max pixel intensity)
{
if (newInten > grayMax) // If the new intensity is higher than old then change head to new point and run again.
{
grayMax = newInten;
maxX = j;
maxY = i;
}
}
}
}
Point LeftSide;
bool leftSideFlag = false;
for (int i = maxX; i > 0; i--)
{
newInten = grayImg.at<uchar>(maxY, i);
if (!leftSideFlag)
{
if (newInten < TC)
{
LeftSide = { maxY, i};
leftSideFlag = true;
//i = 1;
}
}
}
int CircleRadius = maxX - LeftSide.x;
circle(img, Point(maxY, maxX), 10, Scalar(255, 255, 255));
cout << IBKG <<" " << IMAX << " " << IBKG_LOC << " " << IMAX_LOC << " " << TC << endl;
namedWindow("Image", WINDOW_NORMAL);
namedWindow("Gray Image", WINDOW_NORMAL);
imshow("Image", img);
imshow("Gray Image", grayImg);
waitKey(0);
return 0;
}
The Mat.at function needs parameters row , column order.
So first Y and then X
int imgHight = grayImg.rows;
int imgWidth = grayImg.cols;
int maxX = 0, maxY = 0;
uint8_t grayMax = grayImg.at<uint8_t>(maxY,maxX);
for (int i = 0; i < imgHight; i++) //Going through the height or Y axis.
{ // The height and width are equal the image is a square
for (int j = 0; j < imgWidth; j++) // Going through the width or X axis.
{
uint8_t newInten = grayImg.at<uchar>(i, j); // This uses the X and Y to find a new point
if (newInten > grayMax)
{
grayMax = newInten;
maxX = j;
maxY = i;
}
}
}
Also changed the code a bit to make it faster.
Not the fastest possible option though.
After adding the suggested fixes I swapped the X and Y round the way I would expect them normally for drawing the circle, I also needed to remember to compare maxX to Leftside.y. The outcome is now as expected. Thank you all!
I wants to embed watermark into an image using dct with c++ and opencv.
I split image into 8x8 block and apply dct to each block.
Now I don't know what to do next, Can anyone give me some hint or help me?
Here is my work so far.
int main() {
Mat originalImage;
originalImage = imread("image.jpg");
if( !originalImage.data )
{
std::cout<< "Error loading original image!"<<std::endl;
return -1;
}
cout << "Working on image from image.jpg" << endl;
/// Create Windows
namedWindow("Original", 1);
imshow( "Original", originalImage );
int x = 0; int y = 0;
moveWindow("Original", x, y);
imshow("Original", originalImage);
x += 100; y += 100;
int width = originalImage.size().width;
int height = originalImage.size().width;
cout << "Original image Width x Height is " << width << "x" << height << endl;
// Leave original alone, work on a copy
Mat dctImage = originalImage.clone();
// Step through the copied image with rectangles size 8x8
// For each block, split into planes, do dct, and merge back
// into the block. (This will affect the image from
// which the block is selected each time.)
for (int i = 0; i < height; i += 8)
{
for (int j = 0; j < width; j+= 8)
{
Mat block = dctImage(Rect(i, j, 8, 8));
vector<Mat> planes;
split(block, planes);
vector<Mat> outplanes(planes.size());
for (size_t k = 0; k < planes.size(); k++)
{
planes[k].convertTo(planes[k], CV_32FC1);
dct(planes[k], outplanes[k]);
outplanes[k].convertTo(outplanes[k], CV_8UC1);
}
merge(outplanes, block);
}
}
namedWindow("dctBlockImage");
moveWindow("dctBlockImage", x, y);
imshow("dctBlockImage", dctImage);
x += 100; y += 100;
waitKey();
destroyAllWindows();
return 0;
}
After I do some image manipulation, and apply mask, I get what I want. I can clearly see on imshow result of "crop" that there's gray pixels in the middle of image.
I'm trying to get the maximum pixel value location. I've checked the crop.channels(), which returns 1.
Mat mask = drawing2;
drawContours(mask, contours, -1, Scalar(255), CV_FILLED);
Mat dist;
distanceTransform( cannyInv, dist, CV_DIST_L2, 3 );
normalize(dist,dist,0.0,1.0,NORM_MINMAX);
Mat crop;
dist.copyTo(crop, mask);
cout << "max.. "<< *std::max_element(crop.begin<double>(),crop.end<double>()) <<endl;
which returns max.. 4.25593e-08
for(int y = 0; y < crop.rows; y++)
{
for(int x = 0; x < crop.cols; x++)
{
if (crop.at<unsigned char>(x,y) > 0){
cout << "X........"<<x<<" Y......"<<y<< " = "<<crop.at<unsigned char>(x,y) <<endl;
}
}
}
The output is:
X........604 Y......479 = ¿
X........607 Y......479 =
X........610 Y......479 = ¿
Help me please
PD: I know that there's similar question. But this is specific problem.
I'm not sure how I solved it. A lot of time has passed. But the code that currently I have and it works is this:
Mat dist=Mat::zeros(480,640, CV_8UC1);;
distanceTransform( cannyInv, dist, CV_DIST_L2, 3 );
Mat distNorm;
dist.convertTo(distNorm, CV_8UC1,1,0);
Mat result= Mat::zeros(480,640, CV_8UC1);
distNorm.copyTo(result, mask);
Mat tmp=Mat::zeros(480,640, CV_8UC1);
Mat fik=Mat::zeros(480,640, CV_8UC3);
for(int i = 0; i < result.rows; i++)
{
for(int j = 0; j < result.cols; j++)
{
if ( result.at< uchar >( i,j ) > 0){
uchar val = result.at< uchar >( i,j );
if(val>maxVal){
if(val>0){
cv::circle(tmp,cvPoint(j,i),2,255,-1);
}
maxVal=val;
maxX = j;
maxY = i;
}
}
}
}
Are you sure that normalizing the Mat automatically converts it from uchar to double? It's very likely the data is still stored as uchars and you're reading wrong numbers from it.
Try dist.convertTo(dist, CV_64F);
Print the numbers as doubles everywhere
OR work only with uchars.
Try this code:
cout << "X........"
<< x
<< " Y......"
<< y
<< " = "
<< (double) crop.at< unsigned char>(x,y) <<endl;