How to put text into a bounding box in OpenCV? - c++

I know how to pass putText position and font size:
void TextBox( cv::Mat & img, const std::string & text, const cv::Rect & bbox )
{
cv::Point position;
double size;
int face = CV_FONT_HERSHEY_PLAIN;
Trick( /*in:*/ text, bbox, face /*out:*/ position, size );
cv::putText( img, text, position, face, size, cv::Scalar( 100, 255, 100 ) );
}
How to do the trick?
I would like to scale text to fit its bounding box.
(Font face might be unused input to that.)

It's a bit tricky, but you can play with cv::getTextSize(). There is a sample code in the description of the function but it only renders some text, the tight box (surrounding the text), and the baseline of it.
If you'd like to render the text in an arbitrary ROI of an image then you first need to render it into another image (which fits to the text size), resize it to the ROI desired and then put it over the image, such as below:
void PutText(cv::Mat& img, const std::string& text, const cv::Rect& roi, const cv::Scalar& color, int fontFace, double fontScale, int thickness = 1, int lineType = 8)
{
CV_Assert(!img.empty() && (img.type() == CV_8UC3 || img.type() == CV_8UC1));
CV_Assert(roi.area() > 0);
CV_Assert(!text.empty());
int baseline = 0;
// Calculates the width and height of a text string
cv::Size textSize = cv::getTextSize(text, fontFace, fontScale, thickness, &baseline);
// Y-coordinate of the baseline relative to the bottom-most text point
baseline += thickness;
// Render the text over here (fits to the text size)
cv::Mat textImg(textSize.height + baseline, textSize.width, img.type());
if (color == cv::Scalar::all(0)) textImg = cv::Scalar::all(255);
else textImg = cv::Scalar::all(0);
// Estimating the resolution of bounding image
cv::Point textOrg((textImg.cols - textSize.width) / 2, (textImg.rows + textSize.height - baseline) / 2);
// TR and BL points of the bounding box
cv::Point tr(textOrg.x, textOrg.y + baseline);
cv::Point bl(textOrg.x + textSize.width, textOrg.y - textSize.height);
cv::putText(textImg, text, textOrg, fontFace, fontScale, color, thickness);
// Resizing according to the ROI
cv::resize(textImg, textImg, roi.size());
cv::Mat textImgMask = textImg;
if (textImgMask.type() == CV_8UC3)
cv::cvtColor(textImgMask, textImgMask, cv::COLOR_BGR2GRAY);
// Creating the mask
cv::equalizeHist(textImgMask, textImgMask);
if (color == cv::Scalar::all(0)) cv::threshold(textImgMask, textImgMask, 1, 255, cv::THRESH_BINARY_INV);
else cv::threshold(textImgMask, textImgMask, 254, 255, cv::THRESH_BINARY);
// Put into the original image
cv::Mat destRoi = img(roi);
textImg.copyTo(destRoi, textImgMask);
}
And call it like:
cv::Mat image = cv::imread("C:/opencv_logo.png");
cv::Rect roi(5, 5, image.cols - 5, image.rows - 5);
cv::Scalar color(255, 0, 0);
int fontFace = cv::FONT_HERSHEY_SCRIPT_SIMPLEX;
double fontScale = 2.5;
int thickness = 2;
PutText(image, "OpenCV", roi, color, fontFace, fontScale, thickness);
As a result of the PutText() function you can render any text over an arbitrary ROI of the image, such as:
Hope it helps and works :)
Update #1:
And keep in mind that text rendering (with or without this trick) in OpenCV is very expensive and can affect to the runtime of your applications. Other libraries may outperform the OpenCVs rendering system.

Related

triangle mask with opencv

