How can I get the minimum enclosing circle with OPENCV? - c++

I'm using cv::minEnclosingCircle(...) in order to get the minimum circle that exactly evolves my contour, but I'm getting a circle a little big bigger.
In other words, I'm trying to get something like this:
https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Simple_concave_polygon_Min_Enclosing_Circle.svg/441px-Simple_concave_polygon_Min_Enclosing_Circle.svg.png
But I'm getting this (the circle):
Note how the circle is a little bigger than the item to enclose.
I need to enclose my object into a circle, not an ellipse.
Thank you in advance.
This is my code:
cv::vector<cv::Point> allPixels;
int columnas = img.cols, filas = img.rows;
cv::Point pt;
for(int col = 0; col < columnas; col++){
for(int row = 0; row < filas; row++){
int val = img.at<uchar>(row,col);
if(val == 255){
pt.x = col;
pt.y = row;
allPixels.push_back(pt);
}
}
}
cv::Mat dispImage = img.clone();
cv::Point2f center;
float radius;
cv::minEnclosingCircle(allPixels,center,radius);
cv::circle(dispImage,center,radius,cv::Scalar(128),1);
cv::circle(dispImage,center,1,cv::Scalar(128),1);
cv::imwrite("Enclosing_Result.png",dispImage);
With 'img' a cv::Mat with size (760,760) and format CV_8UC1. The final result ("Enclosing_Result.png") is next:
And my target is which follows (drawn):

My result is OK.
1. for only one region
## only one region
cnts = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
(x,y), r = cv2.minEnclosingCircle(cnts[0])
2. for more than one region
## more than one region
mask = threshed.copy()
## find centers
for cnt in cnts:
(x,y), r = cv2.minEnclosingCircle(cnt)
pt = (int(x), int(y))
centers.append(pt)
## connect the `centers`
for i in range(1, len(centers)):
cv2.line(mask, centers[i-1], centers[i], 255, 2)
## find the center
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
(x,y), r = cv2.minEnclosingCircle(cnts[0])

Related

Removing a black part of image after stitching 2 images in OpenCV C++

So I stitched 2 images in OpenCV C++ but I know have a full black part in the image and would like to remove it. What would be the way to go?
Here is my image output:
The idea is to sum the pixels of each column then iterate through the data to construct the new image. If the value of a column is zero then it means it is black so we ignore it otherwise we concatenate the column ROI to the final image. Here's the summation of the column pixels:
Result
I implemented it in Python but you can adapt a similar idea to C++
import cv2
import numpy as np
# import matplotlib.pyplot as plt
# Load image, convert to grayscale, and sum column pixels
image = cv2.imread('1.jpg')
h, w = image.shape[:2]
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
first_pass = True
pixels = np.sum(gray, axis=0).tolist()
# Build new image
for index, value in enumerate(pixels):
if value == 0:
continue
else:
ROI = image[0:h, index:index+1]
if first_pass:
result = image[0:h, index+1:index+2]
first_pass = False
continue
result = np.concatenate((result, ROI), axis=1)
cv2.imshow('result', result)
cv2.imwrite('result.png', result)
# Uncomment for plot visualization
# plt.plot(pixels, color='teal')
# plt.show()
cv2.waitKey()
Note: According to nathancy's answer I just coded using C++:
#include <iostream>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("/your/image/directory/image.jpg");
for(int i=0;i<img.cols;i++)
{
int black_cnt = 0;
for(int j=0;j<img.rows;j++)
{
if(img.at<cv::Vec3b>(j,i)[0]==0)
black_cnt++;
}
if(black_cnt==img.rows)
continue;
else
{
Rect roi(i,0,img.cols-i,img.rows);
img = img(roi);
break;
}
}
imshow("Result",img);
waitKey(0);
return 0;
}
Fast way to do it is to use cv::reduce function of OpenCv and find maximum value per column. It is faster than making sum of elements. If max value in column is 0, it means that column is black.
Input of cv::reduce is 2d-array:
[a b c]
[d e f]
[g h i]
as output will get matrix 2d with one row - vector.
[max(a,d,g) max(b,e,h) max(c,f,i)]
Then you need to find cutOff index - first non-black column, and extract ROI:
cv::Mat img = imread("test.jpg");
cv::Mat out;
cv::reduce(img, out, 0, cv::REDUCE_MAX);
int cutOffIdx = 0;
for (int col = 0; col < out.cols; ++col) {
const cv::Vec3b& vec = out.at<Vec3b>(0, col);
if (vec[0] || vec[1] || vec[2]) {
cutOffIdx = col;
break;
}
}
cv::imshow("test",img(cv::Rect(cutOffIdx,0,img.cols-cutOffIdx-1,img.rows)));
cv::waitKey(0);
I would do this:
Thresholding the graysclae image
Finding the outermost contours in the image
Find the biggest one from the contours
Get the bounding box of that contour
Crop the image by that bounding box
And the code (C++ opencv):
Mat K,J,I = imread("D:/1.jpg",1);
cvtColor(I, K, CV_BGR2GRAY);
threshold(K, J, 0, 255, THRESH_BINARY);
vector<vector<Point>> contours;
vector< Vec4i > hierarchy;
findContours(J, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE); # Gives the outer contours
Mat tmp = Mat::zeros(I.size(), CV_8U);
int k = 0;
double max = -1;
for (size_t i = 0; i < contours.size(); i++) # Of course in this case, There is only one external contour but I write the loop for more clarification
{
double area = contourArea(contours[i]);
if (area > max)
{
k = i;
max = area;
}
}
drawContours(tmp, contours, k, Scalar(255, 255, 255), -1); # You can comment this line. I wrote it just for showing the procedure
Rect r = cv::boundingRect(contours[k]);
Mat output;
I(r).copyTo(output);
imshow("0", I);
imshow("1", J);
imshow("2", tmp);
imshow("3", output);
waitKey(0);

