I am beginning in image processing, I would like to make a vortex in the center of an image with OpenCV in C++.
My first intuition would be to make a rotation and a translation, but I can't figure it out how to make the equation, or there is a simple way to make it.
There is an example of what I want to achieve : Image
You can try the code I shamelessly stole from here:
#include <iostream>
#include <cmath>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void trackbar_callback(int value, void* userdata)
{
Mat* image = (Mat*)userdata;
const float width = (float)image->rows;
const float height = (float)image->cols;
Mat result(image->rows, image->cols, image->type());
for (int i = 0; i < image->rows; i++) {
for (int j = 0; j < image->cols; j++) {
float x = (j / height) - 0.5f;
float y = (i / width) - 0.5f;
float angle = atan2f(y, x);
float radius = sqrtf((x * x) + (y * y));
angle += radius * (value / 10.0f);
float xr = ((radius * sinf(angle)) + 0.5f) * width;
float yr = ((radius * cosf(angle)) + 0.5f) * height;
int k = (int)std::min(width - 1, std::max(0.0f, xr));
int m = (int)std::min(height - 1, std::max(0.0f, yr));
uchar* src = image->ptr<uchar>(k, m);
uchar* out = result.ptr<uchar>(i, j);
out[0] = src[0];
out[1] = src[1];
out[2] = src[2];
}
}
imshow("Result Image", result);
}
int main(int argc, char** argv)
{
Mat image = imread("data/lena.jpg", CV_LOAD_IMAGE_COLOR);
if (image.empty())
{
printf("No image data \n");
return -1;
}
try
{
const cv::String name_window = "Twirl Image";
const cv::String name_trackbar = "Twirl";
namedWindow(name_window);
createTrackbar(name_trackbar, name_window, NULL, 200, trackbar_callback, &image);
setTrackbarPos(name_trackbar, name_window, 80);
imshow(name_window, image);
waitKey(0);
destroyAllWindows();
}
catch (cv::Exception& e)
{
const char* err_msg = e.what();
std::cout << "exception caught: " << err_msg << std::endl;
}
return 0;
}
Related
I tried to perform object detection using the yolov5 model with c++. I have a custom-trained yolov5 model which is working perfectly fine in python but my whole complete setup is in c++ thereby I have to switch. So I have converted the yolov5s model into ONNX format and tried to run it as by "https://github.com/doleron/yolov4-opencv-cpp-python"1. Unfortunately, I'm getting multiple bounding boxes in the top left corner as in the image.
I don't know how to eliminate this kind of error, but when I used the inbuilt pre-train yolov5s model the c++ code is detecting and worked perfectly. Similarly, when I used the custom-trained model in python it's working perfectly.
Here is my c++ code for object detection using c++
#include <fstream>
#include <opencv2/opencv.hpp>
std::vector<std::string> load_class_list()
{
std::vector<std::string> class_list;
std::ifstream ifs("config_files/classes.txt");
std::string line;
while (getline(ifs, line))
{
class_list.push_back(line);
}
return class_list;
}
void load_net(cv::dnn::Net &net, bool is_cuda)
{
auto result = cv::dnn::readNet("config_files/yolov5s_custom.onnx");
if (is_cuda)
{
std::cout << "Attempty to use CUDA\n";
result.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
result.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16);
}
else
{
std::cout << "Running on CPU\n";
result.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
result.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
}
net = result;
}
const std::vector<cv::Scalar> colors = {cv::Scalar(255, 255, 0), cv::Scalar(0, 255, 0), cv::Scalar(0, 255, 255), cv::Scalar(255, 0, 0)};
const float INPUT_WIDTH = 640.0;
const float INPUT_HEIGHT = 640.0;
const float SCORE_THRESHOLD = 0.2;
const float NMS_THRESHOLD = 0.4;
const float CONFIDENCE_THRESHOLD = 0.4;
struct Detection
{
int class_id;
float confidence;
cv::Rect box;
};
cv::Mat format_yolov5(const cv::Mat &source) {
int col = source.cols;
int row = source.rows;
int _max = MAX(col, row);
cv::Mat result = cv::Mat::zeros(_max, _max, CV_8UC3);
source.copyTo(result(cv::Rect(0, 0, col, row)));
return result;
}
void detect(cv::Mat &image, cv::dnn::Net &net, std::vector<Detection> &output, const std::vector<std::string> &className) {
cv::Mat blob;
auto input_image = format_yolov5(image);
cv::dnn::blobFromImage(input_image, blob, 1./255., cv::Size(INPUT_WIDTH, INPUT_HEIGHT), cv::Scalar(), true, false);
net.setInput(blob);
std::vector<cv::Mat> outputs;
net.forward(outputs, net.getUnconnectedOutLayersNames());
float x_factor = input_image.cols / INPUT_WIDTH;
float y_factor = input_image.rows / INPUT_HEIGHT;
float *data = (float *)outputs[0].data;
const int dimensions = 85;
const int rows = 25200;
std::vector<int> class_ids;
std::vector<float> confidences;
std::vector<cv::Rect> boxes;
for (int i = 0; i < rows; ++i) {
float confidence = data[4];
if (confidence >= CONFIDENCE_THRESHOLD) {
float * classes_scores = data + 5;
cv::Mat scores(1, className.size(), CV_32FC1, classes_scores);
cv::Point class_id;
double max_class_score;
minMaxLoc(scores, 0, &max_class_score, 0, &class_id);
if (max_class_score > SCORE_THRESHOLD) {
confidences.push_back(confidence);
class_ids.push_back(class_id.x);
float x = data[0];
float y = data[1];
float w = data[2];
float h = data[3];
int left = int((x - 0.5 * w) * x_factor);
int top = int((y - 0.5 * h) * y_factor);
int width = int(w * x_factor);
int height = int(h * y_factor);
boxes.push_back(cv::Rect(left, top, width, height));
}
}
data += 85;
}
std::vector<int> nms_result;
cv::dnn::NMSBoxes(boxes, confidences, SCORE_THRESHOLD, NMS_THRESHOLD, nms_result);
for (int i = 0; i < nms_result.size(); i++) {
int idx = nms_result[i];
Detection result;
result.class_id = class_ids[idx];
result.confidence = confidences[idx];
result.box = boxes[idx];
output.push_back(result);
}
}
int main(int argc, char **argv)
{
std::vector<std::string> class_list = load_class_list();
cv::Mat frame;
cv::VideoCapture capture("sample.mp4");
if (!capture.isOpened())
{
std::cerr << "Error opening video file\n";
return -1;
}
bool is_cuda = argc > 1 && strcmp(argv[1], "cuda") == 0;
cv::dnn::Net net;
load_net(net, is_cuda);
auto start = std::chrono::high_resolution_clock::now();
int frame_count = 0;
float fps = -1;
int total_frames = 0;
while (true)
{
capture.read(frame);
if (frame.empty())
{
std::cout << "End of stream\n";
break;
}
std::vector<Detection> output;
detect(frame, net, output, class_list);
frame_count++;
total_frames++;
int detections = output.size();
for (int i = 0; i < detections; ++i)
{
auto detection = output[i];
auto box = detection.box;
auto classId = detection.class_id;
const auto color = colors[classId % colors.size()];
cv::rectangle(frame, box, color, 3);
cv::rectangle(frame, cv::Point(box.x, box.y - 20), cv::Point(box.x + box.width, box.y), color, cv::FILLED);
cv::putText(frame, class_list[classId].c_str(), cv::Point(box.x, box.y - 5), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));
}
if (frame_count >= 30)
{
auto end = std::chrono::high_resolution_clock::now();
fps = frame_count * 1000.0 / std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
frame_count = 0;
start = std::chrono::high_resolution_clock::now();
}
if (fps > 0)
{
std::ostringstream fps_label;
fps_label << std::fixed << std::setprecision(2);
fps_label << "FPS: " << fps;
std::string fps_label_str = fps_label.str();
cv::putText(frame, fps_label_str.c_str(), cv::Point(10, 25), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
}
cv::imshow("output", frame);
if (cv::waitKey(1) != -1)
{
capture.release();
std::cout << "finished by user\n";
break;
}
}
std::cout << "Total frames: " << total_frames << "\n";
return 0;
}
Kindly guide me on how to eliminate these multiple boxes on the output video stream.
With compiling this project I have several issues:
Due to "file not found" I had to change header
from:
#include <cv.h>
#include <highgui.h>
to:
#include <opencv.hpp>
#include <highgui.hpp>
that solved Include issues but compiling give several more faults:
What I'm doing wrong?
Below whole code:
#include <stdio.h>
#include <unistd.h>
#include <opencv.hpp>
#include <highgui.hpp>
int main(int argc, char **argv)
{
// get command line parameters
if(argc < 6)
{
printf("Usage: %s <video> <vga window x> <vga window y> <vga width> <vga height>\n", argv[0]);
printf("Usage: %s <camera id> <vga window x> <vga window y> <vga width> <vga height>\n", argv[0]);
exit(0);
}
const char *filename = argv[1];
int vgaX = atoi(argv[2]);
int vgaY = atoi(argv[3]);
int vgaWidth = atoi(argv[4]);
int vgaHeight = atoi(argv[5]);
int outputPixelCount = vgaWidth * vgaHeight;
// opencv initializations
CvCapture* cap = cvCaptureFromFile(filename);
int isVideoFile = cap != 0;
if (!cap)
cap = cvCaptureFromCAM(atoi(filename));
if (!cap)
{
printf("Could not open file/camera!\n");
exit(1);
}
IplImage* frame = cvQueryFrame(cap); // get first frame for size
if (!frame)
{
printf("The Video is empty!\n");
cvReleaseCapture(&cap);
exit(1);
}
IplImage* edges = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 1);
IplImage* lines = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 3);
IplImage* out = cvCreateImage(cvSize(vgaWidth, vgaHeight), IPL_DEPTH_8U, 3);
int outStep = out->widthStep;
int outChannels = out->nChannels;
unsigned char *outData = (unsigned char*)out->imageData;
// position windows // TODO: make debug output windows optional?
cvNamedWindow("frame", CV_WINDOW_AUTOSIZE);
cvMoveWindow("frame", 0, 32);
cvNamedWindow("edges", CV_WINDOW_AUTOSIZE);
cvMoveWindow("edges", frame->width, 32);
cvNamedWindow("lines", CV_WINDOW_AUTOSIZE);
cvMoveWindow("lines", 2 * frame->width, 32);
cvNamedWindow("out", CV_WINDOW_AUTOSIZE);
cvMoveWindow("out", vgaX, vgaY);
while (42)
{
frame = cvQueryFrame(cap);
if (!frame || (cvWaitKey(1) & 0xff) == 'q')
break;
cvShowImage("frame", frame);
// edge detection
cvCanny(frame, edges, 128.0, 130.0, 3); // TODO: tweakable parameters?
cvShowImage("edges", edges);
// get contours
CvMemStorage *storage = cvCreateMemStorage(0);
CvSeq *contours;
int contourCount = cvFindContours(
edges, storage, &contours, sizeof(CvContour),
CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));
cvZero(lines);
cvDrawContours(lines, contours, cvScalar(32, 255, 32, 255), cvScalarAll(0), 100, 1, 8, cvPoint(0, 0));
cvShowImage("lines", lines);
// calculate total length over all contours
float contourLengthSum = 0.0f;
for(CvSeq *c = contours; c; c = c->h_next)
{
for(int i = 0; i < c->total - 1; i++)
{
CvPoint *p0 = CV_GET_SEQ_ELEM(CvPoint, c, i);
CvPoint *p1 = CV_GET_SEQ_ELEM(CvPoint, c, i + 1);
int dx = p1->x - p0->x;
int dy = p1->y - p0->y;
contourLengthSum += sqrtf(dx * dx + dy * dy);
}
}
float factor = (float)outputPixelCount / contourLengthSum;
// write output image
int cx = 0, cy = 0;
float xScale = 255.0f / frame->width;
float yScale = 255.0f / frame->height;
unsigned char *dp = &outData[1];
for(CvSeq *c = contours; c; c = c->h_next)
{
CvPoint *p0 = CV_GET_SEQ_ELEM(CvPoint, c, 0);
for(int i = 1; i < c->total; i++)
{
CvPoint *p1 = CV_GET_SEQ_ELEM(CvPoint, c, i);
float x0x1 = p1->x - p0->x;
float y0y1 = p1->y - p0->y;
int n = (int)(sqrtf(x0x1 * x0x1 + y0y1 * y0y1) * factor);
float x = (float)p0->x * xScale;
float y = 255.0f - (float)p0->y * yScale;
float dt = 1.0f / (float)(n - 1);
float dx = dt * x0x1 * xScale;
float dy = dt * -y0y1 * yScale;
for (int j = 0; j < n; j++)
{
dp[0] = (unsigned char)x;
dp[1] = (unsigned char)y;
x += dx;
y += dy;
dp += outChannels;
if (++cx == vgaWidth)
{
cx = 0;
dp = &outData[++cy * outStep + 1];
if (cy == vgaHeight)
goto full;
}
}
p0 = p1;
}
}
// fill last few pixels with last pixel value, if there are any left
for (; cy < vgaHeight; cy++)
{
for (;cx < vgaWidth; cx++)
{
outData[cy * outStep + cx * outChannels + 1] = 0;
outData[cy * outStep + cx * outChannels + 2] = 0;
}
cx = 0;
}
full:
cvReleaseMemStorage(&storage);
cvShowImage("out", out);
//if (isVideoFile)
// usleep(8000); // TODO: proper synchronization
}
cvReleaseImage(&out);
cvReleaseImage(&lines);
cvReleaseImage(&edges);
cvReleaseCapture(&cap);
return 0;
}
Looks like you are using old OpenCV API. They are replaced with new methods. Your code does not work with recent OpenCV 4.3.0. cvCaptureFromFile, cvCaptureFromCAM and some others exists in 4.0.0-rc of OpenCV docs.
https://docs.opencv.org/4.0.0-rc/dd/d01/group__videoio__c.html
After 4.0.0-rc of OpenCV the documentation points to videoio.
https://docs.opencv.org/master/dd/de7/group__videoio.html
Further Reference:
https://answers.opencv.org/question/55344/undeclared-indentifier-opencv-cvcapturefromcam-and-cvqueryframe/
Also if you have include problems in new OpenCV version try,
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
I define a function void segRgb(Mat &src, Mat &dst, Rect roi), using which I try to segment the region of region (ROI) of an input RGB image by simply thresholding a lumped pixel intensities derived from R, G and B channels. Here below is the code of the function:
void segRgb(Mat &src, Mat &dst, Rect roi)
{
uchar *bgrdata = src.data;
uchar *outdata = dst.data;
int ystart = roi.y;
int yend = roi.y + roi.height;
int xstart = roi.x;
int xend = roi.x+roi.width;
int step1 = src.cols-roi.width;
int step3 = 3*step1;
int start1 = roi.y*src.cols+roi.x;
int start3 = 3*start1;
bgrdata += start3;
outdata += start1;
uchar r, g, b;
double score=0.0;
for(int i=ystart; i<yend; i++)
{
qDebug()<<"Rows: "<<i;
for(int j=xstart; j<xend; j++)
{
b = *bgrdata++;
g = *bgrdata++;
r = *bgrdata++;
score = 0.21*r+0.72*g+0.07*b; //a simple rule to lump RGB values
if(score>100)
{
*outdata = 255;
}
else
{
*outdata = 0;
}
outdata++;
}
outdata+=step1;
bgrdata+=step3;
}
}
Following is my test code for the function:
Rect cvRect = Rect(10,50,256,256);
Mat dst;
segRgb(im, dst, cvRect); //im is a loaded Matrix of 427*640*3, CV_8UC3
namedWindow("Thresholded");
imshow("Thresholed", dst);
I run the codes above. The function segRgb does not work for some reason. No image is shown. Actually, the loop inside the segRgb does not proceed. Anyone can point to the problem, debug my codes bit? Thanks!
void segRgb(Mat &src, Mat &dst, Rect roi)
{
uchar *bgrdata = src.data;
uchar *outdata = dst.data;
int ystart = roi.y;
int yend = roi.y + roi.height;
int xstart = roi.x;
int xend = roi.x + roi.width;
int step1 = src.cols - roi.width;
int step3 = 3 * step1;
int start1 = roi.y*src.cols + roi.x;
int start3 = 3 * start1;
bgrdata += start3;
outdata += start1;
uchar r, g, b;
double score = 0.0;
for (int i = ystart; i < yend; i++)
{
cout << "Rows: " << i;
for (int j = xstart; j < xend; j++)
{
b = *bgrdata++;
g = *bgrdata++;
r = *bgrdata++;
score = 0.21*r + 0.72*g + 0.07*b; //a simple rule to lump RGB values
if (score > 100)
{
*outdata = 255;
}
else
{
*outdata = 0;
}
outdata++;
}
outdata += step1;
bgrdata += step3;
}
}
int main() {
Mat im = imread("urimage");
Rect cvRect = Rect(10, 50, 256, 256);
// you have to allocate a size for the dst Mat otherwise the uchar* output you point to above will be garbage
Mat dst(im.size(),im.type());
segRgb(im, dst, cvRect); //im is a loaded Matrix of 427*640*3, CV_8UC3
//Resize you dst or you can change a bit in your function paramters to get it directly
dst=Mat(dst, cvRect);
namedWindow("Thresholded");
imshow("Thresholed", dst);
waitKey(0);
}
I'm trying to Shear an image along the X-axis using OpenCV to load the image, and the following algorithm to shear the image: x′=x+y·Bx, but for some reason, I end up with the following shear:
My source code looks like this:
#include "stdafx.h"
#include "opencv2\opencv.hpp"
using namespace std;
using namespace cv;
int main()
{
Mat src = imread("B2DBy.jpg", 1);
if (src.empty())
cout << "Error: Loading image" << endl;
int r1, c1; // tranformed point
int rows, cols; // original image rows and columns
rows = src.rows;
cols = src.cols;
float Bx = 2; // amount of shearing in x-axis
float By = 0; // amount of shearing in y-axis
int maxXOffset = abs(cols * Bx);
int maxYOffset = abs(rows * By);
Mat out = Mat::ones(src.rows + maxYOffset, src.cols + maxXOffset, src.type()); // create output image to be the same as the source
for (int r = 0; r < out.rows; r++) // loop through the image
{
for (int c = 0; c < out.cols; c++)
{
r1 = r + c * By - maxYOffset; // map old point to new
c1 = r * Bx + c - maxXOffset;
if (r1 >= 0 && r1 <= out.rows && c1 >= 0 && c1 <= out.cols) // check if the point is within the boundaries
{
out.at<uchar>(r, c) = src.at<uchar>(r1, c1); // set value
}
}
}
namedWindow("Source image", CV_WINDOW_AUTOSIZE);
namedWindow("Rotated image", CV_WINDOW_AUTOSIZE);
imshow("Source image", src);
imshow("Rotated image", out);
waitKey(0);
return 0;
}
EDIT
Fixed it myself.
Didn't need to substract the offset. Heres the updated source code:
Mat forward(Mat img) {
Mat umg = img;
int y1, x1; // tranformed point
int rows, cols; // original image rows and columns
rows = umg.rows;
cols = umg.cols;
float Bx = 0.7; // amount of shearing in x-axis
float By = 0; // amount of shearing in y-axis
int maxXOffset = abs(rows * Bx);
int maxYOffset = abs(cols * By);
Mat out = Mat::ones(rows + maxYOffset, cols + maxXOffset, umg.type()); // create output image to be the same as the source
for (int y = 0; y < rows; y++) // loop through the image
{
for (int x = 0; x < cols; x++)
{
y1 = y + x * By; // map old point to new
x1 = y * Bx + x;
out.at<uchar>(y1, x1) = umg.at<uchar>(y, x); // set value
}
}
return out;
}
Mat backwards(Mat img) {
Mat umg = img;
int y1, x1; // tranformed point
int rows, cols; // original image rows and columns
rows = umg.rows;
cols = umg.cols;
float Bx = 0.7; // amount of shearing in x-axis
float By = 0; // amount of shearing in y-axis
int maxXOffset = abs(rows * Bx);
int maxYOffset = abs(cols * By);
Mat out = Mat::ones(rows + maxYOffset, cols + maxXOffset, umg.type()); // create output image to be the same as the source
for (int y = 0; y < rows; y++) // loop through the image
{
for (int x = 0; x < cols; x++)
{
//y1 = y + x * By; // map old point to new
//x1 = y * Bx + x;
y1 = (1 / (1 - Bx*By)) * (y + x * By);
x1 = (1 / (1 - Bx*By)) * (y * Bx + x);
out.at<uchar>(y1, x1) = umg.at<uchar>(y, x); // set value
}
}
return out;
}
int main()
{
Mat src = imread("B2DBy.jpg", 0);
if (src.empty())
cout << "Error: Loading image" << endl;
Mat forwards = forward(src);
Mat back = backwards(src);
namedWindow("Source image", CV_WINDOW_NORMAL);
imshow("Source image", src);
imshow("back", back);
imshow("forward image", forwards);
waitKey(0);
return 0;
}
I found some time to work on this.
Now I understand what you tried to achieve with the offset computation, but I'm not sure whether yours is correct.
Just change all the cv::Vec3b to unsigned char or uchar and load as grayscale, if wanted.
Please try this code and maybe you'll find your error:
// no interpolation yet
// cv::Vec3b only
cv::Mat shear(const cv::Mat & input, float Bx, float By)
{
if (Bx*By == 1)
{
throw("Shearing: Bx*By==1 is forbidden");
}
if (input.type() != CV_8UC3) return cv::Mat();
// shearing:
// x'=x+y·Bx
// y'=y+x*By
// shear the extreme positions to find out new image size:
std::vector<cv::Point2f> extremePoints;
extremePoints.push_back(cv::Point2f(0, 0));
extremePoints.push_back(cv::Point2f(input.cols, 0));
extremePoints.push_back(cv::Point2f(input.cols, input.rows));
extremePoints.push_back(cv::Point2f(0, input.rows));
for (unsigned int i = 0; i < extremePoints.size(); ++i)
{
cv::Point2f & pt = extremePoints[i];
pt = cv::Point2f(pt.x + pt.y*Bx, pt.y + pt.x*By);
}
cv::Rect offsets = cv::boundingRect(extremePoints);
cv::Point2f offset = -offsets.tl();
cv::Size resultSize = offsets.size();
cv::Mat shearedImage = cv::Mat::zeros(resultSize, input.type()); // every pixel here is implicitely shifted by "offset"
// perform the shearing by back-transformation
for (int j = 0; j < shearedImage.rows; ++j)
{
for (int i = 0; i < shearedImage.cols; ++i)
{
cv::Point2f pp(i, j);
pp = pp - offset; // go back to original coordinate system
// go back to original pixel:
// x'=x+y·Bx
// y'=y+x*By
// y = y'-x*By
// x = x' -(y'-x*By)*Bx
// x = +x*By*Bx - y'*Bx +x'
// x*(1-By*Bx) = -y'*Bx +x'
// x = (-y'*Bx +x')/(1-By*Bx)
cv::Point2f p;
p.x = (-pp.y*Bx + pp.x) / (1 - By*Bx);
p.y = pp.y - p.x*By;
if ((p.x >= 0 && p.x < input.cols) && (p.y >= 0 && p.y < input.rows))
{
// TODO: interpolate, if wanted (p is floating point precision and can be placed between two pixels)!
shearedImage.at<cv::Vec3b>(j, i) = input.at<cv::Vec3b>(p);
}
}
}
return shearedImage;
}
int main(int argc, char* argv[])
{
cv::Mat input = cv::imread("C:/StackOverflow/Input/Lenna.png");
cv::Mat output = shear(input, 0.7, 0);
//cv::Mat output = shear(input, -0.7, 0);
//cv::Mat output = shear(input, 0, 0.7);
cv::imshow("input", input);
cv::imshow("output", output);
cv::waitKey(0);
return 0;
}
Giving me these outputs for the 3 sample lines:
#robot_sherrick answered me this question, this is a follow-up question for his answer.
cv::SimpleBlobDetector in Opencv 2.4 looks very exciting but I am not sure I can make it work for more detailed data extraction.
I have the following concerns:
if this only returns center of the blob, I can't have an entire, labelled Mat, can I?
how can I access the features of the detected blobs like area, convexity, color and so on?
can I display an exact segmentation with this? (like with say, waterfall)
So the code should look something like this:
cv::Mat inputImg = imread(image_file_name, CV_LOAD_IMAGE_COLOR); // Read a file
cv::SimpleBlobDetector::Params params;
params.minDistBetweenBlobs = 10.0; // minimum 10 pixels between blobs
params.filterByArea = true; // filter my blobs by area of blob
params.minArea = 20.0; // min 20 pixels squared
params.maxArea = 500.0; // max 500 pixels squared
SimpleBlobDetector myBlobDetector(params);
std::vector<cv::KeyPoint> myBlobs;
myBlobDetector.detect(inputImg, myBlobs);
If you then want to have these keypoints highlighted on your image:
cv::Mat blobImg;
cv::drawKeypoints(inputImg, myBlobs, blobImg);
cv::imshow("Blobs", blobImg);
To access the info in the keypoints, you then just access each element like so:
for(std::vector<cv::KeyPoint>::iterator blobIterator = myBlobs.begin(); blobIterator != myBlobs.end(); blobIterator++){
std::cout << "size of blob is: " << blobIterator->size << std::endl;
std::cout << "point is at: " << blobIterator->pt.x << " " << blobIterator->pt.y << std::endl;
}
Note: this has not been compiled and may have typos.
Here is a version that will allow you to get the last contours back, via the getContours() method. They will match up by index to the keypoints.
class BetterBlobDetector : public cv::SimpleBlobDetector
{
public:
BetterBlobDetector(const cv::SimpleBlobDetector::Params ¶meters = cv::SimpleBlobDetector::Params());
const std::vector < std::vector<cv::Point> > getContours();
protected:
virtual void detectImpl( const cv::Mat& image, std::vector<cv::KeyPoint>& keypoints, const cv::Mat& mask=cv::Mat()) const;
virtual void findBlobs(const cv::Mat &image, const cv::Mat &binaryImage,
std::vector<Center> ¢ers, std::vector < std::vector<cv::Point> >&contours) const;
};
Then cpp
using namespace cv;
BetterBlobDetector::BetterBlobDetector(const SimpleBlobDetector::Params ¶meters)
{
}
void BetterBlobDetector::findBlobs(const cv::Mat &image, const cv::Mat &binaryImage,
vector<Center> ¢ers, std::vector < std::vector<cv::Point> >&curContours) const
{
(void)image;
centers.clear();
curContours.clear();
std::vector < std::vector<cv::Point> >contours;
Mat tmpBinaryImage = binaryImage.clone();
findContours(tmpBinaryImage, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
for (size_t contourIdx = 0; contourIdx < contours.size(); contourIdx++)
{
Center center;
center.confidence = 1;
Moments moms = moments(Mat(contours[contourIdx]));
if (params.filterByArea)
{
double area = moms.m00;
if (area < params.minArea || area >= params.maxArea)
continue;
}
if (params.filterByCircularity)
{
double area = moms.m00;
double perimeter = arcLength(Mat(contours[contourIdx]), true);
double ratio = 4 * CV_PI * area / (perimeter * perimeter);
if (ratio < params.minCircularity || ratio >= params.maxCircularity)
continue;
}
if (params.filterByInertia)
{
double denominator = sqrt(pow(2 * moms.mu11, 2) + pow(moms.mu20 - moms.mu02, 2));
const double eps = 1e-2;
double ratio;
if (denominator > eps)
{
double cosmin = (moms.mu20 - moms.mu02) / denominator;
double sinmin = 2 * moms.mu11 / denominator;
double cosmax = -cosmin;
double sinmax = -sinmin;
double imin = 0.5 * (moms.mu20 + moms.mu02) - 0.5 * (moms.mu20 - moms.mu02) * cosmin - moms.mu11 * sinmin;
double imax = 0.5 * (moms.mu20 + moms.mu02) - 0.5 * (moms.mu20 - moms.mu02) * cosmax - moms.mu11 * sinmax;
ratio = imin / imax;
}
else
{
ratio = 1;
}
if (ratio < params.minInertiaRatio || ratio >= params.maxInertiaRatio)
continue;
center.confidence = ratio * ratio;
}
if (params.filterByConvexity)
{
vector < Point > hull;
convexHull(Mat(contours[contourIdx]), hull);
double area = contourArea(Mat(contours[contourIdx]));
double hullArea = contourArea(Mat(hull));
double ratio = area / hullArea;
if (ratio < params.minConvexity || ratio >= params.maxConvexity)
continue;
}
center.location = Point2d(moms.m10 / moms.m00, moms.m01 / moms.m00);
if (params.filterByColor)
{
if (binaryImage.at<uchar> (cvRound(center.location.y), cvRound(center.location.x)) != params.blobColor)
continue;
}
//compute blob radius
{
vector<double> dists;
for (size_t pointIdx = 0; pointIdx < contours[contourIdx].size(); pointIdx++)
{
Point2d pt = contours[contourIdx][pointIdx];
dists.push_back(norm(center.location - pt));
}
std::sort(dists.begin(), dists.end());
center.radius = (dists[(dists.size() - 1) / 2] + dists[dists.size() / 2]) / 2.;
}
centers.push_back(center);
curContours.push_back(contours[contourIdx]);
}
static std::vector < std::vector<cv::Point> > _contours;
const std::vector < std::vector<cv::Point> > BetterBlobDetector::getContours() {
return _contours;
}
void BetterBlobDetector::detectImpl(const cv::Mat& image, std::vector<cv::KeyPoint>& keypoints, const cv::Mat&) const
{
//TODO: support mask
_contours.clear();
keypoints.clear();
Mat grayscaleImage;
if (image.channels() == 3)
cvtColor(image, grayscaleImage, CV_BGR2GRAY);
else
grayscaleImage = image;
vector < vector<Center> > centers;
vector < vector<cv::Point> >contours;
for (double thresh = params.minThreshold; thresh < params.maxThreshold; thresh += params.thresholdStep)
{
Mat binarizedImage;
threshold(grayscaleImage, binarizedImage, thresh, 255, THRESH_BINARY);
vector < Center > curCenters;
vector < vector<cv::Point> >curContours, newContours;
findBlobs(grayscaleImage, binarizedImage, curCenters, curContours);
vector < vector<Center> > newCenters;
for (size_t i = 0; i < curCenters.size(); i++)
{
bool isNew = true;
for (size_t j = 0; j < centers.size(); j++)
{
double dist = norm(centers[j][ centers[j].size() / 2 ].location - curCenters[i].location);
isNew = dist >= params.minDistBetweenBlobs && dist >= centers[j][ centers[j].size() / 2 ].radius && dist >= curCenters[i].radius;
if (!isNew)
{
centers[j].push_back(curCenters[i]);
size_t k = centers[j].size() - 1;
while( k > 0 && centers[j][k].radius < centers[j][k-1].radius )
{
centers[j][k] = centers[j][k-1];
k--;
}
centers[j][k] = curCenters[i];
break;
}
}
if (isNew)
{
newCenters.push_back(vector<Center> (1, curCenters[i]));
newContours.push_back(curContours[i]);
//centers.push_back(vector<Center> (1, curCenters[i]));
}
}
std::copy(newCenters.begin(), newCenters.end(), std::back_inserter(centers));
std::copy(newContours.begin(), newContours.end(), std::back_inserter(contours));
}
for (size_t i = 0; i < centers.size(); i++)
{
if (centers[i].size() < params.minRepeatability)
continue;
Point2d sumPoint(0, 0);
double normalizer = 0;
for (size_t j = 0; j < centers[i].size(); j++)
{
sumPoint += centers[i][j].confidence * centers[i][j].location;
normalizer += centers[i][j].confidence;
}
sumPoint *= (1. / normalizer);
KeyPoint kpt(sumPoint, (float)(centers[i][centers[i].size() / 2].radius));
keypoints.push_back(kpt);
_contours.push_back(contours[i]);
}
}
//Access SimpleBlobDetector datas for video
#include "opencv2/imgproc/imgproc.hpp" //
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <math.h>
#include <vector>
#include <fstream>
#include <string>
#include <sstream>
#include <algorithm>
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/features2d/features2d.hpp"
using namespace cv;
using namespace std;
int main(int argc, char *argv[])
{
const char* fileName ="C:/Users/DAGLI/Desktop/videos/new/m3.avi";
VideoCapture cap(fileName); //
if(!cap.isOpened()) //
{
cout << "Couldn't open Video " << fileName << "\n";
return -1;
}
for(;;) // videonun frameleri icin sonsuz dongu
{
Mat frame,labelImg;
cap >> frame;
if(frame.empty()) break;
//imshow("main",frame);
Mat frame_gray;
cvtColor(frame,frame_gray,CV_RGB2GRAY);
//////////////////////////////////////////////////////////////////////////
// convert binary_image
Mat binaryx;
threshold(frame_gray,binaryx,120,255,CV_THRESH_BINARY);
Mat src, gray, thresh, binary;
Mat out;
vector<KeyPoint> keyPoints;
SimpleBlobDetector::Params params;
params.minThreshold = 120;
params.maxThreshold = 255;
params.thresholdStep = 100;
params.minArea = 20;
params.minConvexity = 0.3;
params.minInertiaRatio = 0.01;
params.maxArea = 1000;
params.maxConvexity = 10;
params.filterByColor = false;
params.filterByCircularity = false;
src = binaryx.clone();
SimpleBlobDetector blobDetector( params );
blobDetector.create("SimpleBlob");
blobDetector.detect( src, keyPoints );
drawKeypoints( src, keyPoints, out, CV_RGB(255,0,0), DrawMatchesFlags::DEFAULT);
cv::Mat blobImg;
cv::drawKeypoints(frame, keyPoints, blobImg);
cv::imshow("Blobs", blobImg);
for(int i=0; i<keyPoints.size(); i++){
//circle(out, keyPoints[i].pt, 20, cvScalar(255,0,0), 10);
//cout<<keyPoints[i].response<<endl;
//cout<<keyPoints[i].angle<<endl;
//cout<<keyPoints[i].size()<<endl;
cout<<keyPoints[i].pt.x<<endl;
cout<<keyPoints[i].pt.y<<endl;
}
imshow( "out", out );
if ((cvWaitKey(40)&0xff)==27) break; // esc 'ye basilinca break
}
system("pause");
}