I'm currently trying to write a menu function for one of my projects. I'm remaking pac-man and I'd like to be able to modularize the main menu, calling it when I want it to appear. I'm thinking an SDL_surface and a TTF font are all I need as parameters for this particular function.
I ran into trouble when I used SDL functions only available in SDL 1.2 as opposed to 2.0. I would like this code to be as reusable as possible. How would you guys approach this problem? I have some sample code below, though not all of it is my own. I'd like to get something like this working:
int showmenu(SDL_Surface* screen, TTF_Font* font)
{
Uint32 time;
int x, y;
const int NUMMENU = 2;
const char* labels[NUMMENU] = {"Continue","Exit"};
SDL_Surface* menus[NUMMENU];
bool selected[NUMMENU] = {0,0};
SDL_Color color[2] = {{255,255,255},{255,0,0}};
menus[0] = TTF_RenderText_Solid(font,labels[0],color[0]);
menus[1] = TTF_RenderText_Solid(font,labels[1],color[0]);
SDL_Rect pos[NUMMENU];
pos[0].x = screen->clip_rect.w/2 - menus[0]->clip_rect.w/2;
pos[0].y = screen->clip_rect.h/2 - menus[0]->clip_rect.h;
pos[1].x = screen->clip_rect.w/2 - menus[0]->clip_rect.w/2;
pos[1].y = screen->clip_rect.h/2 + menus[0]->clip_rect.h;
SDL_FillRect(screen,&screen>clip_rect,SDL_MapRGB(screen>format,0x00,0x00,0x00));
SDL_Event event;
while(1)
{
time = SDL_GetTicks();
while(SDL_PollEvent(&event))
{
switch(event.type)
{
case SDL_QUIT:
SDL_FreeSurface(menus[0]);
SDL_FreeSurface(menus[1]);
return 1;
case SDL_MOUSEMOTION:
x = event.motion.x;
y = event.motion.y;
for(int i = 0; i < NUMMENU; i += 1)
{
if(x >= pos[i].x && x <= pos[i].x + pos[i].w &&
y >= pos[i].y && y <= pos[i].y + pos[i].h)
{
if(!selected[i])
{
selected[i] = 1;
SDL_FreeSurface(menus[i]);
menus[i] = TTF_RenderText_Solid(font,labels[i],color[1]);
}
}
else
{
if(selected[i])
{
selected[i] = 0;
SDL_FreeSurface(menus[i]);
menus[i] = TTF_RenderText_Solid(font,labels[i],color[0]);
}
}
}
break;
case SDL_MOUSEBUTTONDOWN:
x = event.button.x;
y = event.button.y;
for(int i = 0; i < NUMMENU; i += 1)
{
if(x >= pos[i].x && x <= pos[i].x + pos[i].w &&
y >= pos[i].y && y <= pos[i].y + pos[i].h)
{
SDL_FreeSurface(menus[0]);
SDL_FreeSurface(menus[1]);
return i;
}
}
break;
case SDL_KEYDOWN:
if(event.key.keysym.sym == SDLK_ESCAPE)
{
SDL_FreeSurface(menus[0]);
SDL_FreeSurface(menus[1]);
return 0;
}
}
}
for(int i = 0; i < NUMMENU; i += 1)
{
SDL_BlitSurface(menus[i],NULL,screen,&pos[i]);
}
}
}
Related
I am looking for an OpenCV function that can find connected components and perform a few tasks on them ( like getting the number of pixels, contour, list of pixels in the object etc.. )
Is there a function of OpenCV (C++) that is similar to MatLab's regionprops ?
Starting from version 3.0, OpenCV has connectedComponents function.
Have a look at the cvFindContours function. It's very versatile -- it can find both interior and exterior contours, and return the results in a variety of formats (e.g. flat list vs. tree structure). Once you have the contours, functions like cvContourArea allow you to determine basic properties of the connected component corresponding to a particular contour.
If you prefer to use the newer C++ interface (as opposed to the older C-style interface I described above), then the function names are similar.
set -std=c++0x option when compiling
.h file
//connected_components.h
#ifndef CONNECTED_COMPONENTS_H_
#define CONNECTED_COMPONENTS_H_
#include <opencv2/core/core.hpp>
#include <memory>
class DisjointSet {
private:
std::vector<int> m_disjoint_array;
int m_subset_num;
public:
DisjointSet();
DisjointSet(int size);
~DisjointSet();
int add(); //add a new element, which is a subset by itself;
int find(int x); //return the root of x
void unite(int x, int y);
int getSubsetNum(void);
};
class ConnectedComponent {
private:
cv::Rect m_bb;
int m_pixel_count;
std::shared_ptr< std::vector<cv::Point2i> > m_pixels;
public:
ConnectedComponent();
ConnectedComponent(int x, int y);
~ConnectedComponent();
void addPixel(int x, int y);
int getBoundingBoxArea(void) const;
cv::Rect getBoundingBox(void) const;
int getPixelCount(void) const;
std::shared_ptr< const std::vector<cv::Point2i> > getPixels(void) const;
};
void findCC(const cv::Mat& src, std::vector<ConnectedComponent>& cc);
#endif //CONNECTED_COMPONENTS_H_
.cc file
//connected_components.cpp
#include "connected_components.h"
using namespace std;
/** DisjointSet **/
DisjointSet::DisjointSet() :
m_disjoint_array(),
m_subset_num(0)
{ }
DisjointSet::DisjointSet(int size) :
m_disjoint_array(),
m_subset_num(0)
{
m_disjoint_array.reserve(size);
}
DisjointSet::~DisjointSet()
{ }
//add a new element, which is a subset by itself;
int DisjointSet::add()
{
int cur_size = m_disjoint_array.size();
m_disjoint_array.push_back(cur_size);
m_subset_num ++;
return cur_size;
}
//return the root of x
int DisjointSet::find(int x)
{
if (m_disjoint_array[x] < 0 || m_disjoint_array[x] == x)
return x;
else {
m_disjoint_array[x] = this->find(m_disjoint_array[x]);
return m_disjoint_array[x];
}
}
// point the x and y to smaller root of the two
void DisjointSet::unite(int x, int y)
{
if (x==y) {
return;
}
int xRoot = find(x);
int yRoot = find(y);
if (xRoot == yRoot)
return;
else if (xRoot < yRoot) {
m_disjoint_array[yRoot] = xRoot;
}
else {
m_disjoint_array[xRoot] = yRoot;
}
m_subset_num--;
}
int DisjointSet::getSubsetNum()
{
return m_subset_num;
}
/** ConnectedComponent **/
ConnectedComponent::ConnectedComponent() :
m_bb(0,0,0,0),
m_pixel_count(0),
m_pixels()
{
m_pixels = std::make_shared< std::vector<cv::Point2i> > ();
}
ConnectedComponent::ConnectedComponent(int x, int y) :
m_bb(x,y,1,1),
m_pixel_count(1),
m_pixels()
{
m_pixels = std::make_shared< std::vector<cv::Point2i> > ();
}
ConnectedComponent::~ConnectedComponent(void)
{ }
void ConnectedComponent::addPixel(int x, int y) {
m_pixel_count++;
// new bounding box;
if (m_pixel_count == 0) {
m_bb = cv::Rect(x,y,1,1);
}
// extend bounding box if necessary
else {
if (x < m_bb.x ) {
m_bb.width+=(m_bb.x-x);
m_bb.x = x;
}
else if ( x > (m_bb.x+m_bb.width) ) {
m_bb.width=(x-m_bb.x);
}
if (y < m_bb.y ) {
m_bb.height+=(m_bb.y-y);
m_bb.y = y;
}
else if ( y > (m_bb.y+m_bb.height) ) {
m_bb.height=(y-m_bb.y);
}
}
m_pixels->push_back(cv::Point(x,y));
}
int ConnectedComponent::getBoundingBoxArea(void) const {
return (m_bb.width*m_bb.height);
}
cv::Rect ConnectedComponent::getBoundingBox(void) const {
return m_bb;
}
std::shared_ptr< const std::vector<cv::Point2i> > ConnectedComponent::getPixels(void) const {
return m_pixels;
}
int ConnectedComponent::getPixelCount(void) const {
return m_pixel_count;
}
/** find connected components **/
void findCC(const cv::Mat& src, std::vector<ConnectedComponent>& cc) {
if (src.empty()) return;
CV_Assert(src.type() == CV_8U);
cc.clear();
int total_pix = src.total();
int frame_label[total_pix];
DisjointSet labels(total_pix);
int root_map[total_pix];
int x, y;
const uchar* cur_p;
const uchar* prev_p = src.ptr<uchar>(0);
int left_val, up_val;
int cur_idx, left_idx, up_idx;
cur_idx = 0;
//first logic loop
for (y = 0; y < src.rows; y++ ) {
cur_p = src.ptr<uchar>(y);
for (x = 0; x < src.cols; x++, cur_idx++) {
left_idx = cur_idx - 1;
up_idx = cur_idx - src.size().width;
if ( x == 0)
left_val = 0;
else
left_val = cur_p[x-1];
if (y == 0)
up_val = 0;
else
up_val = prev_p[x];
if (cur_p[x] > 0) {
//current pixel is foreground and has no connected neighbors
if (left_val == 0 && up_val == 0) {
frame_label[cur_idx] = (int)labels.add();
root_map[frame_label[cur_idx]] = -1;
}
//current pixel is foreground and has left neighbor connected
else if (left_val != 0 && up_val == 0) {
frame_label[cur_idx] = frame_label[left_idx];
}
//current pixel is foreground and has up neighbor connect
else if (up_val != 0 && left_val == 0) {
frame_label[cur_idx] = frame_label[up_idx];
}
//current pixel is foreground and is connected to left and up neighbors
else {
frame_label[cur_idx] = (frame_label[left_idx] > frame_label[up_idx]) ? frame_label[up_idx] : frame_label[left_idx];
labels.unite(frame_label[left_idx], frame_label[up_idx]);
}
}//endif
else {
frame_label[cur_idx] = -1;
}
} //end for x
prev_p = cur_p;
}//end for y
//second loop logic
cur_idx = 0;
int curLabel;
int connCompIdx = 0;
for (y = 0; y < src.size().height; y++ ) {
for (x = 0; x < src.size().width; x++, cur_idx++) {
curLabel = frame_label[cur_idx];
if (curLabel != -1) {
curLabel = labels.find(curLabel);
if( root_map[curLabel] != -1 ) {
cc[root_map[curLabel]].addPixel(x, y);
}
else {
cc.push_back(ConnectedComponent(x,y));
root_map[curLabel] = connCompIdx;
connCompIdx++;
}
}
}//end for x
}//end for y
}
If you don't mind using an external library that uses OpenCV, you can do that using cvBlobsLib.
A library to perform binary images connected component labelling
(similar to regionprops Matlab function). It also provides functions
to manipulate, filter and extract results from the extracted blobs,
see features section for more information.
You can use cv::connectedComponentsWithStats() function.
Here is an example.
// ...
cv::Mat labels, stats, centroids;
int connectivity = 8; // or 4
int label_count = cv::connectedComponentsWithStats(src, labels, stats, centroids, connectivity);
for (int i = 0; i < label_count; i++)
{
int x = stats.at<int>(i, cv::CC_STAT_LEFT);
int y = stats.at<int>(i, cv::CC_STAT_TOP);
int w = stats.at<int>(i, cv::CC_STAT_WIDTH);
int h = stats.at<int>(i, cv::CC_STAT_HEIGHT);
int area = stats.at<int>(i, cv::CC_STAT_AREA);
double cx = centroids.at<double>(i, 0);
double cy = centroids.at<double>(i, 1);
// ...
}
Following DXM's code above which assumes 4-connected components, here is a version for 'findCC' that detects 8-connected components.
void findCC(const cv::Mat& src, std::vector<ConnectedComponent>& cc) {
if (src.empty()) return;
CV_Assert(src.type() == CV_8U);
cc.clear();
int total_pix = int(src.total());
int *frame_label = new int[total_pix];
DisjointSet labels(total_pix);
int *root_map = new int[total_pix];
int x, y;
const uchar* cur_p;
const uchar* prev_p = src.ptr<uchar>(0);
int left_val, up_val, up_left_val, up_right_val;
int cur_idx, left_idx, up_idx, up_left_idx, up_right_idx;
cur_idx = 0;
//first logic loop
for (y = 0; y < src.rows; y++) {
cur_p = src.ptr<uchar>(y);
for (x = 0; x < src.cols; x++, cur_idx++) {
left_idx = cur_idx - 1;
up_idx = cur_idx - src.size().width;
up_left_idx = up_idx - 1;
up_right_idx = up_idx + 1;
if (x == 0)
{
left_val = 0;
}
else
{
left_val = cur_p[x - 1];
}
if (y == 0)
{
up_val = 0;
}
else
{
up_val = prev_p[x];
}
if (x == 0 || y == 0)
{
up_left_val = 0;
}
else
{
up_left_val = prev_p[x-1];
}
if (x == src.cols - 1 || y == 0)
{
up_right_val = 0;
}
else
{
up_right_val = prev_p[x+1];
}
if (cur_p[x] > 0) {
//current pixel is foreground and has no connected neighbors
if (left_val == 0 && up_val == 0 && up_left_val == 0 && up_right_val == 0) {
frame_label[cur_idx] = (int)labels.add();
root_map[frame_label[cur_idx]] = -1;
}
//Current pixel is foreground and has at least one neighbor
else
{
vector<int> frame_lbl;
frame_lbl.reserve(4);
//Find minimal label
int min_frame_lbl = INT_MAX;
int valid_entries_num = 0;
if (left_val != 0)
{
frame_lbl.push_back(frame_label[left_idx]);
min_frame_lbl = min(min_frame_lbl, frame_label[left_idx]);
valid_entries_num++;
}
if (up_val != 0)
{
frame_lbl.push_back(frame_label[up_idx]);
min_frame_lbl = min(min_frame_lbl, frame_label[up_idx]);
valid_entries_num++;
}
if (up_left_val != 0)
{
frame_lbl.push_back(frame_label[up_left_idx]);
min_frame_lbl = min(min_frame_lbl, frame_label[up_left_idx]);
valid_entries_num++;
}
if (up_right_val != 0)
{
frame_lbl.push_back(frame_label[up_right_idx]);
min_frame_lbl = min(min_frame_lbl, frame_label[up_right_idx]);
valid_entries_num++;
}
CV_Assert(valid_entries_num > 0);
frame_label[cur_idx] = min_frame_lbl;
//Unite if necessary
if (valid_entries_num > 1)
{
for (size_t i = 0; i < frame_lbl.size(); i++)
{
labels.unite(frame_lbl[i], min_frame_lbl);
}
}
}
}//endif
else {
frame_label[cur_idx] = -1;
}
} //end for x
prev_p = cur_p;
}//end for y
//second loop logic
cur_idx = 0;
int curLabel;
int connCompIdx = 0;
for (y = 0; y < src.size().height; y++) {
for (x = 0; x < src.size().width; x++, cur_idx++) {
curLabel = frame_label[cur_idx];
if (curLabel != -1) {
curLabel = labels.find(curLabel);
if (root_map[curLabel] != -1) {
cc[root_map[curLabel]].addPixel(x, y);
}
else {
cc.push_back(ConnectedComponent(x, y));
root_map[curLabel] = connCompIdx;
connCompIdx++;
}
}
}//end for x
}//end for y
//Free up allocated memory
delete[] frame_label;
delete[] root_map;
}
I'm fighting with this for like few days, and I have no idea, how to do that, so I'd like to ask you for help. I've got no idea how collision should look like right here, so player could jump through 'down zone' of the block, and stay right on the block.
block.cpp
bool block::CollidingWithPlayer(character& player) {
for (int i = 1; i < MAX_BLOCKS; i++) {
if (player.x + player.width >= coordinateX[i] && player.x <= coordinateX[i] + width[i] && player.y + player.height >= coordinateY[i] && player.y <= coordinateY[i] + block_height) {
player.onGround = true;
return true;
}
}
}
character.cpp
void character::startJump(map& Map, character& player) {
if (onGround)
{
vel[1] = -11.0;
onGround = false;
}
}
void character::updateJump(block& Block, character& player) {
if (!onGround) {
Block.CollidingWithPlayer(player);
vel[1] += 0.5;
y += vel[1];
x += vel[0];
}
if (y > 460){
y = 460;
vel[1] = 0.0;
onGround = true;
vel[0] = 0.0;
}
if ((x + width >= START_OF_RIGHT_WALL && x <= WALL_WIDTH + START_OF_RIGHT_WALL) || (x + width >= START_OF_LEFT_WALL &&x <= START_OF_LEFT_WALL + WALL_WIDTH)){
vel[0] *= -1;
bound = true;
if (direction == 1)
direction = 2;
else if (direction == 2)
direction = 1;
}
}
As the title suggests, I am trying to implement collision with scrolling.
The code here isn't the tidiest and it doesn't make use of object-oriented programming. But this code is a prototype for collision in a game I am making so I can reuse in that game.
Anyway, when I wrote this code and implemented no scrolling, the collision worked fine. However, when I implemented scrolling, suddenly, one pixel of the rectangle that represent the player gets stuck in the rectangles I've used to test the collision.
Here's the code.
Main.cpp:
#include "SDL.h"
#include "SDL_image.h"
#include "SDL_mixer.h"
#include "SDL_ttf.h"
using namespace std;
int main(int argc, char *argv[]) {
SDL_Init(SDL_INIT_EVERYTHING);
IMG_Init(IMG_INIT_PNG | IMG_INIT_JPG);
Mix_Init(MIX_INIT_MP3);
Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 640);
TTF_Init();
SDL_Window *Window = SDL_CreateWindow("Test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1000, 600, SDL_WINDOW_SHOWN);
SDL_Renderer *Renderer = SDL_CreateRenderer(Window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
bool quit = false;
SDL_Event Event;
SDL_Rect Box;
Box.w = 25;
Box.h = 25;
Box.x = 500 - int(Box.w / 2);
Box.y = 300 - int(Box.h / 2);
int CAMERA_X = 0;
int CAMERA_Y = 0;
int xVel = 0;
int yVel = 0;
SDL_Rect RECTS[3];
RECTS[0].x = 50;
RECTS[0].y = 50;
RECTS[0].w = 50;
RECTS[0].h = 100;
RECTS[1].x = 150;
RECTS[1].y = 50;
RECTS[1].w = 50;
RECTS[1].h = 100;
RECTS[2].x = 250;
RECTS[2].y = 50;
RECTS[2].w = 50;
RECTS[2].h = 100;
SDL_Rect CAM_RECTS[3];
CAM_RECTS[0].x = 50;
CAM_RECTS[0].y = 50;
CAM_RECTS[0].w = 50;
CAM_RECTS[0].h = 100;
CAM_RECTS[1].x = 150;
CAM_RECTS[1].y = 50;
CAM_RECTS[1].w = 50;
CAM_RECTS[1].h = 100;
CAM_RECTS[2].x = 250;
CAM_RECTS[2].y = 50;
CAM_RECTS[2].w = 50;
CAM_RECTS[2].h = 100;
const Uint8 *Input = NULL;
while (!quit) {
Input = SDL_GetKeyboardState(NULL);
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_QUIT) {
quit = true;
}
}
if (Input[SDL_SCANCODE_RIGHT]) {
xVel = -7;
} else if (Input[SDL_SCANCODE_LEFT]) {
xVel = 7;
} else {
xVel = 0;
}
if (Input[SDL_SCANCODE_UP]) {
yVel = 7;
} else if (Input[SDL_SCANCODE_DOWN]) {
yVel = -7;
} else {
yVel = 0;
}
for (unsigned int i = 0; i < 3; i++) {
CAM_RECTS[i].x = RECTS[i].x + CAMERA_X;
CAM_RECTS[i].y = RECTS[i].y + CAMERA_Y;
}
CAMERA_X += xVel;
for (unsigned int i = 0; i < 3; i++) {
if (Box.x <= CAM_RECTS[i].x + CAM_RECTS[i].w && Box.x + Box.w >= CAM_RECTS[i].x && Box.y <= CAM_RECTS[i].y + CAM_RECTS[i].h && Box.y + Box.h >= CAM_RECTS[i].y) {
if (Box.x <= CAM_RECTS[i].x + CAM_RECTS[i].w && Box.x + Box.w >= CAM_RECTS[i].x) {
CAMERA_X += -xVel;
}
}
}
CAMERA_Y += yVel;
for (unsigned int i = 0; i < 3; i++) {
if (Box.x <= CAM_RECTS[i].x + CAM_RECTS[i].w && Box.x + Box.w >= CAM_RECTS[i].x && Box.y <= CAM_RECTS[i].y + CAM_RECTS[i].h && Box.y + Box.h >= CAM_RECTS[i].y) {
if (Box.y <= CAM_RECTS[i].y + CAM_RECTS[i].h && Box.y + Box.h >= CAM_RECTS[i].y) {
CAMERA_Y += -yVel;
}
}
}
SDL_SetRenderDrawColor(Renderer, 0, 0, 0, 255);
SDL_RenderClear(Renderer);
SDL_SetRenderDrawColor(Renderer, 255, 255, 255, 255);
SDL_RenderFillRect(Renderer, &Box);
SDL_SetRenderDrawColor(Renderer, 255, 255, 255, 255);
for (unsigned int i = 0; i < 3; i++) {
SDL_RenderFillRect(Renderer, &CAM_RECTS[i]);
}
SDL_RenderPresent(Renderer);
}
Input = NULL;
SDL_DestroyRenderer(Renderer);
SDL_DestroyWindow(Window);
TTF_Quit();
Mix_CloseAudio();
Mix_Quit();
IMG_Quit();
SDL_Quit();
return 0;
}
Please help.
Thank you very much :)
Ok, I changed answer, now it should work without jittering.
It's definitely not the best solution, but it would depends on wider concepion of your program.
int sign(int x)
{
return (x > 0) - (x < 0);
}
/* Movement values are updated by referenced arguments.*/
void check_collisions(SDL_Rect* obj_rects, unsigned num_of_obj,
SDL_Rect obstacle, int& horizontal_move, int& vertical_move)
{
for (unsigned int i = 0; i < num_of_obj; i++)
{
int obj_new_x = obj_rects[i].x + horizontal_move;
// Horizontal collision
if (obj_new_x < (obstacle.x + obstacle.w)
&& (obj_new_x + obj_rects[i].w) > obstacle.x
&& obj_rects[i].y < (obstacle.y + obstacle.h)
&& (obj_rects[i].h + obj_rects[i].y) > obstacle.y)
{
// Calculate maximal possible horizontal movement.
if (sign(horizontal_move) == 1)
horizontal_move = obstacle.x - (obj_rects[i].x + obj_rects[i].w);
else if (sign(horizontal_move) == -1)
horizontal_move = (obstacle.x + obstacle.w) - obj_rects[i].x;
}
int obj_new_y = obj_rects[i].y + vertical_move;
// Vertical collision
if (obj_rects[i].x < (obstacle.x + obstacle.w)
&& (obj_rects[i].x + obj_rects[i].w) > obstacle.x
&& obj_new_y < (obstacle.y + obstacle.h)
&& (obj_new_y + obj_rects[i].h) > obstacle.y)
{
if (sign(vertical_move) == 1)
vertical_move = obstacle.y - (obj_rects[i].y + obj_rects[i].h);
else if (sign(vertical_move) == -1)
vertical_move = (obstacle.y + obstacle.h) - obj_rects[i].y;
}
}
}
int cam_velocity = 5;
...
while (!quit) {
...
if (Input[SDL_SCANCODE_RIGHT])
{
x_movement = cam_velocity;
}
else if (Input[SDL_SCANCODE_LEFT])
{
x_movement = -cam_velocity;
}
if (Input[SDL_SCANCODE_UP])
{
y_movement = -cam_velocity;
}
else if (Input[SDL_SCANCODE_DOWN])
{
y_movement = cam_velocity;
}
check_collisions(CAM_RECTS, 3, Box, x_movement, y_movement);
CAMERA_X += x_movement;
CAMERA_Y += y_movement;
for (unsigned int i = 0; i < 3; i++) {
CAM_RECTS[i].x = RECTS[i].x + CAMERA_X;
CAM_RECTS[i].y = RECTS[i].y + CAMERA_Y;
}
...
I am looking for an OpenCV function that can find connected components and perform a few tasks on them ( like getting the number of pixels, contour, list of pixels in the object etc.. )
Is there a function of OpenCV (C++) that is similar to MatLab's regionprops ?
Starting from version 3.0, OpenCV has connectedComponents function.
Have a look at the cvFindContours function. It's very versatile -- it can find both interior and exterior contours, and return the results in a variety of formats (e.g. flat list vs. tree structure). Once you have the contours, functions like cvContourArea allow you to determine basic properties of the connected component corresponding to a particular contour.
If you prefer to use the newer C++ interface (as opposed to the older C-style interface I described above), then the function names are similar.
set -std=c++0x option when compiling
.h file
//connected_components.h
#ifndef CONNECTED_COMPONENTS_H_
#define CONNECTED_COMPONENTS_H_
#include <opencv2/core/core.hpp>
#include <memory>
class DisjointSet {
private:
std::vector<int> m_disjoint_array;
int m_subset_num;
public:
DisjointSet();
DisjointSet(int size);
~DisjointSet();
int add(); //add a new element, which is a subset by itself;
int find(int x); //return the root of x
void unite(int x, int y);
int getSubsetNum(void);
};
class ConnectedComponent {
private:
cv::Rect m_bb;
int m_pixel_count;
std::shared_ptr< std::vector<cv::Point2i> > m_pixels;
public:
ConnectedComponent();
ConnectedComponent(int x, int y);
~ConnectedComponent();
void addPixel(int x, int y);
int getBoundingBoxArea(void) const;
cv::Rect getBoundingBox(void) const;
int getPixelCount(void) const;
std::shared_ptr< const std::vector<cv::Point2i> > getPixels(void) const;
};
void findCC(const cv::Mat& src, std::vector<ConnectedComponent>& cc);
#endif //CONNECTED_COMPONENTS_H_
.cc file
//connected_components.cpp
#include "connected_components.h"
using namespace std;
/** DisjointSet **/
DisjointSet::DisjointSet() :
m_disjoint_array(),
m_subset_num(0)
{ }
DisjointSet::DisjointSet(int size) :
m_disjoint_array(),
m_subset_num(0)
{
m_disjoint_array.reserve(size);
}
DisjointSet::~DisjointSet()
{ }
//add a new element, which is a subset by itself;
int DisjointSet::add()
{
int cur_size = m_disjoint_array.size();
m_disjoint_array.push_back(cur_size);
m_subset_num ++;
return cur_size;
}
//return the root of x
int DisjointSet::find(int x)
{
if (m_disjoint_array[x] < 0 || m_disjoint_array[x] == x)
return x;
else {
m_disjoint_array[x] = this->find(m_disjoint_array[x]);
return m_disjoint_array[x];
}
}
// point the x and y to smaller root of the two
void DisjointSet::unite(int x, int y)
{
if (x==y) {
return;
}
int xRoot = find(x);
int yRoot = find(y);
if (xRoot == yRoot)
return;
else if (xRoot < yRoot) {
m_disjoint_array[yRoot] = xRoot;
}
else {
m_disjoint_array[xRoot] = yRoot;
}
m_subset_num--;
}
int DisjointSet::getSubsetNum()
{
return m_subset_num;
}
/** ConnectedComponent **/
ConnectedComponent::ConnectedComponent() :
m_bb(0,0,0,0),
m_pixel_count(0),
m_pixels()
{
m_pixels = std::make_shared< std::vector<cv::Point2i> > ();
}
ConnectedComponent::ConnectedComponent(int x, int y) :
m_bb(x,y,1,1),
m_pixel_count(1),
m_pixels()
{
m_pixels = std::make_shared< std::vector<cv::Point2i> > ();
}
ConnectedComponent::~ConnectedComponent(void)
{ }
void ConnectedComponent::addPixel(int x, int y) {
m_pixel_count++;
// new bounding box;
if (m_pixel_count == 0) {
m_bb = cv::Rect(x,y,1,1);
}
// extend bounding box if necessary
else {
if (x < m_bb.x ) {
m_bb.width+=(m_bb.x-x);
m_bb.x = x;
}
else if ( x > (m_bb.x+m_bb.width) ) {
m_bb.width=(x-m_bb.x);
}
if (y < m_bb.y ) {
m_bb.height+=(m_bb.y-y);
m_bb.y = y;
}
else if ( y > (m_bb.y+m_bb.height) ) {
m_bb.height=(y-m_bb.y);
}
}
m_pixels->push_back(cv::Point(x,y));
}
int ConnectedComponent::getBoundingBoxArea(void) const {
return (m_bb.width*m_bb.height);
}
cv::Rect ConnectedComponent::getBoundingBox(void) const {
return m_bb;
}
std::shared_ptr< const std::vector<cv::Point2i> > ConnectedComponent::getPixels(void) const {
return m_pixels;
}
int ConnectedComponent::getPixelCount(void) const {
return m_pixel_count;
}
/** find connected components **/
void findCC(const cv::Mat& src, std::vector<ConnectedComponent>& cc) {
if (src.empty()) return;
CV_Assert(src.type() == CV_8U);
cc.clear();
int total_pix = src.total();
int frame_label[total_pix];
DisjointSet labels(total_pix);
int root_map[total_pix];
int x, y;
const uchar* cur_p;
const uchar* prev_p = src.ptr<uchar>(0);
int left_val, up_val;
int cur_idx, left_idx, up_idx;
cur_idx = 0;
//first logic loop
for (y = 0; y < src.rows; y++ ) {
cur_p = src.ptr<uchar>(y);
for (x = 0; x < src.cols; x++, cur_idx++) {
left_idx = cur_idx - 1;
up_idx = cur_idx - src.size().width;
if ( x == 0)
left_val = 0;
else
left_val = cur_p[x-1];
if (y == 0)
up_val = 0;
else
up_val = prev_p[x];
if (cur_p[x] > 0) {
//current pixel is foreground and has no connected neighbors
if (left_val == 0 && up_val == 0) {
frame_label[cur_idx] = (int)labels.add();
root_map[frame_label[cur_idx]] = -1;
}
//current pixel is foreground and has left neighbor connected
else if (left_val != 0 && up_val == 0) {
frame_label[cur_idx] = frame_label[left_idx];
}
//current pixel is foreground and has up neighbor connect
else if (up_val != 0 && left_val == 0) {
frame_label[cur_idx] = frame_label[up_idx];
}
//current pixel is foreground and is connected to left and up neighbors
else {
frame_label[cur_idx] = (frame_label[left_idx] > frame_label[up_idx]) ? frame_label[up_idx] : frame_label[left_idx];
labels.unite(frame_label[left_idx], frame_label[up_idx]);
}
}//endif
else {
frame_label[cur_idx] = -1;
}
} //end for x
prev_p = cur_p;
}//end for y
//second loop logic
cur_idx = 0;
int curLabel;
int connCompIdx = 0;
for (y = 0; y < src.size().height; y++ ) {
for (x = 0; x < src.size().width; x++, cur_idx++) {
curLabel = frame_label[cur_idx];
if (curLabel != -1) {
curLabel = labels.find(curLabel);
if( root_map[curLabel] != -1 ) {
cc[root_map[curLabel]].addPixel(x, y);
}
else {
cc.push_back(ConnectedComponent(x,y));
root_map[curLabel] = connCompIdx;
connCompIdx++;
}
}
}//end for x
}//end for y
}
If you don't mind using an external library that uses OpenCV, you can do that using cvBlobsLib.
A library to perform binary images connected component labelling
(similar to regionprops Matlab function). It also provides functions
to manipulate, filter and extract results from the extracted blobs,
see features section for more information.
You can use cv::connectedComponentsWithStats() function.
Here is an example.
// ...
cv::Mat labels, stats, centroids;
int connectivity = 8; // or 4
int label_count = cv::connectedComponentsWithStats(src, labels, stats, centroids, connectivity);
for (int i = 0; i < label_count; i++)
{
int x = stats.at<int>(i, cv::CC_STAT_LEFT);
int y = stats.at<int>(i, cv::CC_STAT_TOP);
int w = stats.at<int>(i, cv::CC_STAT_WIDTH);
int h = stats.at<int>(i, cv::CC_STAT_HEIGHT);
int area = stats.at<int>(i, cv::CC_STAT_AREA);
double cx = centroids.at<double>(i, 0);
double cy = centroids.at<double>(i, 1);
// ...
}
Following DXM's code above which assumes 4-connected components, here is a version for 'findCC' that detects 8-connected components.
void findCC(const cv::Mat& src, std::vector<ConnectedComponent>& cc) {
if (src.empty()) return;
CV_Assert(src.type() == CV_8U);
cc.clear();
int total_pix = int(src.total());
int *frame_label = new int[total_pix];
DisjointSet labels(total_pix);
int *root_map = new int[total_pix];
int x, y;
const uchar* cur_p;
const uchar* prev_p = src.ptr<uchar>(0);
int left_val, up_val, up_left_val, up_right_val;
int cur_idx, left_idx, up_idx, up_left_idx, up_right_idx;
cur_idx = 0;
//first logic loop
for (y = 0; y < src.rows; y++) {
cur_p = src.ptr<uchar>(y);
for (x = 0; x < src.cols; x++, cur_idx++) {
left_idx = cur_idx - 1;
up_idx = cur_idx - src.size().width;
up_left_idx = up_idx - 1;
up_right_idx = up_idx + 1;
if (x == 0)
{
left_val = 0;
}
else
{
left_val = cur_p[x - 1];
}
if (y == 0)
{
up_val = 0;
}
else
{
up_val = prev_p[x];
}
if (x == 0 || y == 0)
{
up_left_val = 0;
}
else
{
up_left_val = prev_p[x-1];
}
if (x == src.cols - 1 || y == 0)
{
up_right_val = 0;
}
else
{
up_right_val = prev_p[x+1];
}
if (cur_p[x] > 0) {
//current pixel is foreground and has no connected neighbors
if (left_val == 0 && up_val == 0 && up_left_val == 0 && up_right_val == 0) {
frame_label[cur_idx] = (int)labels.add();
root_map[frame_label[cur_idx]] = -1;
}
//Current pixel is foreground and has at least one neighbor
else
{
vector<int> frame_lbl;
frame_lbl.reserve(4);
//Find minimal label
int min_frame_lbl = INT_MAX;
int valid_entries_num = 0;
if (left_val != 0)
{
frame_lbl.push_back(frame_label[left_idx]);
min_frame_lbl = min(min_frame_lbl, frame_label[left_idx]);
valid_entries_num++;
}
if (up_val != 0)
{
frame_lbl.push_back(frame_label[up_idx]);
min_frame_lbl = min(min_frame_lbl, frame_label[up_idx]);
valid_entries_num++;
}
if (up_left_val != 0)
{
frame_lbl.push_back(frame_label[up_left_idx]);
min_frame_lbl = min(min_frame_lbl, frame_label[up_left_idx]);
valid_entries_num++;
}
if (up_right_val != 0)
{
frame_lbl.push_back(frame_label[up_right_idx]);
min_frame_lbl = min(min_frame_lbl, frame_label[up_right_idx]);
valid_entries_num++;
}
CV_Assert(valid_entries_num > 0);
frame_label[cur_idx] = min_frame_lbl;
//Unite if necessary
if (valid_entries_num > 1)
{
for (size_t i = 0; i < frame_lbl.size(); i++)
{
labels.unite(frame_lbl[i], min_frame_lbl);
}
}
}
}//endif
else {
frame_label[cur_idx] = -1;
}
} //end for x
prev_p = cur_p;
}//end for y
//second loop logic
cur_idx = 0;
int curLabel;
int connCompIdx = 0;
for (y = 0; y < src.size().height; y++) {
for (x = 0; x < src.size().width; x++, cur_idx++) {
curLabel = frame_label[cur_idx];
if (curLabel != -1) {
curLabel = labels.find(curLabel);
if (root_map[curLabel] != -1) {
cc[root_map[curLabel]].addPixel(x, y);
}
else {
cc.push_back(ConnectedComponent(x, y));
root_map[curLabel] = connCompIdx;
connCompIdx++;
}
}
}//end for x
}//end for y
//Free up allocated memory
delete[] frame_label;
delete[] root_map;
}
I'm trying to make it where the character is in a tile and when they move up or down it moves to the next tile but I'm not sure how to do that. Right now, I have it set up where the character moves by pixels but I want it to move by 1 square.
The code right now is this, and it works, but it's glitchy in pixel mode. I believe if it was by blocks it might work better but I might change it anyway.
float spritewidth = sprite->stretchX;
float spriteheight = sprite->stretchY;
float bushwidth = bush->stretchX;
float bushheight = bush->stretchY;
//Basic border collision
if (sprite->x <= 0)
sprite->x = 0;
if (sprite->y <= 0)
sprite->y = 0;
if (sprite->x >= 455)
sprite->x = 455;
if (sprite->y >= 237)
sprite->y = 237;
if ( (sprite->x + spritewidth > bush->x) && (sprite->x < bush->x + bushwidth) && (sprite->y + spriteheight > bush->y) && (sprite->y < bush->y + bushheight) )
{
bushcol = 1;
}
else
{
bushcol = 0;
}
if (osl_keys->held.down)
{
if (bushcol == 1)
{
sprite->y = bush->y - spriteheight - 3;
bushcol = 0;
}
else
{
bushcol = 0;
sprite->y += 3;
}
}
if (osl_keys->held.up)
{
if (bushcol == 1)
{
sprite->y = bush->y + bushheight + 3;
bushcol = 0;
}
else
{
bushcol = 0;
sprite->y -= 3;
}
}
if (osl_keys->held.right)
{
if (bushcol == 1)
{
sprite->x = bush->x - spritewidth - 3;
bushcol = 0;
}
else
{
bushcol = 0;
sprite->x += 3;}
}
if (osl_keys->held.left)
{
if (bushcol == 1)
{
sprite->x = bush->x + bushwidth + 3;
bushcol = 0;
}
else
{
bushcol = 0;
sprite->x -= 3;
}
}
If you want the character to move one tile/square/block at a time, just move the sprite the number of pixels the tile is wide (or tall).
const int tile_width = 32; // or something
// and then
sprite->x += tile_width;