OCRTesseract in OpenCV3 not working properly for relatively simple image

Edit:: made some code change and at least am not getting the empty page error. Update code below.
I am using OpenCV3 and Tesseract and have done some processing on a relatively simple image and I was expecting the ocr part to work smoothly but it's not.
Image:
Code:
Ptr<cv::text::OCRTesseract> ocr =
cv::text::OCRTesseract::create(NULL /*datapath*/, "eng" /*lang*/, "ABCDEFGHIJKLMNOPQRSTUVWXYZ" /*whitelist*/, 2 /*oem*/, 10 /*psmode*/);
string output;
vector<Rect> boxes;
vector<string> words;
vector<float> confidences;
ocr->run(gray3, output, &boxes, &words, &confidences, cv::text::OCR_LEVEL_WORD);
Output:
I
Any idea what's going on?
Thanks.
Removing the blobs connected to the borders will help improve tesseract. So we take your image:
You want to invert the image so the character is white and background black:
Mat img = imread("T2.png"); // reading the example image
cvtColor(img, img, CV_RGB2GRAY);
bitwise_not(img, img); // invert the image
Then we want to remove the blobs connected to the borders using the floodFill method
// Remove blobs attached on corners
uchar white(255);
// do top and bottom row
for (int y = 0; y < img.rows; y += img.rows - 1)
{
uchar* row = img.ptr<uchar>(y);
for (int x = 0; x < img.cols; ++x)
{
if (row[x] == white)
{
floodFill(img, Point(x, y), Scalar(0), (Rect*)0, Scalar(), Scalar(200));
}
}
}
// fix left and right sides
for (int y = 0; y < img.rows; ++y)
{
uchar* row = img.ptr<uchar>(y);
for (int x = 0; x < img.cols; x += img.cols - 1)
{
if (row[x] == white)
{
floodFill(img, Point(x, y), Scalar(0), (Rect*)0, Scalar(), Scalar(200));
}
}
}
This will produce the following image:
Running tesseract on this image will result in 'T' instead of 'I'
Hope this helps you solving your problem. :)

Calculate mahalanobis distance

From a bunch of images I, a mean color C_m evolves. Now I want to obtain a distance image, using mahalanobis distance, in which each pixels mahalanobis distance to the C_m gets calculated. I can't get OpenCV's Mahalanobis() function to work.
I calculate the calcCovarMatrix with all pixel colors of I, invert it and pass it to Mahalanobis(). Next I'm looping over the new image to calculate the distance for every single pixel:
Mat covar, incovar, mean;
calcCovarMatrix(...);
invert(covar,incovar,DECOMP_SVD);
for (int row = 0; row < image.rows; ++row) {
for (int col = 0; col < image.cols; ++col) {
Scalar color = image.at<Vec3b>(row, col);
double m_dist = Mahalanobis(color, mean, incovar);
}
}
Resulting in:
OpenCV Error: Assertion failed (type == v2.type() && type == icovar.type() && sz == v2.size() && len == icovar.rows && len == icovar.cols) in Mahalanobis, file /tmp/opencv-8GA996/opencv-2.4.9/modules/core/src/matmul.cpp,
What's my mistake here? Thanks in advance!
Mahalanobis is not working on single pixels, but on whole images. so instead try :
double dist = Mahalanobis( image1, image2, invcovar );

