openCV C++ reduce objects/blobs to centroids - c++

Task:
I try to write a method, that reduces the objects in a binary image to their centroids (meaning center of mass).

Parameters:
Mat *pMA_Out, Mat *pMA_In, int mode, int method, unsigned int thickness
Body:
//Allocate contours
vector<vector<Point>> vvp_countours;
//Find countours
findContours(
*pMA_In,
vvp_countours,
mode,
method,
Point(0, 0));
//Get the moments
vector<Moments> v_moments(vvp_countours.size());
for(int c = 0; c < vvp_countours.size(); c++)
v_moments[c] = moments(vvp_countours[c], false);
//Get the mass centers
vector<Point2f> v_centroid(vvp_countours.size());
for(int c = 0; c < vvp_countours.size(); c++)
v_centroid[c] = Point2f(
v_moments[c].m10 / v_moments[c].m00,
v_moments[c].m01 / v_moments[c].m00);
//Create output image
Mat MA_tmp = Mat(pMA_In->size(), CV_8UC1, Scalar(0));
//Draw centroids
for(int c = 0; c < v_centroid.size(); c++)
line(
MA_tmp,
v_centroid[c],
v_centroid[c],
Scalar(255),
thickness,
8);
*pMA_Out = MA_tmp.clone();

Related

OpenCV code doesn't work on specific image

I am trying to run the followin code (based on this page) on an image, but it doesn't work:
Mat src=imread("img.jpg",1);
Mat tmp,thr;
cvtColor(src,tmp,CV_BGR2GRAY);
threshold(tmp,thr,200,255,THRESH_BINARY_INV);
vector< vector <Point> > contours;
vector< Vec4i > hierarchy;
Mat dst(src.rows,src.cols,CV_8UC1,Scalar::all(0));//Ceate Mat to draw contour
int box_w=10; // Define box width here
int box_h=10; // Define box height here
int threshold_perc=25; //perceantage value for eliminating the box according to pixel count inside the box
int threshold=(box_w*box_h*threshold_perc)/100;
findContours( thr, contours, hierarchy,CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE ); //Find contour
for( int i = 0; i< contours.size(); i++ ){
drawContours( dst,contours, i, Scalar(255,255,255),CV_FILLED, 8, hierarchy ); // Draw contour with thickness = filled
Rect r= boundingRect(contours[i]); // Find bounding rect
// Scan the image with in bounding box
for(int j=r.x;j<r.x+r.width;j=j+box_w){
for(int k=r.y;k<r.y+r.height;k=k+box_h){
Rect roi_rect(j,k,box_w,box_h);
Mat roi = dst(roi_rect);
int count = countNonZero(roi);
if(count > threshold)
rectangle(src, roi_rect, Scalar(255,0,0),1,8,0 );
}
}
}
imshow("src",src);
waitKey();
It works fine for any normal image, but for the images below, it either breaks or doesn't find the contour and draws boxes all over the image.
It says:
Unhandled exception at 0x00007FF9A72DA388 in test2.exe: Microsoft C++ exception: cv::Exception at memory location 0x000000FECC9DEAC0.
It breaks and points to here:
inline
Mat Mat::operator()( const Rect& roi ) const
{
return Mat(*this, roi);
}
in mat.inl.hpp.
What is wrong with my image? I have changed it from Gray-scale to RGB, but didn't help.
On the following image, it works fine:
As I commented, you're trying to access a region of the image that doesn't exist by using a rectangle of fixed size.
By intersecting the roi with the rectangle, you can avoid this problem:
Mat roi = dst(roi_rect & r);
The problem was that in the first images, the contour gets close to the boundaries of the image and in the bottom for loop of the program, it exceeds the coordinates. It was fixed with this:
// Scan the image with in bounding box
for (int j = r.x;j<r.x + r.width;j = j + box_w) {
for (int k = r.y;k<r.y + r.height;k = k + box_h) {
Rect roi_rect(j, k, box_w, box_h);
if (j + box_w < dst.cols && k + box_h < dst.rows)
{
Mat roi = dst(roi_rect);
int count = countNonZero(roi);
if (count > threshold)
rectangle(src, roi_rect, Scalar(0,0,255), 1, 8, 0);
}
}
}