i have this image
i want to create a tranigle mask to get only this zone
but with the following code i get this result
Moments mu = moments(red,true);
Point center;
center.x = mu.m10 / mu.m00;
center.y = mu.m01 / mu.m00;
circle(red, center, 2, Scalar(0, 0, 255));
cv::Size sz = red.size();
int imageWidth = sz.width;
int imageHeight = sz.height;
Mat mask3(red.size(), CV_8UC1, Scalar::all(0));
// Create Polygon from vertices
vector<Point> ptmask3(3);
ptmask3.push_back(Point(imageHeight-1, imageWidth-1));
ptmask3.push_back(Point(center.x, center.y));
ptmask3.push_back(Point(0, red.rows - 1));
vector<Point> pt;
approxPolyDP(ptmask3, pt, 1.0, true);
// Fill polygon white
fillConvexPoly(mask3, &pt[0], pt.size(), 255, 8, 0);
// Create new image for result storage
Mat hide3(red.size(), CV_8UC3);
// Cut out ROI and store it in imageDest
red.copyTo(hide3, mask3);
imshow("mask3", hide3);
Updated Version (with the Help of Dan MaĊĦek)
Your Triangle is wrong
This is because you're initializing the vector with size 3, then putting another three points into it, for a total of 6 points of which three have default values. Try this instead:
vector<Point> ptmask3;
Also, make sure that the coordinates of the points are correct. You'll want to have a point in the bottom left corner, but it doesn't seem like your current triangle has one like that.
Your image is gray
You need to initialize hide3 properly, like this:
cv::Mat hide3(img.size(), CV_8UC3, cv::Scalar(0));

OpenCV - Cropping non rectangular region from image using C++

How can I crop a non rectangular region from image?
Imagine I have four points and I want to crop it, this shape wouldn't be a triangle somehow!
For example I have the following image :
and I want to crop this from image :
How can I do this?
regards..
The procedure for cropping an arbitrary quadrilateral (or any polygon for that matter) part of an image is summed us as:
Generate a "mask". The mask is black where you want to keep the image, and white where you don't want to keep it
Compute the "bitwise_and" between your input image and the mask
So, lets assume you have an image. Throughout this I'll use an image size of 30x30 for simplicity, you can change this to suit your use case.
cv::Mat source_image = cv::imread("filename.txt");
And you have four points you want to use as the corners:
cv::Point corners[1][4];
corners[0][0] = Point( 10, 10 );
corners[0][1] = Point( 20, 20 );
corners[0][2] = Point( 30, 10 );
corners[0][3] = Point( 20, 10 );
const Point* corner_list[1] = { corners[0] };
You can use the function cv::fillPoly to draw this shape on a mask:
int num_points = 4;
int num_polygons = 1;
int line_type = 8;
cv::Mat mask(30,30,CV_8UC3, cv::Scalar(0,0,0));
cv::fillPoly( mask, corner_list, &num_points, num_polygons, cv::Scalar( 255, 255, 255 ), line_type);
Then simply compute the bitwise_and of the image and mask:
cv::Mat result;
cv::bitwise_and(source_image, mask, result);
result now has the cropped image in it. If you want the edges to end up white instead of black you could instead do:
cv::Mat result_white(30,30,CV_8UC3, cv::Scalar(255,255,255));
cv::bitwise_and(source_image, mask, result_white, mask);
In this case we use bitwise_and's mask parameter to only do the bitwise_and inside the mask. See this tutorial for more information and links to all the functions I mentioned.
You may use cv::Mat::copyTo() like this:
cv::Mat img = cv::imread("image.jpeg");
// note mask may be single channel, even if img is multichannel
cv::Mat mask = cv::Mat::zeros(img.rows, img.cols, CV_8UC1);
// fill mask with nonzero values, e.g. as Tim suggests
// cv::fillPoly(...)
cv::Mat result(img.size(), img.type(), cv::Scalar(255, 255, 255));
img.copyTo(result, mask);

Remove colour 'shades'/shadows