OpenCV: crop image with gaussian blur

I have an grayscale image, and I want to crop a rectangle of size w x h centered at pixel (x,y). The problem is, I don't want the crop to look boxy so around the edge I want to gaussian blur the values so that they smoothly transisition to zero. Any ideas on how to do this?
Currently I am doing:
int bb_min_x = center_x - width/2.0;
int bb_max_x = center_x + width/2.0;
int bb_min_y = center_y - height/2.0;
int bb_max_y = center_y + height/2.0;
for(int y = bb_min_y; y <= bb_max_y; y++){
for(int x = bb_min_x; x <= bb_max_x; x++){
final_img.at<uchar>(y,x) = original_img.at<uchar>(y,x);
}
}
try this function:
compute the distance from your input rectangle and use that as a fading factor.
cv::Mat cropFade(cv::Mat _img, cv::Rect _roi, int _maxFadeDistance)
{
cv::Mat fadeMask = cv::Mat::ones(_img.size(), CV_8UC1);
cv::rectangle(fadeMask, _roi, cv::Scalar(0),-1);
cv::imshow("mask",fadeMask>0);
cv::Mat dt;
cv::distanceTransform(fadeMask > 0, dt, CV_DIST_L2 ,CV_DIST_MASK_PRECISE);
// fade to a maximum distance:
double maxFadeDist;
if(_maxFadeDistance > 0)
maxFadeDist = _maxFadeDistance;
else
{
// find min/max vals
double min,max;
cv::minMaxLoc(dt,&min,&max);
maxFadeDist = max;
}
//dt = 1.0-(dt* 1.0/max); // values between 0 and 1 since min val should alwaysbe 0
dt = 1.0-(dt* 1.0/maxFadeDist); // values between 0 and 1 in fading region
cv::imshow("blending mask", dt);
cv::Mat imgF;
_img.convertTo(imgF,CV_32FC3);
std::vector<cv::Mat> channels;
cv::split(imgF,channels);
// multiply pixel value with the quality weights for image 1
for(unsigned int i=0; i<channels.size(); ++i)
channels[i] = channels[i].mul(dt);
cv::Mat outF;
cv::merge(channels,outF);
cv::Mat out;
outF.convertTo(out,CV_8UC3);
return out;
}
calling that with cv::Mat out = cropFade(in, cv::Rect(in.cols/4, in.rows/4, in.cols/2, in.rows/2), in.cols/8); gives me those results for a lena with the specified rect:
this is the result for full image fading from the same unchanged rect:
One simple approach:
// Create a weight image
int border=25;
cv::Mat_<float> rect=cv::Mat_<float>::zeros(height,width)
cv::rectangle(rect,cv::Rect(border/2,border/2,width-border,height-border),cv::Scalar(1),-1);
cv::Mat_<float> weights, kernel=cv::getStructuringElement(cv::MORPH_ELLIPSE,cv::Size(border,border));
int nnz = cv::countNonZero(kernel);
cv::filter2D(rect,weights,-1,kernel/nnz);
This creates a weight image like the following:
Then you use it to fade your image out:
for(int y = bb_min_y; y <= bb_max_y; y++){
for(int x = bb_min_x; x <= bb_max_x; x++){
float w = weights.at<float>(y-bb_min_y,x-bb_min_x);
uchar val = original_img.at<uchar>(y,x);
final_img.at<uchar>(y,x) = cv::saturate_cast<uchar>(w*val);
}
}
If you turn your bounding box into a contour you can use pointPolygonTest to calculate the distance to the edge of the bounding box for each pixel. If you then lower the color values to zero depending on this distance you get a blur effect.
See this page for an example.

How to run findContours() on meanShiftSegmentation() output?

