I am a new C++ programmer and I have some experience in Python and C but I was almost completely self taught (I learned C++ with OpenClassrooms).
I would like to learn the conventions and how things should be done. But I would also like to know why a method should be used over another.
This is my first real project in C++. Any advice or corrections are very welcome. I am more interested in the techniques in C++ generally than specifically with this algorithm, but if I am doing something in an inefficient way, I invite you to suggest corrections.
main.cpp
/* K-Means image segmentation
*
* This program only works with uncompressed images
* PPM with type P3
*/
#include "../include/image.h"
#include <iostream>
#include <string>
#include <ctime>
#include <cstdlib>
#include <vector>
using namespace std;
int main()
{
// Set the seed for better random generation
srand(time(NULL));
// Get the path of the original image
string imageDir;
cout << "Image path: ";
cin >> imageDir;
Image *image(new Image(imageDir));
// Get the path of the image to save
string saveImageAs;
cout << "Save image as: ";
cin >> saveImageAs;
// Create each cluster
int clusterCount;
cout << "How many colours? ";
cin >> clusterCount;
vector<Cluster*> clusters;
for (int i(0); i < clusterCount; i++)
{
clusters.push_back(new Cluster(image));
}
// Get the threshold
double threshold;
cout << "Threshold: ";
cin >> threshold;
// Repeat the algorithm until the average centroid change goes below the threshold
double averageCentroidChange;
do
{
// Clear all pixels for each cluster
for (int i(0); i < clusterCount; i++)
{
clusters[i]->clearPixels();
}
// Go through each pixel in the image
for (int i(0); i < image->getLength(); i++)
{
// Calculate which cluster centroid the pixel is nearest to
int closestClusterIndex(0);
double dist;
for (int j(0); j < clusters.size(); j++)
{
dist = clusters[j]->getDistanceTo(image->getPixel(i));
if (dist < clusters[closestClusterIndex]->getDistanceTo(image->getPixel(i)))
{
closestClusterIndex = j;
}
}
// Add the pixel to the nearest cluster
clusters[closestClusterIndex]->addPixel(image->getPixel(i));
}
// Calculate the average change of the centroids
averageCentroidChange = 0;
for (int i(0); i < clusters.size(); i++)
{
averageCentroidChange += clusters[i]->adjustCentroid();
}
averageCentroidChange /= clusters.size();
cout << "Average centroid change: " << averageCentroidChange << endl;
} while (averageCentroidChange > threshold);
// Change all pixels to the color of the corresponding cluster centroid
for (int i(0); i < clusters.size(); i++)
{
clusters[i]->changeAll();
}
// Save the new image
image->saveImage(saveImageAs);
return 0;
}
image.h
#ifndef IMAGE_H_INCLUDED
#define IMAGE_H_INCLUDED
#include <string>
#include <vector>
class Pixel
{
public:
Pixel(int red, int green, int blue);
Pixel(Pixel *pixel);
int getRed() const;
int getGreen() const;
int getBlue() const;
std::string getRGB() const;
void setRGB(int red, int green, int blue);
private:
int m_red;
int m_green;
int m_blue;
};
class Image
{
public:
Image(int width, int height);
Image(std::string name);
~Image();
void saveImage(std::string savePath) const;
Pixel* getRandPixel() const;
std::vector<Pixel*> getPixels() const;
Pixel* getPixel(int index) const;
int getLength() const;
private:
int m_width;
int m_height;
int m_depth;
std::vector<Pixel*> m_pixels;
};
class Cluster
{
public:
Cluster(Image *image);
~Cluster();
double adjustCentroid();
double getDistanceTo(Pixel *pixel) const;
double getDistanceTo(int red, int green, int blue) const;
void addPixel(Pixel *pixel);
void clearPixels();
void changeAll();
private:
Image *m_image;
Pixel *m_centroid;
std::vector<Pixel*> m_pixels;
};
#endif
image.cpp
#include "../include/image.h"
#include <cmath>
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <cstdlib>
using namespace std;
Pixel::Pixel(int red, int green, int blue) : m_red(red), m_green(green), m_blue(blue) {}
Pixel::Pixel(Pixel *pixel) : m_red(pixel->m_red), m_green(pixel->m_green), m_blue(pixel->m_blue) {}
int Pixel::getRed() const
{
return m_red;
}
int Pixel::getGreen() const
{
return m_green;
}
int Pixel::getBlue() const
{
return m_blue;
}
string Pixel::getRGB() const
{
return to_string(m_red) + " " + to_string(m_green) + " " + to_string(m_blue);
}
void Pixel::setRGB(int red, int green, int blue)
{
m_red = red;
m_green = green;
m_blue = blue;
}
Image::Image(int width, int height) : m_width(width), m_height(height)
{
for (int i(0); i < m_width*m_height; i++)
{
m_pixels.push_back(new Pixel(0, 0, 0));
}
}
Image::Image(string imageDir)
{
ifstream image(imageDir);
if (image)
{
string type;
image >> type;
if (type == "P3")
{
int red;
int green;
int blue;
image >> m_width;
image >> m_height;
image >> m_depth;
for (int i(0); i < m_width*m_height; i++)
{
image >> red;
image >> green;
image >> blue;
m_pixels.push_back(new Pixel(red, green, blue));
}
} else {
cout << imageDir << " is in the wrong format (should be P3)" << endl;
}
} else {
cout << imageDir << " could not be opened!" << endl;
}
}
Image::~Image()
{
for (int i(0); i < m_width*m_height; i++)
{
delete m_pixels[i];
}
}
void Image::saveImage(string name) const
{
ofstream image(name);
if (image)
{
image << "P3" << endl;
image << m_width << " " << m_height << endl;
image << m_depth << endl;
for (int y(0); y < m_width; y++)
{
for (int x(0); x < m_height; x++)
{
Pixel *pixel = m_pixels[m_height*y + x];
image << pixel->getRGB() << " ";
}
image << endl;
}
} else {
cout << name << ".ppm could not be opened" << endl;
}
}
Pixel* Image::getRandPixel() const
{
return m_pixels[rand() % m_width*m_height];
}
vector<Pixel*> Image::getPixels() const
{
return m_pixels;
}
Pixel* Image::getPixel(int index) const
{
return m_pixels[index];
}
int Image::getLength() const
{
return m_width*m_height;
}
Cluster::Cluster(Image *image) : m_image(image), m_centroid(new Pixel(image->getRandPixel()))
{
}
Cluster::~Cluster()
{
delete m_centroid;
}
double Cluster::adjustCentroid()
{
float red(0);
float green(0);
float blue(0);
for (int i(0); i < m_pixels.size(); i++)
{
red += m_pixels[i]->getRed();
green += m_pixels[i]->getGreen();
blue += m_pixels[i]->getBlue();
}
int denominator(m_pixels.size());
if (m_pixels.size() < 1)
{
denominator = 1;
}
red /= denominator;
green /= denominator;
blue /= denominator;
double change(this->getDistanceTo(red, green, blue));
m_centroid->setRGB(red, green, blue);
return change;
}
double Cluster::getDistanceTo(Pixel *pixel) const
{
int diffRed(pixel->getRed() - m_centroid->getRed());
int diffGreen(pixel->getGreen() - m_centroid->getGreen());
int diffBlue(pixel->getBlue() - m_centroid->getBlue());
return sqrt(pow(diffRed, 2) + pow(diffGreen, 2) + pow(diffBlue, 2));
}
double Cluster::getDistanceTo(int red, int green, int blue) const
{
int diffRed(red - m_centroid->getRed());
int diffGreen(green - m_centroid->getGreen());
int diffBlue(blue - m_centroid->getBlue());
return sqrt(pow(diffRed, 2) + pow(diffGreen, 2) + pow(diffBlue, 2));
}
void Cluster::addPixel(Pixel *pixel)
{
m_pixels.push_back(pixel);
}
void Cluster::clearPixels()
{
m_pixels = {};
}
void Cluster::changeAll()
{
for (int i(0); i < m_pixels.size(); i++)
{
m_pixels[i]->setRGB(m_centroid->getRed(), m_centroid->getGreen(), m_centroid->getBlue());
}
}