What OpenCV functions can be used to ignore/filter out colour 'variation'/shades of a colour (shadows, reflections, etc.)?
Shouldn't removing the Value/Intensity channel from HSV images create colour blocks and reduce/eliminate 'colour variance'/shades of white due to light?
If you look at the following image, the *walls are painted one solid consistent colour of cream/white. But it's colour varies alot because of shadows and light reflections. *Referring to the white walls above the lockers.
I thought that if I convert the image to HSV then remove the Value/Intensity channel that I can filter out those wall reflections and shadows, colour variation - ie, the light. Then I just colour reduce the image and I should have a large colour block for the wall (above the lockers)? Ie, see the wall in it's true form/colour as one solid colour block.
But from my image above you can see the wall is not one solid/consistent colour after removing the V channel and after colour reducing.
void removeIntensity()
{
Mat hsv, hs, reducedHs;
Mat image = imread("../../Book_Tutorials/images/11.jpg");
if (image.cols > 300) {
float scale = 300.0 / (float)image.rows;
resize(image, image, { int(scale * image.cols), 300 });
}
cvtColor(image, hsv, CV_BGR2HSV);
std::vector<Mat> hsvChannels;
split(hsv, hsvChannels);
// Set value/intensity to constant value: can I remove those channels completely?
hsvChannels[2] = 0;
merge(hsvChannels, hs);
reduce(hs, reducedHs, 6);
imshow("image", image);
imshow("hsv", hsv);
imshow("hs", hs);
imshow("reducedHs", reducedHs);
}
void reduce(const Mat& hsv, Mat& reduced, int nColours)
{
int n = hsv.rows * hsv.cols;
std::vector<int> labels;
Mat centres;
Mat collapsedImage = hsv.reshape(1, n);
collapsedImage.convertTo(collapsedImage, CV_32F);
kmeans(collapsedImage, nColours, labels, TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 1.0),
3, KMEANS_PP_CENTERS, centres);
for (int i = 0; i < n; i++) {
collapsedImage.at<float>(i, 0) = centres.at<float>(labels[i], 0);
collapsedImage.at<float>(i, 1) = centres.at<float>(labels[i], 1);
collapsedImage.at<float>(i, 2) = centres.at<float>(labels[i], 2);
}
reduced = collapsedImage.reshape(3, hsv.rows);
reduced.convertTo(reduced, CV_8U);
}

OpenCV: how can I interpret the results of inRange?