I'm trying to rewrite my very slow naive segmentation using floodFill to something faster. I ruled out meanShiftFiltering a year ago because of the difficulty in labelling the colours and then finding their contours.
The current version of opencv seems to have a fast new function that labels segments using mean shift: gpu::meanShiftSegmentation(). It produces images like the following:
(source: ekran.org)
So this looks to me pretty close to being able to generating contours. How can I run findContours to generate segments?
Seems to me, this would be done by extracting the labelled colours from the image, and then testing which pixel values in the image match each label colour to make a boolean image suitable for findContours. This is what I have done in the following (but its a bit slow and strikes me there should be a better way):
Mat image = imread("test.png");
...
// gpu operations on image resulting in gpuOpen
...
// Mean shift
TermCriteria iterations = TermCriteria(CV_TERMCRIT_ITER, 2, 0);
gpu::meanShiftSegmentation(gpuOpen, segments, 10, 20, 300, iterations);
// convert to greyscale (HSV image)
vector<Mat> channels;
split(segments, channels);
// get labels from histogram of image.
int size = 256;
labels = Mat(256, 1, CV_32SC1);
calcHist(&channels.at(2), 1, 0, Mat(), labels, 1, &size, 0);
// Loop through hist bins
for (int i=0; i<256; i++) {
float count = labels.at<float>(i);
// Does this bin represent a label in the image?
if (count > 0) {
// find areas of the image that match this label and findContours on the result.
Mat label = Mat(channels.at(2).rows, channels.at(2).cols, CV_8UC1, Scalar::all(i)); // image filled with label colour.
Mat boolImage = (channels.at(2) == label); // which pixels in labeled image are identical to this label?
vector<vector<Point>> labelContours;
findContours(boolImage, labelContours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
// Loop through contours.
for (int idx = 0; idx < labelContours.size(); idx++) {
// get bounds for this contour.
bounds = boundingRect(labelContours[idx]);
// create ROI for bounds to extract this region
Mat patchROI = image(bounds);
Mat maskROI = boolImage(bounds);
}
}
}
Is this the best approach or is there a better way to get the label colours? Seems it would be logical for meanShiftSegmentation to provide this information? (vector of colour values, or vector of masks for each label, etc.)
Thank you.
Following is another way of doing this without thowing away the colour information in the meanShiftSegmentation results. I did not compare the two for performance.
// Loop through whole image, pixel and pixel and then use the colour to index an array of bools indicating presence.
vector<Scalar> colours;
vector<Scalar>::iterator colourIter;
vector< vector< vector<bool> > > colourSpace;
vector< vector< vector<bool> > >::iterator colourSpaceBIter;
vector< vector<bool> >::iterator colourSpaceGIter;
vector<bool>::iterator colourSpaceRIter;
// Initialize 3D Vector
colourSpace.resize(256);
for (int i = 0; i < 256; i++) {
colourSpace[i].resize(256);
for (int j = 0; j < 256; j++) {
colourSpace[i][j].resize(256);
}
}
// Loop through pixels in the image (should be fastish, look into LUT for faster)
uchar r, g, b;
for (int i = 0; i < segments.rows; i++)
{
Vec3b* pixel = segments.ptr<Vec3b>(i); // point to first pixel in row
for (int j = 0; j < segments.cols; j++)
{
b = pixel[j][0];
g = pixel[j][1];
r = pixel[j][2];
colourSpace[b][g][r] = true; // this colour is in the image.
//cout << "BGR: " << int(b) << " " << int(g) << " " << int(r) << endl;
}
}
// Get all the unique colours from colourSpace
// loop through colourSpace
int bi=0;
for (colourSpaceBIter = colourSpace.begin(); colourSpaceBIter != colourSpace.end(); colourSpaceBIter++) {
int gi=0;
for (colourSpaceGIter = colourSpaceBIter->begin(); colourSpaceGIter != colourSpaceBIter->end(); colourSpaceGIter++) {
int ri=0;
for (colourSpaceRIter = colourSpaceGIter->begin(); colourSpaceRIter != colourSpaceGIter->end(); colourSpaceRIter++) {
if (*colourSpaceRIter)
colours.push_back( Scalar(bi,gi,ri) );
ri++;
}
gi++;
}
bi++;
}
// For each colour
int segmentCount = 0;
for (colourIter = colours.begin(); colourIter != colours.end(); colourIter++) {
Mat label = Mat(segments.rows, segments.cols, CV_8UC3, *colourIter); // image filled with label colour.
Mat boolImage = Mat(segments.rows, segments.cols, CV_8UC3);
inRange(segments, *colourIter, *colourIter, boolImage); // which pixels in labeled image are identical to this label?
vector<vector<Point> > labelContours;
findContours(boolImage, labelContours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
// Loop through contours.
for (int idx = 0; idx < labelContours.size(); idx++) {
// get bounds for this contour.
Rect bounds = boundingRect(labelContours[idx]);
float area = contourArea(labelContours[idx]);
// Draw this contour on a new blank image
Mat maskImage = Mat::zeros(boolImage.rows, boolImage.cols, boolImage.type());
drawContours(maskImage, labelContours, idx, Scalar(255,255,255), CV_FILLED);
Mat patchROI = frame(bounds);
Mat maskROI = maskImage(bounds);
}
segmentCount++;
}