Improve Text Binarization / OCR Preprocessing with OpenCV

I am building a scanner feature for my app and binarize the photo of the document with OpenCV:
// convert to greyscale
cv::Mat converted, blurred, blackAndWhite;
converted = cv::Mat(inputMatrix.rows, inputMatrix.cols, CV_8UC1);
cv::cvtColor(inputMatrix, converted, CV_BGR2GRAY );
// remove noise
cv::GaussianBlur(converted, blurred, cvSize(3,3), 0);
// adaptive threshold
cv::adaptiveThreshold(blackAndWhite, blackAndWhite, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, 15 , 9);
The result is okay, but scans from different scanner apps are much better. Especially very small, tiny sized text is much better:
Processed with opencv
Scanned With DropBox
What can I do, to improve my result?
May be the apps are using anti-aliasing to make their binarized output look nicer. To obtain a similar effect, I first tried binarizing the image, but the result didn't look very nice with all the jagged edges. Then I applied pyramid upsampling and then downsampling to the result, and the output was better.
I didn't use adaptive thresholding however. I segmented the text-like regions and processed those regions only, then pasted them to form the final images. It is a kind of local thresholding using the Otsu method or the k-means (using combinations of thr_roi_otsu, thr_roi_kmeans and proc_parts in the code). Below are some results.
Apply Otsu threshold to all text regions, then upsample followed by downsample:
Some text:
Full image:
Upsample input image, apply Otsu threshold to individual text regions, downsample the result:
Some text:
Full image:
/*
apply Otsu threshold to the region in mask
*/
Mat thr_roi_otsu(Mat& mask, Mat& im)
{
Mat bw = Mat::ones(im.size(), CV_8U) * 255;
vector<unsigned char> pixels(countNonZero(mask));
int index = 0;
for (int r = 0; r < mask.rows; r++)
{
for (int c = 0; c < mask.cols; c++)
{
if (mask.at<unsigned char>(r, c))
{
pixels[index++] = im.at<unsigned char>(r, c);
}
}
}
// threshold pixels
threshold(pixels, pixels, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
// paste pixels
index = 0;
for (int r = 0; r < mask.rows; r++)
{
for (int c = 0; c < mask.cols; c++)
{
if (mask.at<unsigned char>(r, c))
{
bw.at<unsigned char>(r, c) = pixels[index++];
}
}
}
return bw;
}
/*
apply k-means to the region in mask
*/
Mat thr_roi_kmeans(Mat& mask, Mat& im)
{
Mat bw = Mat::ones(im.size(), CV_8U) * 255;
vector<float> pixels(countNonZero(mask));
int index = 0;
for (int r = 0; r < mask.rows; r++)
{
for (int c = 0; c < mask.cols; c++)
{
if (mask.at<unsigned char>(r, c))
{
pixels[index++] = (float)im.at<unsigned char>(r, c);
}
}
}
// cluster pixels by gray level
int k = 2;
Mat data(pixels.size(), 1, CV_32FC1, &pixels[0]);
vector<float> centers;
vector<int> labels(countNonZero(mask));
kmeans(data, k, labels, TermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 10, 1.0), k, KMEANS_PP_CENTERS, centers);
// examine cluster centers to see which pixels are dark
int label0 = centers[0] > centers[1] ? 1 : 0;
// paste pixels
index = 0;
for (int r = 0; r < mask.rows; r++)
{
for (int c = 0; c < mask.cols; c++)
{
if (mask.at<unsigned char>(r, c))
{
bw.at<unsigned char>(r, c) = labels[index++] != label0 ? 255 : 0;
}
}
}
return bw;
}
/*
apply procfn to each connected component in the mask,
then paste the results to form the final image
*/
Mat proc_parts(Mat& mask, Mat& im, Mat (procfn)(Mat&, Mat&))
{
Mat tmp = mask.clone();
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(tmp, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
Mat byparts = Mat::ones(im.size(), CV_8U) * 255;
for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
{
Rect rect = boundingRect(contours[idx]);
Mat msk = mask(rect);
Mat img = im(rect);
// process the rect
Mat roi = procfn(msk, img);
// paste it to the final image
roi.copyTo(byparts(rect));
}
return byparts;
}
int _tmain(int argc, _TCHAR* argv[])
{
Mat im = imread("1.jpg", 0);
// detect text regions
Mat morph;
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
morphologyEx(im, morph, CV_MOP_GRADIENT, kernel, Point(-1, -1), 1);
// prepare a mask for text regions
Mat bw;
threshold(morph, bw, 0, 255, THRESH_BINARY | THRESH_OTSU);
morphologyEx(bw, bw, CV_MOP_DILATE, kernel, Point(-1, -1), 10);
Mat bw2x, im2x;
pyrUp(bw, bw2x);
pyrUp(im, im2x);
// apply Otsu threshold to all text regions, then upsample followed by downsample
Mat otsu1x = thr_roi_otsu(bw, im);
pyrUp(otsu1x, otsu1x);
pyrDown(otsu1x, otsu1x);
// apply k-means to all text regions, then upsample followed by downsample
Mat kmeans1x = thr_roi_kmeans(bw, im);
pyrUp(kmeans1x, kmeans1x);
pyrDown(kmeans1x, kmeans1x);
// upsample input image, apply Otsu threshold to all text regions, downsample the result
Mat otsu2x = thr_roi_otsu(bw2x, im2x);
pyrDown(otsu2x, otsu2x);
// upsample input image, apply k-means to all text regions, downsample the result
Mat kmeans2x = thr_roi_kmeans(bw2x, im2x);
pyrDown(kmeans2x, kmeans2x);
// apply Otsu threshold to individual text regions, then upsample followed by downsample
Mat otsuparts1x = proc_parts(bw, im, thr_roi_otsu);
pyrUp(otsuparts1x, otsuparts1x);
pyrDown(otsuparts1x, otsuparts1x);
// apply k-means to individual text regions, then upsample followed by downsample
Mat kmeansparts1x = proc_parts(bw, im, thr_roi_kmeans);
pyrUp(kmeansparts1x, kmeansparts1x);
pyrDown(kmeansparts1x, kmeansparts1x);
// upsample input image, apply Otsu threshold to individual text regions, downsample the result
Mat otsuparts2x = proc_parts(bw2x, im2x, thr_roi_otsu);
pyrDown(otsuparts2x, otsuparts2x);
// upsample input image, apply k-means to individual text regions, downsample the result
Mat kmeansparts2x = proc_parts(bw2x, im2x, thr_roi_kmeans);
pyrDown(kmeansparts2x, kmeansparts2x);
return 0;
}

OpenCV--how to get better hand contour from low quality gray image?

I need to get contour from hand image, usually I process image with 4 steps:
get raw RGB gray image from 3 channels to 1 channel:
cvtColor(sourceGrayImage, sourceGrayImage, COLOR_BGR2GRAY);
use Gaussian blur to filter gray image:
GaussianBlur(sourceGrayImage, sourceGrayImage, Size(3,3), 0);
binary gray image, I split image by height, normally I split image to 6 images by its height, then each one I do threshold process:
// we split source picture to binaryImageSectionCount(here it's 8) pieces by its height,
// then we for every piece, we do threshold,
// and at last we combine them agin to binaryImage
const binaryImageSectionCount = 8;
void GetBinaryImage(Mat &grayImage, Mat &binaryImage)
{
// get every partial gray image's height
int partImageHeight = grayImage.rows / binaryImageSectionCount;
for (int i = 0; i < binaryImageSectionCount; i++)
{
Mat partialGrayImage;
Mat partialBinaryImage;
Rect partialRect;
if (i != binaryImageSectionCount - 1)
{
// if it's not last piece, Rect's height should be partImageHeight
partialRect = Rect(0, i * partImageHeight, grayImage.cols, partImageHeight);
}
else
{
// if it's last piece, Rect's height should be (grayImage.rows - i * partImageHeight)
partialRect = Rect(0, i * partImageHeight, grayImage.cols, grayImage.rows - i * partImageHeight);
}
Mat partialResource = grayImage(partialRect);
partialResource.copyTo(partialGrayImage);
threshold( partialGrayImage, partialBinaryImage, 0, 255, THRESH_OTSU);
// combin partial binary image to one piece
partialBinaryImage.copyTo(binaryImage(partialRect));
///*stringstream resultStrm;
//resultStrm << "partial_" << (i + 1);
//string string = resultStrm.str();
//imshow(string, partialBinaryImage);
//waitKey(0);*/
}
imshow("result binary image.", binaryImage);
waitKey(0);
return;
}
use findcontour to get biggest area contour:
vector<vector<Point> > contours;
findContours(binaryImage, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
normally it works well,
But for some low quality gray image, it doesn't work,like below:
the complete code is here:
#include <opencv2/imgproc/imgproc.hpp>
#include<opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
// we split source picture to binaryImageSectionCount(here it's 8) pieces by its height,
// then we for every piece, we do threshold,
// and at last we combine them agin to binaryImage
const binaryImageSectionCount = 8;
void GetBinaryImage(Mat &grayImage, Mat &binaryImage)
{
// get every partial gray image's height
int partImageHeight = grayImage.rows / binaryImageSectionCount;
for (int i = 0; i < binaryImageSectionCount; i++)
{
Mat partialGrayImage;
Mat partialBinaryImage;
Rect partialRect;
if (i != binaryImageSectionCount - 1)
{
// if it's not last piece, Rect's height should be partImageHeight
partialRect = Rect(0, i * partImageHeight, grayImage.cols, partImageHeight);
}
else
{
// if it's last piece, Rect's height should be (grayImage.rows - i * partImageHeight)
partialRect = Rect(0, i * partImageHeight, grayImage.cols, grayImage.rows - i * partImageHeight);
}
Mat partialResource = grayImage(partialRect);
partialResource.copyTo(partialGrayImage);
threshold( partialGrayImage, partialBinaryImage, 0, 255, THRESH_OTSU);
// combin partial binary image to one piece
partialBinaryImage.copyTo(binaryImage(partialRect));
///*stringstream resultStrm;
//resultStrm << "partial_" << (i + 1);
//string string = resultStrm.str();
//imshow(string, partialBinaryImage);
//waitKey(0);*/
}
imshow("result binary image.", binaryImage);
waitKey(0);
return;
}
int main(int argc, _TCHAR* argv[])
{
// get image path
string imgPath("C:\\Users\\Alfred\\Desktop\\gray.bmp");
// read image
Mat src = imread(imgPath);
imshow("Source", src);
//medianBlur(src, src, 7);
cvtColor(src, src, COLOR_BGR2GRAY);
imshow("gray", src);
// do filter
GaussianBlur(src, src, Size(3,3), 0);
// binary image
Mat threshold_output(src.rows, src.cols, CV_8UC1, Scalar(0, 0, 0));
GetBinaryImage(src, threshold_output);
imshow("binaryImage", threshold_output);
// get biggest contour
vector<vector<Point> > contours;
findContours(threshold_output,contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
int biggestContourIndex = 0;
int maxContourArea = -1000;
for (int i = 0; i < contours.size(); i++)
{
if (contourArea(contours[i]) > maxContourArea)
{
maxContourArea = contourArea(contours[i]);
biggestContourIndex = i;
}
}
// show biggest contour
Mat biggestContour(threshold_output.rows, threshold_output.cols, CV_8UC1, Scalar(0, 0, 0));
drawContours(biggestContour, contours, biggestContourIndex, cv::Scalar(255,255,255), 2, 8, vector<Vec4i>(), 0, Point());
imshow("maxContour", biggestContour);
waitKey(0);
}
could anybody please help me to get a better hand contour result?
thanks!!!
I have the code snippet in python, you can follow the same approach in C:
img = cv2.imread(x, 1)
cv2.imshow("img",img)
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv2.imshow("gray",imgray)
#Code for histogram equalization
equ = cv2.equalizeHist(imgray)
cv2.imshow('equ', equ)
#Code for contrast limited adaptive histogram equalization
#clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
#cl2 = clahe.apply(imgray)
#cv2.imshow('clahe2', cl2)
This is the result I obtained:
If you're image is horribly bad you could try the code that I commented involving contrast limited adaptive histogram equalization.

Get Pixel Values along several lines from a mask which is tilted

currently I have segmented the object (rectangular) and now I want to create a Line profile. I dont know how to get along this line.
detected object
Aim is to get this:
object with lines
Update 14:25:
I know already the angle from the bounding rect and used this to calculate the shift in y-direction in order to rearrange the values to a new mat so that I only need go through the matrix to get a line profile.
Here my Code, but the rearrangement did not work.
Mat imgIn(SizeY, SizeX, CV_16U, &Wire[0]),
imgOut(SizeY, SizeX, CV_16U, Scalar(0)),
temp, drawing, mask, lineProfile(SizeY, SizeX, CV_16U, Scalar(0));
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
Point center;
char buffer[100];
bool found = false;
int rect_no (0);
double angle(0.0);
// Detecting outer contours
temp = ::adaptiveThreshold(imgIn, SizeY, SizeX, kernelSize, thresh, 0);
// Find contours
findContours(temp, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_TC89_KCOS, Point(0, 0) );
/// Find the rotated rectangles and ellipses for each contour
vector<RotatedRect> minRect( contours.size() );
for( int i = 0; i < contours.size(); i++ ) minRect[i] = minAreaRect( Mat(contours[i]) );
// Draw contours + rotated rects
drawing = Mat::zeros(temp.size(), CV_8U );
Point2f rect_points[4];
for( int i = 0; i< minRect.size(); i++ ){
if((float)minRect[i].boundingRect().height/(float)minRect[i].boundingRect().width > 3.0 && (float)minRect[i].boundingRect().height/(float)minRect[i].boundingRect().width < 4.9){
// rotated rectangle
minRect[i].points(rect_points);
for(int j = 0; j < 4; j++) line(drawing, rect_points[j], rect_points[(j+1)%4], Scalar(255), 1, 8);
//found = minRect[i].boundingRect().contains(Point(459, 512));
if(minRect[i].boundingRect().area() > 1000)
rect_no = i;
}
}
center = computeCentroid(drawing);
cv::floodFill(drawing, center, cv::Scalar(255));
drawing.convertTo(imgOut, CV_16U, 257.0);
imgIn.copyTo(imgOut, drawing);
// Calculate Wire SR_min
// Get angle of Wire
angle = (90 - (-1 * minRect[rect_no].angle))*(CV_PI/180);
for(int i = 0;i < SizeY;i++){
for(int j = 0;j < SizeX;j++){
if(imgOut.at<ushort>(i, j) != 0)
lineProfile.at<ushort>(i, j) = imgOut.at<ushort>((unsigned short)(i/cos(angle)), j);
}
}
for(int i = 0;i < SizeY;i++){
for(int j = 0;j < SizeX;j++){
*Wire++ = lineProfile.at<ushort>(i, j);//imgOut.at<ushort>(i, j);
}
}
If you know the coordinates of the beginning and the end of your line, getting the values at each point on the line should be easy with OpenCV's LineIterator. Feed it your image and your two points and let it work its magic.
If you are able to binarize the detected object image , then you could possibly able to apply Houghlines function of OpenCv . You can find it in the below link
http://docs.opencv.org/2.4/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.html

How to remove horizontal line from this x-ray image?

I'm new in opencv (c++) and I want to remove horizontal line from this x-ray image. But I can not.
These is my image:
What ideas on how to solve this task would you suggest? Or on what resource on the internet can I find help?
This is my c++ code
src = imread("C:/Users/Alireza/Desktop/New folder (3)/11.bmp");
cvtColor(src, gray, CV_RGB2GRAY);
imshow("Original Image", gray);
imwrite("Original Image.png", gray);
normalize(gray, gray, 0, 250, NORM_MINMAX, -1, Mat());
threshold(gray, thresh, 170, 255, THRESH_BINARY_INV);
vector< vector <Point> > contours;
vector< Vec4i > hierarchy;
int largest_contour_index = 0;
int largest_area = 0;
Mat alpha(src.size(), CV_8UC1, Scalar(0));
findContours(thresh, contours, hierarchy, CV_RETR_TREE,CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
for (int i = 0; i< contours.size(); i++)
{
double a = contourArea(contours[i], false);
if (a>largest_area)
{
largest_area = a;
largest_contour_index = i;
}
}
drawContours(alpha, contours, largest_contour_index,Scalar(255),CV_FILLED, 8, hierarchy);
vector<Mat> rgb;
split(src, rgb);
Mat rgba[4] = { rgb[0], rgb[1], rgb[2], alpha };
merge(rgba, 4, Tafrigh);
imshow("Tafrigh", Tafrigh);
imwrite("Tafrigh.png", Tafrigh);
Take a 2D FFT, take a look at the spectrum. You will see plenty of dots along the center y-axis. Suppress those dots , back transform, your vertical lines will be gone.
Below the result (since I don't have C++ and opencv installed) in Python, with sliders to vary the region to be suppressed. Consider it Pseudocode. This is still pretty rough as I'm not making a smooth transition between suppressed pixels and their neighbors here for simplicity.
%matplotlib inline
from __future__ import division
import numpy as np
import matplotlib.pyplot as p
from ipywidgets import *
from scipy import misc
f = misc.imread('xray_image_with_horizontal_lines.png')
a=np.fft.fftshift(np.fft.fft2(f))
def process(kx,ky):
p.figure(figsize=(12,8))
p.subplot(221)
p.imshow(f, cmap=p.cm.gray)
p.subplot(222)
p.imshow(np.abs(np.log(a)), cmap=p.cm.gray)
print np.shape(a)
b=np.zeros_like(a)
for i in range(639):
for j in range(406):
if not ( 320-kx<i<320+kx and (j<203-ky or j>203+ky)):
b[j,i]=a[j,i]
c=np.fft.ifft2(b)
p.subplot(223)
p.imshow(np.abs(np.log(b)), cmap=p.cm.gray)
p.subplot(224)
p.imshow(np.abs(c), cmap=p.cm.gray)
interact(process, kx=[1,20,1],ky=[1,20,1])
That's just an idea : keep mean line constant.
cv::Mat image = cv::imread("Tf6HO.png",CV_LOAD_IMAGE_GRAYSCALE);
vector<double> moyenne;
double minval,maxval;
minMaxLoc(image,&minval,&maxval);
imshow("original",image);
for (int i = 0; i < image.rows; i++)
{
double s=0;
// Caluclate mean for row i
for (int j=0;j<image.cols;j++)
s += image.at<uchar>(i,j);
// Store result in vector moyenne
moyenne.push_back(s/image.cols);
}
// Energy for row i equal to a weighted mean of row in [i-nbInf,i+nbSup]
int nbInf=32,nbSup=0;
for (int i = 0; i < image.rows; i++)
{
double s=0,p=0;
// weighted mean (border effect process with max and min method
for (int j = max(0, i - nbInf); j <= min(image.rows - 1, i + nbSup); j++)
{
s+=moyenne[j]*1./(1+abs(i-j));
p+=1./(1+abs(i-j));
}
// Weighted mean
s/=p;
// process pixel in row i : mean of row i equal to s
for (int j=0;j<image.cols;j++)
image.at<uchar>(i,j) =saturate_cast<uchar>((image.at<uchar>(i,j)-moyenne[i])+s);
}
imshow("process",image);
waitKey();
or with reduce resolution
if you want to improve you can read this paper and bibliography