I am processing video images and I would like to detect if the video contains any pixels of a certain range of red. Is this possible?
Here is the code I am adapting from a tutorial:
#ifdef __cplusplus
- (void)processImage:(Mat&)image;
{
cv::Mat orig_image = image.clone();
cv::medianBlur(image, image, 3);
cv::Mat hsv_image;
cv::cvtColor(image, hsv_image, cv::COLOR_BGR2HSV);
cv::Mat lower_red_hue_range;
cv::Mat upper_red_hue_range;
cv::inRange(hsv_image, cv::Scalar(0, 100, 100), cv::Scalar(10, 255, 255), lower_red_hue_range);
cv::inRange(hsv_image, cv::Scalar(160, 100, 100), cv::Scalar(179, 255, 255), upper_red_hue_range);
// Interpret values here
}
Interpreting values
I would like to detect if the results from the inRange operations are nil or not. In other words I want to understand if there are any matching pixels in the original image with a colour inRange from the given lower and upper red scale. How can I interpret the results?
First you need to OR the lower and upper mask:
Mat mask = lower_red_hue_range | upper_red_hue_range;
Then you can countNonZero to see if there are non zero pixels (i.e. you found something).
int number_of_non_zero_pixels = countNonZero(mask);
It could be better to first apply morphological erosion or opening to remove small (probably noisy) blobs:
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
morphologyEx(mask, mask, MORPH_OPEN, kernel); // or MORPH_ERODE
or find connected components (findContours, connectedComponentsWithStats) and prune / search for according to some criteria:
vector<vector<Point>> contours
findContours(mask.clone(), contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
double threshold_on_area = 100.0;
for(int i=0; i<contours.size(); ++i)
{
double area = countourArea(contours[i]);
if(area < threshold_on_area)
{
// don't consider this contour
continue;
}
else
{
// do something (e.g. drawing a bounding box around the contour)
Rect box = boundingRect(contours[i]);
rectangle(hsv_image, box, Scalar(0, 255, 255));
}
}

Skew angle detection on a image with scattered characters

I've been following this tutorial to get the skew angle of an image. It seems like HoughLinesP is struggling to find lines when characters are a bit scattered on the target image.
This is my input image:
This is the lines the HoughLinesP has found:
It's not really getting most of the lines and it seems pretty obvious to me why. This is because I've set my minLineWidth to be (size.width / 2.f). The point is that because of the few lines it has found it turns out that the skew angle is also wrong. (-3.15825 in this case, when it should be something close to 0.5)
I've tried to erode my input file to make characters get closer and in this case it seems to work out, but I don't feel this is best approach for situations akin to it.
This is my eroded input image:
This is the lines the HoughLinesP has found:
This time it has found a skew angle of -0.2185 degrees, which is what I was expecting but in other hand it is losing the vertical space between lines which in my humble opinion isn't a good thing.
Is there another to pre-process this kind of image to make houghLinesP get better results for scattered characters ?
Here is the source code I'm using:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
static cv::Scalar randomColor( cv::RNG& rng )
{
int icolor = (unsigned) rng;
return cv::Scalar( icolor&255, (icolor>>8)&255, (icolor>>16)&255 );
}
void rotate(cv::Mat& src, double angle, cv::Mat& dst)
{
int len = std::max(src.cols, src.rows);
cv::Point2f pt(len/2., len/2.);
cv::Mat r = cv::getRotationMatrix2D(pt, angle, 1.0);
cv::warpAffine(src, dst, r, cv::Size(len, len));
}
double compute_skew(cv::Mat& src)
{
// Random number generator
cv::RNG rng( 0xFFFFFFFF );
cv::Size size = src.size();
cv::bitwise_not(src, src);
std::vector<cv::Vec4i> lines;
cv::HoughLinesP(src, lines, 1, CV_PI/180, 100, size.width / 2.f, 20);
cv::Mat disp_lines(size, CV_8UC3, cv::Scalar(0, 0, 0));
double angle = 0.;
unsigned nb_lines = lines.size();
for (unsigned i = 0; i < nb_lines; ++i)
{
cv::line(disp_lines, cv::Point(lines[i][0], lines[i][1]),
cv::Point(lines[i][2], lines[i][3]), randomColor(rng));
angle += atan2((double)lines[i][3] - lines[i][1],
(double)lines[i][2] - lines[i][0]);
}
angle /= nb_lines; // mean angle, in radians.
std::cout << angle * 180 / CV_PI << std::endl;
cv::imshow("HoughLinesP", disp_lines);
cv::waitKey(0);
return angle * 180 / CV_PI;
}
int main()
{
// Load in grayscale.
cv::Mat img = cv::imread("IMG_TESTE.jpg", 0);
cv::Mat rotated;
double angle = compute_skew(img);
rotate(img, angle, rotated);
//Show image
cv::imshow("Rotated", rotated);
cv::waitKey(0);
}
Cheers
I'd suggest finding individual components first (i.e., the lines and the letters), for example using cv::threshold and cv::findContours.
Then, you could drop the individual components that are narrow (i.e., the letters). You can do this using cv::floodFill for example. This should leave you with the lines only.
Effectively, getting rid of the letters might provide easier input for the Hough transform.
Try to detect groups of characters as blocks, then find contours of these blocks. Below I've done it using blurring, a morphological opening and a threshold operation.
Mat im = imread("yCK4t.jpg", 0);
Mat blurred;
GaussianBlur(im, blurred, Size(5, 5), 2, 2);
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
Mat morph;
morphologyEx(blurred, morph, CV_MOP_OPEN, kernel);
Mat bw;
threshold(morph, bw, 0, 255, CV_THRESH_BINARY_INV | CV_THRESH_OTSU);
Mat cont = Mat::zeros(im.rows, im.cols, CV_8U);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(bw, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
{
drawContours(cont, contours, idx, Scalar(255, 255, 255), 1);
}
Then use Hough line transform on contour image.
With accumulator threshold 80, I get following lines that results in an angle of -3.81. This is high because of the outlier line that is almost vertical. With this approach, majority of the lines will have similar angle values except few outliers. Detecting and discarding the outliers will give you a better approximation of the angle.
HoughLinesP(cont, lines, 1, CV_PI/180, 80, size.width / 4.0f, size.width / 8.0f);