Partage
  • Partager sur Facebook
  • Partager sur Twitter

Atelier Cod'Art

Anonyme
8 mars 2010 à 3:17:42

Waouh, arriver à faire de telles choses en JS o_O
  • Partager sur Facebook
  • Partager sur Twitter
8 mars 2010 à 14:48:12

Impressionnant, je pensais pas qu'on pouvait aboutir à un tel résultat en js.
Je jetterai un coup d'œeil au code quand j'aurai un moment.
  • Partager sur Facebook
  • Partager sur Twitter
9 mars 2010 à 16:55:42

Euh oui enfin, vous me faites un peu peur là : ne vous y trompez pas non plus, et ne confondez pas les maths et le JS. Ce dernier ne me donne aucun outil magique, la seule fonctionnalité un brin particulière (et encore !) que j'exploite en JS est la facilité de faire du mouvement un peu dynamique (pour faire tourner la structure en "temps réel", en fait pour faire tourner le point de vue autour de la structure pour être exact), que vous avez déjà sûrement déjà vue dans d'autres petites animations (genre un truc qui suit le curseur de la souris ou rien qu'un truc de drag 'n drop, c'est exactement la même chose). Mais ce qui me permet de parvenir à ce résultat, c'est le contenu mathématique derrière, pas le JS. Enfin, peut-être que j'interprète mal vos remarques hein, mais je me demande si vous en êtes bien conscients...
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
11 mars 2010 à 18:30:24

x] Ouais, tu as raison, peut importe le langage, c'est l'algo qui fait le programme.
  • Partager sur Facebook
  • Partager sur Twitter
13 mars 2010 à 15:39:54

A la demande insistante de Poulet (#sdz) qui vient de me faire connaitre ce topic, je poste ici ma dernière réalisation.

Origine et réseaux de neurones

Depuis quelque temps, je m'intéresse à l'intelligence artificielle, et plus précisément aux réseaux de neurones (voir : http://alp.developpez.com/tutoriels/in [...] -de-neurones/ ).
J'ai donc codé une petite bibliothèque me permettant de créer des réseaux de neurones et de les faire évoluer (avec un algorithme génétique homemade)( à propos des algo génétiques : http://khayyam.developpez.com/articles/algo/genetic/ ).

<pas très intéressant>
Un fois cela fait, j'ai testé mon code en essayant de faire jouer mes réseaux au morpion. Le problème, c'est que cela n'a pas aboutit, car je classait mes réseaux à partir d'un championnat interne, or ce n'est pas parce que un individu bat tous les autres qu'il est réellement fort.
</pas très intéressant>

Maintenant le rapport avec ce topic :
Après cet échec, j'ai eu l'idée de faire faire des images à mes réseaux de neurones. Avec des réseaux à deux entrée (les coordonnée du pixel, ramenées entre 0 et 1) et trois sortie (la couleur du pixel : R G B (entre 0 et 1 -> *255)), j'ai créé (ou plutôt les réseaux) des images.

Mais comment obtenir de belles images? J'ai donc fait évoluer les réseaux. Se posait alors le problème de l'évaluation : en effet, comment savoir si un réseau et mieux qu'un autre?

Pour cela, à chaque réseau de la génération courante, j'ai fait créer une image, et j'ai évalué les réseaux à partir de l'image créée.
Pour chaque image, je calcule donc :

(log(nb_couleur))² * somme(abs(xi*log(xi)))
Où nb_couleur est le nombre de couleurs différentes de l'image, et xi la fréquence de pixel de la couleur i.

La somme est maximale lorsqu'il y a autant de pixels de chaque couleur ( http://fr.wikipedia.org/wiki/Entropie_ [...] _l.27entropie ). Les image sont ainsi "harmonieuses" ^^.
La première partie de la formule permet de favoriser les images aux couleurs diversifées.
L'utilisation de la fonction sigmoïde ( http://fr.wikipedia.org/wiki/Sigmo%C3% [...] A9matiques%29 ) comme fonction de transfert permet une continuité de l'image.

Voilà quelques résultats :


Image utilisateurImage utilisateur

Image utilisateurImage utilisateur

Image utilisateur


Et voila les sources, pour ceux que ça interresse :

Toute la partie réseaux/évolution est commentée, par contre je n'ai pas optimisé, et les fonction ne sont pas "sécurisées" (= elles ne vérifient pas à fond qu'on ne leur envoie pas n'importe quoi)(oui, c'est mal :euh: ).
Il est également possible que vous trouviez des erreurs dans le code. Si c'est le cas, prévenez moi par MP.
voilà les fichiers (en espérant que je puisse tout poster, vu la taille).

Vous pouvez télécharger tous les fichiers affichés ci-dessous ici.

Makefile :
all : ciel

ciel : main.o neurone.o reseau.o darwin.o nuage.o
g++ main.o neurone.o reseau.o darwin.o nuage.o -o ciel -O3 -lcv -lhighgui

main.o : main.cpp nuage.o
g++ -c main.cpp -O3

nuage.o : nuage.cpp
g++ -c nuage.cpp -O3

darwin.o : darwin.cpp
g++ -c darwin.cpp -O3

reseau.o : reseau.cpp
g++ -c reseau.cpp -O3

neurone.o : neurone.cpp
g++ -c neurone.cpp -O3

clean :
rm -rf *.o


main.cpp :
#include <iostream>
#include <sstream>
#include "nuage.h"
using namespace std;


int main()
{
	
	Nuage nuage(100, 100);
	
	
	nuage.creer(10);
	nuage.afficher();
	
	
	/*
	Reseau network;
	
	for(int i = 1; i < 10; i++ )
	{
		ostringstream oss;
		
		oss<<i;
		string s = "champion_gen_";
		s += oss.str();
		network.charger(s, &nuage);
	
		nuage.creer(network);
	
		nuage.afficher();
	}	
	*/
	/*
	Reseau network;
	network.charger("champion_gen_0", &nuage);
	
	nuage.creer(network);
	
	nuage.afficher();
	*/
	return 0;
}

nuage.h :
#ifndef NUAGE
#define NUAGE

#include "darwin.h"

class Nuage : public Darwin_User
{
	private :

		std::vector< std::vector< std::vector<int> > > m_image;
		int m_x;
		int m_y;
		
		void dessiner(Reseau);
	public :
		Nuage(int x, int y);
		void creer(int nb_generation);
		void afficher();
		virtual void fct_retour(float x, int ID);
		virtual std::vector<int> evaluation(std::vector<Reseau> generation);	
		std::vector< std::vector< std::vector<int> > > get_image();
		float entropie();
		void creer(Reseau);

};


#endif

nuage.cpp :
#include "nuage.h"
#include <opencv/cv.h>
#include <opencv/highgui.h>

using namespace std;

void Nuage::dessiner(Reseau artiste)
{
	vector<float> entree;
	entree.resize(2);
	
	for(int j = 0; j < m_image.size(); j++)
	{
		for(int k = 0; k< m_image[0].size(); k++)
		{
			m_x = j;
			m_y = k;
			entree[0] = float(j)/float(m_image.size());
			entree[1] = float(k)/float(m_image[0].size());
			artiste.entree(entree);
		}
	}
}

Nuage::Nuage(int x, int y)
{
	if(x < 1){x = 1;}
	if(y < 1){y = 1;}
	
	vector<int> vect_temp;
	vect_temp.resize(3, 0);
	
	m_image.resize(1);
	m_image[0].resize(y, vect_temp);
	m_image.resize(x, m_image[0]);
	
	
}

void Nuage::creer(int nb_generation)
{
	Darwin evolueur;
	
	vector<int> geometrie;
	geometrie.push_back(2);
	geometrie.push_back(9);
	geometrie.push_back(16);
	geometrie.push_back(9);
	geometrie.push_back(3);
	
	vector<Reseau> generation;
	
	generation = evolueur.evolution(nb_generation, 32, geometrie, this, this);
	
	dessiner(generation[0]);
	
}

void Nuage::creer(Reseau dessinateur)
{

	if(dessinateur.get_entree()==2 and dessinateur.get_sortie() == 3)
	{
		dessinateur.change_user(this);
		
		dessiner(dessinateur);
	}
}

void Nuage::fct_retour(float x, int ID)
{
	static vector< int > triplet;
	

	triplet.push_back( int(255*x) );
	
	
	if(triplet.size() == 3)
	{
		m_image[m_x][m_y] = triplet;
		triplet.clear();
	}
	
}

 std::vector<int> Nuage::evaluation(std::vector<Reseau> generation)
{
	vector<int> scores;
	scores.resize(generation.size());

	
	for(int i = 0; i < generation.size(); i++)
	{
		
		dessiner(generation[i]);
		scores[i] = int(10000*abs(entropie()));
		
	}


	return scores;
}



std::vector< std::vector< std::vector<int> > > Nuage::get_image()
{
	return m_image;
}

float Nuage::entropie()
{
	//inventaire des différentes couleurs de pixels et comptage
	vector< vector< vector<int> > >	couleurs;
	couleurs.resize(1);
	couleurs[0].resize(1);
	couleurs[0][0].resize(256, 0);
	couleurs[0].resize(256, couleurs[0][0]);
	couleurs.resize(256, couleurs[0]);
	
	int nb_couleur = 0;
	
	for(int i = 0; i < m_image.size(); i ++)
	{
		for(int j = 0; j< m_image[0].size(); j++)
		{
			if(couleurs[m_image[i][j][0]][m_image[i][j][1]][m_image[i][j][2]] == 0){nb_couleur++;}
			couleurs[m_image[i][j][0]][m_image[i][j][1]][m_image[i][j][2]] ++;
		}
	}

	//entropie
	float entropie = 0;
	
	for(int i = 0; i < 256; i++)
	{
		for(int j = 0; j < 256; j++)
		{
			for(int k = 0; k < 256; k++)
			{
				if(couleurs[i][j][k] > 0)
				{
					float x = float(couleurs[i][j][k])/float(m_image.size()*m_image[0].size());
					entropie += x*log(x);
				}
			}
		}
	}
	float lognb = log(nb_couleur);
	return entropie*lognb*lognb;
}


void Nuage::afficher()
{
	IplImage *img = cvCreateImage(cvSize(m_image.size(), m_image[0].size()), IPL_DEPTH_8U, 3);
	CvScalar pixel;
	
	for(int i = 0; i<m_image.size(); i++)
	{
		for(int j = 0; j < m_image[0].size(); j++)
		{
			pixel.val[0] = int (m_image[i][j][0]);
			pixel.val[1] = int (m_image[i][j][1]);
			pixel.val[2] = int (m_image[i][j][2]);
			
			cvSet2D( img, j, i, pixel);
		}
	}
	cvNamedWindow("Image obtenu par un réseau de neurones", CV_WINDOW_AUTOSIZE);
	cvShowImage("Image obtenu par un réseau de neurones", img);
	cvWaitKey(0);
        cvReleaseImage(&img);

}

darwin.h :
#ifndef DARWIN
#define DARWIN
#include "neurone.h"
#include "reseau.h"
#include <cstdlib>
#include <ctime> 
#include <algorithm>

class Darwin_User : public Neurone_User	//utilisateur d'évolueur, pour la fonction d'évaluation (et hérite de Neurone_User pour la fct de retour : formule 2 en 1!)
{
	public :
		virtual std::vector<int> evaluation(std::vector<Reseau>) = 0;
};

class Darwin	//l'évolueur
{
	private :
		
		std::vector<Reseau> generation;	//la génération de neural networks
		std::vector < std::pair < std::vector<float> , int> > genome;	//la génération sour forme de génome (pour la création d'individus)
		std::vector< std::vector< std::pair<int, float> > >  moule;//schéma du réseau : géométrie, connexions (les poids sont présents mais ne serve pas sur cette variable, mais sur ses copies)
		
	protected :
		
		void aleatoire(int depart, int arrivee);//genome aleatoire  de genome[depart] à genome[arrivee] (non inclus)
		void convertir(int nb_entree,int  nb_sortie, Neurone_User *user,  float (*fct_transfert)(float x));//convertis le genome en population de réseaux
		void shuffle(int ID, int last);//intervertit les caractères (poids) de génomes de différents réseaux et en dote le génome[ID]; les modèles sont de génome[0] à last non inclus
		void variation(int ID, int last);//même comportement que shuffle mais cette fois, opère des variations aléatoires comprises dans [-1;1] (+); sur le réseau ID
		std::vector<Reseau> algo_evolve_perso(int nb_generation,  Darwin_User *user1 );//algo génétique homemade ; écrit le champion de la génération courante X touteles nb_génération/10 génération dans le fichier champion_genX
		
	public :
		Darwin();
		std::vector<Reseau> evolution(int nb_generation, int nb_individus, std::vector<int> geometrie_reseau, Darwin_User *user1, Neurone_User *user2,  float (*fct_transfert)(float x) = sigmoide);//processu d'évolution ; nb_idividus est le nombre de réseaux par génération : plus il est grand, plus la diversité sera grande -> convergence plus probable (attention au temps de calcul)
		std::vector<Reseau> evolution(std::vector<Reseau> population, int nb_generation, Darwin_User *user1);//évolution à partir d'une population pré éxistante (par exemple, chargée depuis des fichiers)

		
		
};



#endif

darwin.cpp :
#include "darwin.h"
#include <sstream>
#include <string>
using namespace std;

bool compare_pair(pair < std::vector<float> , int> p1, pair < std::vector<float> , int> p2)
{
	return (p1.second>p2.second);
}

void rand_init()//initialise la génération de nombres aléatoires
{
	static bool initie = false;
	
	if(!initie)
	{
		srand(time(NULL));
		initie = true;
	}
}


Darwin::Darwin()
{
	rand_init();
}


vector<Reseau> Darwin::evolution(vector<Reseau> population, int nb_generation, Darwin_User *user)
{
	
	if(population.size() < 2)//pas d'évolution si un seul individu
	{
		return population;
	}
	
	int taille = population[0].schema().size();
	for(int i = 0; i < population.size(); i++)
	{
		if(taille != population[i].schema().size())//on regarde si tous les réseaux ont la même géométrie, sinon pas d'évolution
		{
			return population;
		}
	}
	
	moule = population[0].schema();//création du moule
	generation = population;
	
	genome.clear();
	
	for(int i = 0; i < generation.size(); i++)
	{
		std::vector< std::vector< std::pair<int, float> > >  schema = generation[i].schema();//création du génome à partir de la population
		int step = 0;
		genome.resize(genome.size()+1);
		for(int j = 0; j < schema.size(); j++)
		{
			
			for(int k = 0; k < schema[j].size(); k++)
			{
				
				genome[i].first.push_back(schema[j][k].second);
			}
		}
	}
	
	return algo_evolve_perso(nb_generation, user);//lancement de l'algo 
}

vector<Reseau> Darwin::evolution(int nb_generation, int nb_individus, vector<int> geometrie_reseau, Darwin_User *user1, Neurone_User *user2,  float (*fct_transfert)(float x) )
{
	generation.clear();
	genome.clear();
	moule.clear();
	
	generation.resize(nb_individus);
	genome.resize(1);
	

	
	for(int i =0; i < geometrie_reseau.size(); i++)//création du moule
	{
		for(int j = 0; j< geometrie_reseau[i]; j++)
		{
			vector< pair<int, float> > vect_temp;
			pair <int, float> pair_temp;
			if(i != 0)
			{
				for(int k = moule.size()-geometrie_reseau[i-1]-j ; k < moule.size()-j; k++)
				{
					pair_temp.first = k;
					pair_temp.second = 1;
					vect_temp.push_back(pair_temp);
					genome[0].first.push_back(1);
				}
				//le biais
				pair_temp.first = moule.size();
				pair_temp.second = 1;
				vect_temp.push_back(pair_temp);
				genome[0].first.push_back(1);
			}
			
			moule.push_back(vect_temp);
		}
	}
	genome.resize(nb_individus, genome[0]);
	
	//première génération aléatoire
	
	aleatoire(0, genome.size());
	convertir(geometrie_reseau[0],geometrie_reseau[geometrie_reseau.size()-1] ,user2, fct_transfert);
	
	return algo_evolve_perso(nb_generation, user1);
	
}
	
vector<Reseau> Darwin::algo_evolve_perso(int nb_generation, Darwin_User *user )
{

	if(generation.size()>1)
	{
		int pallier = nb_generation/10;//intervalle entre deux écritures de champions

		
		for(int i = 0; i < nb_generation; i++)//itération par génération
		{
			
			vector<int> palm_temp = user->evaluation(generation);//on évalue la génération qui nous donne le palmares temporaire (tableau de scores)
			
			
			for(int j = 0; j < palm_temp.size(); j++)	//on met à jour les scores associés aux genomes
			{
				genome[j].second = palm_temp[j];
			}
			
			sort(genome.begin(), genome.end(), compare_pair);//on tri le palmares (le meilleur a la place 0)
			//###########################
			cout<<genome[0].second<<endl;
			
			for(int j =0; j < genome.size(); j ++)//evolution des génomes
			{
				if(j>3*genome.size()/4)
				{
					aleatoire(j, j+1);//le dernier quart aléatoire
				}
				else if(j>2*genome.size()/4)
				{
					shuffle(j, (genome.size()/4)+1);//un quart par shuffle
				}
				else if(j>genome.size()/4)
				{
					variation(j, (genome.size()/4)+1);//un quart par mutation
				}
			}
			//conversion des genomes vers les réseaux et réitération
			
			float (*fct_transfert)(float x);
			
			
			generation[0].get_transfert(&fct_transfert);
			convertir(generation[0].get_entree(),generation[0].get_sortie() ,generation[0].get_user(), fct_transfert);
			
			
			if(i == pallier)//écriture du champion si on a atteint le pallier (cela permet, de savoir ou on en est dans l'évolution, et d'avoir une certaine sauvegarde si le processus s'arrete : annulation/coupure de courant (pas X heures d'évolution perdus))
			{
				std::ostringstream flux;
				flux<<i;
				pallier+= nb_generation/10;
				string fichier = "champion_gen_";
				fichier += flux.str();
				generation[0].ecrire(fichier);
			}
		}
	
	}
	
	return generation;
	
}

void Darwin::aleatoire(int depart, int fin)
{
	for(int i = depart; i < fin; i++ )
	{
		for(int j = 0; j < genome[i].first.size(); j++)
		{
			genome[i].first[j] = float(rand()-RAND_MAX/2)/(RAND_MAX/(2*10));//poids choisit entre -10 et 10; valeurs où sur un float, la fonction sigmoide est (presque) confondue avec ses asymptotes
		}
		genome[i].second = -10000;
	}
}

void Darwin::convertir(int nb_entree,int  nb_sortie, Neurone_User *user,  float (*fct_transfert)(float x))
{
	int step = 0;
	generation.resize(genome.size());
	vector< vector< pair<int, float> > > copie = moule;
	
	for(int i = 0; i<genome.size(); i ++)
	{
		//création copie
		step = 0;
		for(int j = 0; j < copie.size(); j++)
		{
			for(int k = 0; k < copie[j].size(); k ++)
			{
				copie[j][k].second = genome[i].first[step];
				step++;
			}
		}
		
		generation[i].creer(copie, nb_entree, nb_sortie, user, fct_transfert);
	}
	
}

void Darwin::shuffle(int ID, int last)//last non inclus
{
	for(int i = 0; i < genome[ID].first.size(); i++)
	{	
		genome[ID].first[i] = genome[rand()%last].first[i];
	}
	genome[ID].second = -10000;//on donne un score improbable : pas évalué
}

void Darwin::variation(int ID, int last)
{
	int modele = rand()%last;
	
	for(int i = 0; i < genome[ID].first.size(); i ++)
	{
		genome[ID].first[i] = genome[modele].first[i]+(float(rand()-RAND_MAX/2)/(RAND_MAX/2));
	}
	genome[ID].second = -10000;
}

reseau.h :
#include <iostream>
#include <vector>
#include <fstream>
#include "neurone.h"

#ifndef RESEAU
#define RESEAU



class Reseau	//Réseau de neurones
{
	private :
		std::vector< Neurone* > reseau;//tableau des neurones composant le réseau
		int nb_entree;
		int nb_sortie;
		Neurone_User *utilisateur;	//utilisateur du réseau
		float (*m_fct_transfert)(float x);//fct mathématique de transfert
		
		
		
	public :
		Reseau();
		Reseau(const Reseau&);//constructeur de copie
		void creer(  std::vector< std::vector< std::pair<int, float> > >    schema,int entree,int sortie , Neurone_User *user,  float (*fct_transfert)(float x) = sigmoide);//création à partir d'un schéma
		//structure du schema :   neurones< liaisons <source , poids> > 
		
		void entree(std::vector<float>);//on envoie un vecteur d'entrée au réseau
		bool charger(std::string nom_fichier, Neurone_User *user,  float (*fct_transfert)(float x) = sigmoide);//on charge le réseau depuis un fichier et on le fait posséder par user
		bool ecrire(std::string nom_fichier);//enregistrement du réseau dans  un fichier
		std::vector< std::vector< std::pair<int, float> > > schema();//retourne le schéma du réseau (tableau des schéma de neurones)
		int get_entree();
		int get_sortie();
		void supprimer();//supprime le réseau (libère notamment reseau[] alloué dynamiquement)
		Reseau operator=(Reseau );
		~Reseau();
		void get_transfert(float (**fct_transfert)(float x));//retourne la fct mathématique de transfert
		Neurone_User*  get_user();	//retourne l'utilisateur
		void change_user(Neurone_User*);
	
};


/*
Format de fichier : (comme en php, les varibles sont précédées de $) :

$nb_entree
$nb_sortie
N
$ID1 $w1
$ID2 $w2
//...
N
//...
//...
E

exemple : 
3
3
N
0 0
N
1 0
...
E
*/

#endif

reseau.cpp :
#include "reseau.h"
#include <string>


using namespace std;



Reseau::Reseau()
{
	nb_entree = 0;
	nb_sortie = 0;
}



//structure du schema :  <nb_entree , neurones< liaisons <source , poids> > > //le biais est defini par une liaison avec soi même
void Reseau::creer(  vector< vector< pair<int, float> > >    schema , int entree,int sortie , Neurone_User *user,  float (*fct_transfert)(float x) )
{
	supprimer();
	nb_entree = entree;
	nb_sortie = sortie;
	
	utilisateur = user;
	m_fct_transfert = fct_transfert;
				
	for(int i = 0; i < nb_entree; i++)//création des entrées
	{
		reseau.push_back(new Entree(i));
	}
	for(int i = nb_entree; i < schema.size()-nb_sortie; i++)//création des neurones et ajustement du biais
	{	
		int biais = 0;
		for(int j = 0; j < schema[i].size(); j++)
		{
			if (schema[i][j].first == i)
			{
				biais = schema[i][j].second;
			}
		}
		reseau.push_back(new Neurone(i, biais, fct_transfert));
	}
	for(int i = schema.size()-nb_sortie; i < schema.size(); i++)//création des sorties et ajustement du biais
	{
		int biais = 0;
		for(int j = 0; j < schema[i].size(); j++)
		{
			if (schema[i][j].first == i)
			{
				biais = schema[i][j].second;
			}
		}
		reseau.push_back(new Sortie(utilisateur , i, biais, fct_transfert));
	}
	
	for(int i = 0; i < schema.size(); i ++)//ramifications selon le schéma
	{	
		for(int j = 0; j < schema[i].size(); j++)
		{	
			if(i != schema[i][j].first)
			{
				reseau[i]->se_ramifier(*reseau[schema[i][j].first], schema[i][j].second);
			}
		}
	}
	
}

void Reseau::entree(vector<float> valeurs)
{

	for(int i = 0; i < valeurs.size() and i < nb_entree; i++)//on envoie chaque valeur d'entrée au neurone d'entrée correspondant
	{
		
		reseau[i]->recevoir(-1, valeurs[i]);
	}
}


bool Reseau::charger(string nom_fichier, Neurone_User *user,  float (*fct_transfert)(float x) )
{
	
	utilisateur = user;
	ifstream fichier;
	fichier.open(nom_fichier.c_str());
	if(!fichier.fail())
	{
		fichier>>nb_entree;//on lit les entrées
		fichier>>nb_sortie;
		string chaine = " ";
		vector< vector< pair<int, float> > >    schema;
		
		getline(fichier, chaine);
		getline(fichier, chaine);
		
		while(chaine.find('E') == string::npos and !fichier.eof())//tant qu'on est pas à la fin du fichier
		{

			
			pair<int ,float> pair_temp;
			vector< pair<int, float> > vect_temp;
			
			streampos debut = fichier.tellg();
			
			do	//on traite un neurone
			{
				debut = fichier.tellg();
				getline(fichier, chaine);
				
				if(chaine.find('N') == string::npos and chaine.find('E') == string::npos )
				{
					
					fichier.seekg(debut);
					
					fichier>>pair_temp.first;
					fichier>>pair_temp.second;
					
					vect_temp.push_back(pair_temp);
					getline(fichier, chaine);
				}
				
				
			}while(chaine.find('N') == string::npos and chaine.find('E') == string::npos and !fichier.eof());
			
			schema.push_back(vect_temp);
			
			
		}
		
		creer(schema, nb_entree, nb_sortie, user, fct_transfert);
		fichier.close();
		return true;
	}
	else
	{
		return false;
	}
}

bool Reseau::ecrire(string nom_fichier)
{
	ofstream fichier;
	fichier.open(nom_fichier.c_str());
	if(!fichier.fail())
	{
		fichier<<nb_entree<<endl<<nb_sortie;//on écrit entree/sortie
		std::vector< std::vector< std::pair<int, float> > > schemas = schema();
		
		for(int i = 0; i < schemas.size(); i++)//pour chaque neurone...
		{
			fichier<<endl<<"N";
			
			for(int j = 0; j < schemas[i].size(); j++)//... on écrit chaque ID/poids
			{
				fichier<<endl<<schemas[i][j].first<<' '<<schemas[i][j].second;
			}
			
		}
		fichier<<endl<<"E"<<endl;
		fichier.close();
		return true;
	}
	else
	{
		return false;
	}
}


std::vector< std::vector< std::pair<int, float> > > Reseau::schema()
{
	std::vector< std::vector< std::pair<int, float> > > schema;
	for(int i = 0; i < reseau.size(); i++)
	{
		schema.push_back(reseau[i]->schema());
	}
	return schema;
}

int Reseau::get_entree(){return nb_entree;}

int Reseau::get_sortie(){return nb_sortie;}


void Reseau::supprimer()
{
	for(int i = 0; i< reseau.size(); i ++)	
	{
		delete reseau[i];
	}
	reseau.clear();
	nb_entree = 0;
	nb_sortie = 0;
}

Reseau::~Reseau()
{
	supprimer();
}

Reseau::Reseau(const Reseau &modele)//constructeur de copie
{
	std::vector< std::vector< std::pair<int, float> > > schema;
	for(int i = 0; i < modele.reseau.size(); i++)
	{
		schema.push_back(modele.reseau[i]->schema());
	}
	
	//on créer un réseau à partir du modèle
	creer(schema, modele.nb_entree, modele.nb_sortie, modele.utilisateur , modele.m_fct_transfert);
	
}

Reseau Reseau::operator=(Reseau modele)
{

	
	
	creer(modele.schema(), modele.nb_entree, modele.nb_sortie, modele.utilisateur , modele.m_fct_transfert);
	
	return *this;

}

void Reseau::get_transfert(float (**fct_transfert)(float ))
{
	*fct_transfert = m_fct_transfert;
}
Neurone_User*  Reseau::get_user()
{
	return utilisateur;
}

void Reseau::change_user(Neurone_User* user)
{
	utilisateur = user;
	
	for(int i = 0; i < reseau.size(); i++)
	{
		
		reseau[i]->change_user(user);
	}
}

neurone.h :
#include <iostream>
#include <vector>
#include <cmath>

#ifndef NEURONE
#define NEURONE



float sigmoide(float x);

class Neurone_User	//classe virtuelle : le neurone a besoin d'un objet d'une classe hérité pour savoir ou retourner le résultat
{
	public :
	virtual void fct_retour(float x, int ID) = 0;//la sortie du réseau (valeur x, ID du neurone de sortie dans le réseau)
};

struct Dendrite		//symbolise les liaison entre un neurone, et ceux le précédant
{
	int num_source;
	float valeur;
	float poids;
	bool activation;
};

class Neurone	//classe neurone
{
	protected :
		int m_ID;	//Id dans le réseau
		float m_bias;	//le biais (w0)
        	std::vector <Dendrite> m_dendrites;	//liste de connection aux neurones précédent pour recevoir leur valeur
        	std::vector <Neurone*> m_receveurs;	//référence aux neurones suivants pour transmettre la valeur de sortie
     	        float (*m_fct_transfert)(float x);	//fonction mathématique de transfert : f(sigma)
     	
     	protected :
     	        int connexion(Neurone *receveur);	//demande de connexion vers un neurone suivant
     	        
	public :
		Neurone(int ID, float bias = 0, float (*fct_transfert)(float x) = sigmoide);//constructeur
		virtual void recevoir(int num_source, float valeur);			//reçois la valeur et la traite (mathématiquement); appelle récursif permettant la transmission sur tout le réseau
		void se_ramifier(Neurone &source, float poids_dendrite);	//connexion à un neurone précédent
		std::vector< std::pair<int, float> > schema();		//renvoie le schema du neurone (tableau de pairs <ID neurone précédent , poids>)
		virtual char type();					//renvoie le type (Neurone/Entree/sortie)
		virtual void change_user(Neurone_User *user){}		//change d'utilisateur
};

class Entree : public Neurone		//neurone d'entrée du réseau
{
	public :
		Entree(int ID, float bias = 0, float (*fct_transfert)(float x) = sigmoide);
		virtual void recevoir(int num_source, float valeur);			//ne fait que transmettre la valeur
		virtual char type();
		
};

class Sortie : public Neurone	//sortie du réseau
{
	protected:
		Neurone_User *utilisateur;
	public : 
		Sortie(Neurone_User *user , int ID, float bias = 0, float (*fct_transfert)(float x) = sigmoide);
		virtual void recevoir(int num_source, float valeur);	//traite le vecteur d'entree, et le renvoie au Neurone_User
		virtual char type();
		virtual void change_user(Neurone_User *user);
};

#endif

neurone.cpp :
#include "neurone.h"

using namespace std;

float sigmoide(float x)
{
	return 1/(1+exp(-x));
}


Neurone::Neurone(int ID, float bias, float (*fct_transfert)(float x))
{
	m_ID = ID;
	m_bias = bias;
	m_fct_transfert = fct_transfert;
}

int Neurone::connexion(Neurone *receveur)//on ajoute le neurone demandant la connexion à ceux à qui on envoie la valeur
{
	m_receveurs.push_back(receveur);
	return m_ID;
}


void Neurone::recevoir(int num_source, float valeur)
{

	bool activation = true;
	for(int i = 0; i < m_dendrites.size() ; i++)//on ajoute la valeur dans la dendrite correspondant à l'ID source et on regarde si toutes les dendrites ont été activées
	{
		if(num_source == m_dendrites[i].num_source)
		{
			m_dendrites[i].valeur = valeur;
			m_dendrites[i].activation = true;
		}
		if(activation)
		{
			activation = m_dendrites[i].activation;
		}
	}
	
	if(activation)
	{
		float somme = m_bias;
		for(int i = 0; i < m_dendrites.size() ; i++)//somme algébrique (sigma)
		{
			somme+= m_dendrites[i].valeur*m_dendrites[i].poids;
		}
		somme = (*m_fct_transfert)(somme);// f(sigma)
		
		for(int i = 0; i < m_receveurs.size() ; i++)//on transmet la valeur
		{
			m_receveurs[i]->recevoir(m_ID, somme);
		}
		
		for(int i = 0; i < m_dendrites.size(); i++)//on désactive les dendrites
		{
			m_dendrites[i].activation = false;
		}
		
	}
}

void Neurone::se_ramifier(Neurone &source, float poids_dendrite)
{
	Dendrite dendrite;
	dendrite.activation = false;
	dendrite.poids = poids_dendrite;
	
	dendrite.num_source = source.connexion(this);//on demande la connexion au neurone source
	m_dendrites.push_back(dendrite);		//on l'ajoute à la liste des dendrites

}


Entree::Entree(int ID, float bias, float (*fct_transfert)(float x) ) : Neurone( ID,   bias  ,fct_transfert)
{
}

Sortie::Sortie( Neurone_User *user, int ID, float bias, float (*fct_transfert)(float x)) : Neurone(  ID,  bias  ,fct_transfert)
{
	utilisateur = user;
}


void Sortie::recevoir(int num_source, float valeur)
{

	bool activation = true;
	for(int i = 0; i < m_dendrites.size() ; i++)	//même comportement qu'un neurone
	{
		if(num_source == m_dendrites[i].num_source)
		{
			m_dendrites[i].valeur = valeur;
			m_dendrites[i].activation = true;
		}
		if(activation)
		{
			activation = m_dendrites[i].activation;
		}
	}
	
	if(activation)
	{
		float somme = 0-m_bias;
		for(int i = 0; i < m_dendrites.size() ; i++)
		{
			somme+= m_dendrites[i].valeur*m_dendrites[i].poids;
		}
		somme = (*m_fct_transfert)(somme);
		
		utilisateur->fct_retour(somme, m_ID);//au lieu de transferer, on retourne la valeur
		
		for(int i = 0; i < m_dendrites.size(); i++)
		{
			m_dendrites[i].activation = false;
		}
	}
}

void Entree::recevoir(int num_source, float valeur)
{

	for(int i = 0; i < m_receveurs.size() ; i++)//pas de traitement mathématique mais simple transfert pour les Entrées
	{
		m_receveurs[i]->recevoir(m_ID, valeur);
	}
}

std::vector< std::pair<int, float> > Neurone::schema()
{
	vector < std::pair<int, float> > schema;
	std::pair<int, float> temp;
	
	for(int i = 0; i < m_dendrites.size(); i++)
	{
		temp.first = m_dendrites[i].num_source;//renvoie les dendrites et le biais
		temp.second = m_dendrites[i].poids;
		schema.push_back(temp);
	}
	temp.first = m_ID;	//le num_source du biais est l'ID de ce neurone
	temp.second = m_bias;
	schema.push_back(temp);
	return schema;
}

char Neurone::type()
{
	return 'N';
}
char Sortie::type()
{
	return 'S';
}
char Entree::type()
{
	return 'E';
}


void Sortie::change_user(Neurone_User *user)
{
	utilisateur = user;
}



En espérant que cela vous a intéressé!


  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
13 mars 2010 à 16:16:28

Sympa, RandImage à du soucis à se faire :p
  • Partager sur Facebook
  • Partager sur Twitter
13 mars 2010 à 16:22:25

Voila un nouvelle série, plus complexe (j'utilise des réseaux à 7 couches ! : 2/9/16/25/16/9/3 (nombre de neurone par couche))

Image utilisateurImage utilisateur

Image utilisateurImage utilisateur

Image utilisateur
  • Partager sur Facebook
  • Partager sur Twitter
13 mars 2010 à 17:40:16

C'est plutôt joli. Tu as essayé d'entrainer les réseaux à la main ? (ou de créer un site web où les gens doivent comparer deux images et choisir la meilleure si c'est trop long)
  • Partager sur Facebook
  • Partager sur Twitter
13 mars 2010 à 18:16:18

Poulet a eu bien raison d'insister, c'est assez intéressant.
Tu pourrais mettre les sources dans une archive ? Quand c'est long et qu'il a plusieurs fichiers, c'est ultra chiant à lire dans les balises [code]. Les questions que je pose dans le reste du message trouvent sans doute leur réponse dans le code, mais il est difficile à inspecter pour l'instant.

Je n'ai pas encore regardé, mais je ne comprends pas très bien le rapport entre la partie "algorithme génétique" et la partie "réseau de neurones".

Tu as donné ta "fonction d'utilité" des images : tu cherches à produire des images qui maximisent cette fonction. Tu utilises pour cela une méthode d'algorithme génétique : génération de plusieurs essais, mutations, sélection.

Je ne comprends pas ce que vient jouer là-dedans le réseau de neurone. Ton réseau est ton génome ? Comment est-ce que le réseau détermine l'image générée ?
Tu dis que chaque couche de neurones génère une image. Quel est le statut des images que tu nous montres : dernière couche, couche intermédiaire ?
  • Partager sur Facebook
  • Partager sur Twitter
13 mars 2010 à 20:14:57

Salut,

@bluestrorm

Voila un lien vers l'archive : http://rs822.rapidshare.com/files/363361348/NNuage.zip .

Pour créer une image de taille (xmax, ymax), j'utilise des réseaux à 2 entrées et 3 sorties. Pour chaque pixel de coordonnées (x, y), je donne en entrée au réseau le vecteur (x/xmax , y/ymax). La sortie de la dernière couche (comportant trois neurones) me donne la couleur du pixel traité. J'applique ça pour tout les pixel de l'image (de (0,0) à (xmax,ymax)). Quand j'ai parlé de pluralité des couches, c'était pour dire que plus il y a de couche, plus l'image (ou plutot, la fonction que l'on approxime) est complexe (cela fait des "traitements intermédiaires"). Les différentes images sont données par des réseaux différents.

Quant à l'algorithme génétique, je l'utilise pour avoir des réseau créant des images qui maximisent la fonction décrite dans mon premier post. En effet, mes génomes sont les vecteurs de poids des réseaux.

J'espère avoir bien répondu à tes questions. (<HS>et à la finale prologin ^^</HS>)

@gnomnain : je pense avoir répondu à ta question dans la réponse à bluestorm
  • Partager sur Facebook
  • Partager sur Twitter
13 mars 2010 à 21:05:30

Ce que je ne comprends pas très bien, c'est ce que les réseaux neuronaux viennent faire là-dedans. Il me semble que l'intérêt d'un réseau neuronal, c'est la structure locale qui permet de modifier finement le poids d'une connection en fonction de l'erreur entre un résultat et le résultat attendu.

Dans ton application, j'ai l'impression que tu n'utilises pas du tout cette possibilité. Tu as une structure de réseau neuronal qui sert de génome, et que tu modifies au hasard sans faire de propagation d'erreurs. C'est bien ça ?
  • Partager sur Facebook
  • Partager sur Twitter
13 mars 2010 à 21:31:22

Un réseau de neurones est avant tout un approximateur fonctionnel. (EDIT : comme quoi, c'est beau les fonctions ^^)

Ce que tu décris est le fonctionnement de la rétro-propagation, qui est l'un des mécanismes possibles pour faire apprendre à un réseau de neurones, reposant sur le fait que l'on dispose d'une base de données [entrée/sortie souhaitée]. Les algorithmes génétiques en sont un autre (de mécanisme).

Il est en effet possible de faire la même chose en considérant les images (matrices de pixels) comme les génomes et en s'affranchissant des réseaux de neurones. Ça a bien sur comme avantage d'accélerer l'algorithme, cependant, l'utilisation des réseaux de neurones se justifie par leur statut d'approximateur fonctionnel continus : cela donne une cohérence d'ensemble à l'image, à l'inverse d'une matrice de pixels au hasard.
Tu me diras que c'est possible de considérer cette continuité dans la fonction d'évaluation, mais c'est alors plus complexe, et on perd sans doute le précédent avantage de rapidité. (EDIT : de plus, la convergence vers de jolis résultat serait alors beaucoup plus lente et incertaine)

Cependant, ces propos ne sont que ceux d'un lycéen s'étant bien documenté sur le sujet. Il est donc parfaitement possible que je me trompe de bout en bout.
  • Partager sur Facebook
  • Partager sur Twitter
14 mars 2010 à 12:44:01

Salut Mendes ,
nice job . Elle sont chouettes tes images , ça fera de joli fond d'écran ... :)
et utiliser l'entropie est une bonne idée dans ce cas.
Par contre , pourquoi tu ajoutes ça à ta fitness : (log(nb_couleur))²
Si je comprends bien, xi correspond à la fréquence de la couleur i , donc rien qu'avec l'entropie tu as déjà une "récompense" sur le nombre de couleurs. Tu as déjà fait des tests seulement avec l'entropie pour voir l'importance du premier terme ?
Ce que tu faits s'appelle du fitness shapping, une bonne idée plutôt que de faire un produit "brutal" et d'utiliser une somme pondéré comme celle-ci : (alpha) f1 + (1-alpha)f2 . Plus smooth, par contre faut normaliser f1 et f2 ...

Ensuite, pour tes réseaux de neurones, tu devrais essayer d'injecter en entrée les r,g,b de le sortie du pas de temps précédent. Ça devrait donner des trucs sympa .
heu, j'avais pas fait attention : 7 couches ! Ça fait un peu grand comme espace de recherche et je suis pas sur que tes couches hautes t'apportent beaucoup ... Tu ne devrais pas avoir besoin de plus de 3 ou 4 couches.
Si tu veux évoluer des réseaux plus grands et plus complexe , tu devrais t'orienter vers les ESN ( echo state network ) et les techniques de reservoir computing => au final tu as une dynamique plus complexe et tu restreint ton espace de recherche ( exactement ce que tu veux au final ).
Sinon , si en plus tu veux en plus taper dans la topologie du réseau, l'état de l'art c'est NEAT et Hyper-NEAT. Par contre c'est gourmand en ressource ... :)

Sinon rien à redire sur la méthode ! :) , par contre cite tes sources ... :p

++ et bonne continuation


##########################################

Quelques remarques :

sednem => "Il est en effet possible de faire la même chose en considérant les images (matrices de pixels) comme les génomes et en s'affranchissant des réseaux de neurones. Ça a bien sur comme avantage d'accélerer l'algorithme"

Ça va surtout augmenter la taille de ton espace de recherche !

sednem => "cependant, l'utilisation des réseaux de neurones se justifie par leur statut d'approximateur fonctionnel continus : cela donne une cohérence d'ensemble à l'image, à l'inverse d'une matrice de pixels au hasard.
Tu me diras que c'est possible de considérer cette continuité dans la fonction d'évaluation"

Vrai. et ajouter cette "continuité" à la fitness doit être chiant à faire ( au hasard => clustering sur l'image et application de l'entropie sur le clustering). Le résultat risque de ne pas etre aussi lisse qu'avec le réseau de neurones. Et encore une fois, la taille de l'espace de recherche sera beaucoup plus grand ! et beaucoup plus chaotique ! L'algo évo aura besoin de plus d'itération .

#############################################################################
  • Partager sur Facebook
  • Partager sur Twitter
14 mars 2010 à 13:58:35

Bon, j'vais passer un peu ridicule après les réseaux neuronaux, mais je tenais a poster cette image.
Il s'agit d'un simple dégradé, mais je recommande aux débutants d'essayer de le faire. Cela ne demande pas trop de connaissances en mathématiques, contrairement a presque toutes les fractales.
Voilou : )

Image utilisateur

code :
void	        draw(libX *libx)
{
  int		x;
  int		y;
  t_rgb		color;
  int		final_color;

  y = 0;
  while (y < WIN_Y)
    {
      x = 0;
      color.g = (float)(y * 255) / WIN_X;
      while (x < WIN_X)
	{
	  color.r = (float)((WIN_X - x) * 255) / WIN_X;
	  color.b = (float)(x * 255) / WIN_X;
	  final_color = (color.r << 16) + (color.g << 8) + color.b;
	  pixel_put_libX(libx, x, y, final_color);
	  x++;
	}
      y++;
    }
}
  • Partager sur Facebook
  • Partager sur Twitter
Découvrez un petit jeu Android bien sympa : http://www.siteduzero.com/forum/sujet/appli-jeu-android-cube-racer
14 mars 2010 à 20:37:47

J'ai modifié quelques points de mon algo et j'ai obtenu des images encore plus magnifiques (qui font penser à la fucaa de Nausicaa, pour ceux qui connaissent (vite fait les couleurs de la 2eme image)).

J'edit le code et je poste les images dans la soirée.

EDIT :
Image utilisateurImage utilisateur

Image utilisateurImage utilisateur


Citation : chewbak


Par contre , pourquoi tu ajoutes ça à ta fitness : (log(nb_couleur))²


C'est pour augmenter le favoritisme envers les réseaux produisant des image très variées (l'entropie, en effet, le fait déjà un peu)

Citation : chewbak


Ensuite, pour tes réseaux de neurones, tu devrais essayer d'injecter en entrée les r,g,b de le sortie du pas de temps précédent. Ça devrait donner des trucs sympa .


Tu veux dire pour faire une video? C'est une idée pas mal.


Sinon, comme dit plus haut, je viens de corriger un bug et j'edit le code. Re-télechargez donc la nouvelle version.
Maintenant, je vais tenter d'optimiser tout ça.

EDIT : je me suis refait une génération. C'est tellement magnifique que je ne peux m'empecher de les partager. Bon, à partir de maintenant je les met en secret pour ne pas surcharger le topic ^^ :


petite préférence pour la quatrieme ^^.

Image utilisateur

Image utilisateur

Image utilisateur

Image utilisateur

Image utilisateur



RE EDIT :
J'ai mis également en entrée de mon réseaux la distance (normalisée entre 0 et 0,5) au centre de l'image et j'obtiens des individus pas mal :

Image utilisateur

Image utilisateur


Éviedemment, en normalisant cette distanc entre 0 et 5, l'aspect ciculaire est beaucoup plus visible :

Image utilisateur

J'en ai fait bien d'autre, mais je n'ai plus de place sur le ftp du sdz -_-". Dès que je peux, je les met toutes sur un ftp et je vous met le lien ici.

EDIT2 : voilà, vous pouvez trouver tous les résultats, images et vidéos ici :

http://biblistory.teria.fr/telechargement/NnArt/

(Pour le vidéo, je met une entrée en plus, l'entrée temps, et pour l'évolution, j'évalue chaque génération avec un temps aléatoire).
  • Partager sur Facebook
  • Partager sur Twitter
27 mars 2010 à 12:56:30

Hello.

Voici un petit script inutile qui transforme des images jpg en image ascii :)

Quelques tests :
http://vlad.washere.free.fr/ascii/1.html
http://vlad.washere.free.fr/ascii/2.html

Pensez a dezoomer sur votre navigateur si vous voulez que l'affichage se fasse correctement : appuyer sur ctrl + faites bouger la roulette de la souris (ctrl + la touche 0 pour revenir à la dimension intiale)

Et le code :
<?php
if (isset($_GET['image']))
{
	$file_name = $_GET['image'];
	$size = getimagesize($file_name);
	$image = imagecreatefromjpeg($file_name);
	$y = 0;
	$ascii = '<html><body><p>';
	while ($y < $size[1])
	{
		$x = 0;
		while ($x < $size[0])
		{
			$color = imagecolorat($image, $x, $y);
			$ascii .= '<span style="color:'.dechex($color).';font-weight:bolder;"> M </span>';
			$x += 1;
		}
		$ascii .= '<br />';
		$y += 1;
	}
	$ascii .= '</p></body></html>';
	echo $ascii;
	imagedestroy($image);
}
?>


PS : Oui oui, on suit toujours :)
  • Partager sur Facebook
  • Partager sur Twitter
Découvrez un petit jeu Android bien sympa : http://www.siteduzero.com/forum/sujet/appli-jeu-android-cube-racer
27 mars 2010 à 13:10:05

C'est amusant, mais à mon avis un peu trop simple pour être vraiment intéressant. Tu n'exploites que la couleur du support (le texte d'une page web) et pas du tout le fait que c'est du ASCII. En gros ça revient à écrire des pixels beaucoup plus gros.

Je pense que tu devrais essayer de pousser les choses en exploitant la capacité des lettres à dessiner des formes. Le but serait d'obtenir un rendu reconnaissable en utilisant beaucoup moins de lettres à résolution équivalente.
Une idée simple, pour commencer, serait de limiter les couleurs que tu t'autorises à une palette restreinte, et de jouer sur la forme des lettres pour la luminosité de ces couleurs : si tu veux une couleur pâle, au lieu de changer la couleur de la lettre, tu choisis une lettre moins "lourde" qui laissera plus de blanc sur l'écran, comme par exemple une minuscule, puis un signe de ponctuation.

Ensuite on peut faire des choses plus sophistiquées en exploitant la forme des lettres. Si tu calcules la "direction" des traits dominants en un point de l'image, tu peux produire le caractère approprié ( | / \ — X ), par exemple.
  • Partager sur Facebook
  • Partager sur Twitter
27 mars 2010 à 13:41:17

J'y ai pensé aussi, j'avais commencé a faire quelque chose en utilisant toutes les lettres de l'alphabet et une palette de couleur de niveau de gris. Ca ne marche pas encore très bien actuellement, je posterais ca une fois fini.
  • Partager sur Facebook
  • Partager sur Twitter
Découvrez un petit jeu Android bien sympa : http://www.siteduzero.com/forum/sujet/appli-jeu-android-cube-racer
27 mars 2010 à 21:50:36

Citation : bluestorm

Je pense que tu devrais essayer de pousser les choses en exploitant la capacité des lettres à dessiner des formes. Le but serait d'obtenir un rendu reconnaissable en utilisant beaucoup moins de lettres à résolution équivalente.
Une idée simple, pour commencer, serait de limiter les couleurs que tu t'autorises à une palette restreinte, et de jouer sur la forme des lettres pour la luminosité de ces couleurs : si tu veux une couleur pâle, au lieu de changer la couleur de la lettre, tu choisis une lettre moins "lourde" qui laissera plus de blanc sur l'écran, comme par exemple une minuscule, puis un signe de ponctuation.



Oui, je pense que l'on peut s'inspirer pour la mise en place de cette méthode de la technique de patterning en tramage d'images (cf. ce doc).

Citation : bluestorm

Ensuite on peut faire des choses plus sophistiquées en exploitant la forme des lettres. Si tu calcules la "direction" des traits dominants en un point de l'image, tu peux produire le caractère approprié ( | / \ — X ), par exemple.



Effectivement, c'est la que ca devient un peu plus compliqué...
J'imagine qu'il existe encore d'autres approches (avec diverses combinaisons en prime) plus ou moins compliquées.

PS: Je viens de découvrir que VLC permet de lire une vidéo en ASCII-Art... Impressionnant !
  • Partager sur Facebook
  • Partager sur Twitter
27 mars 2010 à 22:10:58

Salut !

On voit de jolis trucs sur ce post. Ça permet de dévouvrir de nouvelles choses !

Voici ma petite contribution, un sobel, il me semble pas en avoir vu dans les posts précédents (beaucoup de fractales par contre...). Par contre, je l'admet, le code n'est pas très propre (premier programme en C++ :-° )

PS : si vous connaissez une librairie qui permet de sauver des images facilement, je suis preneur, parce que là, tout est codé à la main (format PPM) !

Lena Sobel

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include <cmath>
#include <sstream>

#define PI 3.141592653589


double gauss(double sigma, double x)
{
	return 1 / sqrt(2 * PI * sigma * sigma) * exp(-(x * x) / (2 * sigma * sigma));
}

class PPM_image
{
public:
	// Attributes
	int w, h;
	unsigned char* pixels;
	
	// Constructors
	PPM_image(const char* filename);
	PPM_image(const int width, const int height);
	PPM_image(const PPM_image &image);
	~PPM_image();
	
	// Utilities
	unsigned char* get_pixel(const int x, const int y) const throw (int);
	void save(const char* name) const;
	
	// Manipulation
	void gaussian_blur(const int str);
	void edge();
	void add(PPM_image &image);
	void revert();
};

PPM_image::PPM_image(const char* filename)
{
	std::ifstream file(filename);
	
	if(file.is_open())
	{
		std::string line;
		std::getline(file, line);
		
		// P6 = RAW
		// P3 = ASCII
		if(line != "P6")
		{
			std::cout << "Pas une image en PPM\n";
			exit(1);
		}
		
		std::getline(file, line);
		
		int max_value;
		file >> w >> h >> max_value;
		
		if(max_value != 255)
		{
			std::cout << "max_value != 255\n";
			exit(1);
		}
		
		file.get();
		
		pixels = new unsigned char[w * h * 3];
		file.read((char*)pixels, w * h * 3);
		
		file.close();
	}
}


PPM_image::PPM_image(const int width, const int height)
{
	w = width;
	h = height;
	
	pixels = new unsigned char[w * h * 3];
}

PPM_image::PPM_image(const PPM_image &image)
{
	w = image.w;
	h = image.h;
	
	pixels = new unsigned char[w * h * 3];
	
	for(int i = 0; i < w * h * 3; i++)
	{
		pixels[i] = image.pixels[i];
	}
}


PPM_image::~PPM_image()
{
	delete[] pixels;
}

unsigned char* PPM_image::get_pixel(const int x, const int y) const throw (int)
{
	if(x < 0 || x > w || y < 0 || y > h)
		throw 1;
		
	return &pixels[3 * (y * w + x)];
}

void PPM_image::save(const char* filename) const
{
	std::cout << "Saving file " << filename << "...\n";
	std::ofstream file(filename);
	
	if(file.is_open())
	{
		file << "P6\n# comment\n" << w << " " << h << "\n255\n";
		
		file.write((char*)pixels, w * h * 3);
	}
	
	file.close();
	std::cout << "Ok\n";
}

void PPM_image::gaussian_blur(const int str)
{
	std::cout << "Gaussian blur...\n";
	
	// Create kernel
	double** mat = new double*[2 * str];
	
	for(int i = 0; i < 2 * str; i++)
	{
		mat[i] = new double[2 * str];
		
		for(int j = 0; j < 2 * str; j++)
		{
			mat[i][j] = gauss(str, sqrt((i - str) * (i - str) + (j - str) * (j - str)));
		}
	}
	
	for (int i = 0; i < w; i++)
	{
		std::cout << "\r" << (int)(100 * (float)(i + 1) / (float)w) << "%";
		//std::cout.flush();
		for (int j = 0; j < h; j++)
		{
			double r = 0, g = 0, b = 0;
			double coeff = 0;
			
			for (int k = -str; k < str; k++)
			{
				for (int l = -str; l < str; l++)
				{
					try
					{
						unsigned char *p = get_pixel(i + k, j + l);
						
						double norm = mat[k + str][l + str];
						
						r += *p * norm;
						g += *(p + 1) * norm;
						b += *(p + 2) * norm;
						
						coeff += norm;
					}
					catch(int n)
					{
					
					}
				}
			}
			
			unsigned char *p = get_pixel(i, j);
			
			if(coeff > 0)
			{
				*p = (int)(r / coeff);
				*(p + 1) = (int)(g / coeff);
				*(p + 2) = (int)(b / coeff);
			}
		}
	}
	
	std::cout << "\n";
}

void PPM_image::edge()
{
	std::cout << "Edge detection...\n";
	PPM_image img(*this);
	
	for (int i = 1; i < w - 1; i++)
	{
		std::cout << "\r" << (int)(100 * (float)(i + 2) / (float)w) << "%";
		std::cout.flush();
		for (int j = 1; j < h - 1; j++)
		{
			int val = 0;
			
			for(int k = 0; k < 3; k++)
			{
				int Gx = *(img.get_pixel(i - 1, j - 1) + k) + 2 * (*(img.get_pixel(i, j - 1) + k)) + *(img.get_pixel(i + 1, j - 1) + k)
					- *(img.get_pixel(i - 1, j + 1) + k) - 2 * (*(img.get_pixel(i, j + 1) + k)) - *(img.get_pixel(i + 1, j + 1) + k);
				int Gy = *(img.get_pixel(i - 1, j - 1) + k) + 2 * (*(img.get_pixel(i - 1, j) + k)) + *(img.get_pixel(i - 1, j + 1) + k)
					- *(img.get_pixel(i + 1, j - 1) + k) - 2 * (*(img.get_pixel(i + 1, j) + k)) - *(img.get_pixel(i + 1, j + 1) + k);
				int G = sqrt(Gx * Gx + Gy * Gy);
				
				val += G;
			}
			
			val /= 3;
			
			for(int k = 0; k < 3; k++)
			{
				if(val > 255)
				{
					*(get_pixel(i, j) + k) = 255;
				}
				else
				{
					*(get_pixel(i, j) + k) = val;
				}
			}
		}
	}
	
	std::cout << "\n";
}

void PPM_image::revert()
{
	std::cout << "Reverting...\n";
	
	for(int i = 0; i < w * h * 3; i++)
	{
		pixels[i] = 255 - pixels[i];
	}
	
	std::cout << "Ok\n";
}

void PPM_image::add(PPM_image &image)
{
	for(int i = 0; i < w * h * 3; i++)
	{
		pixels[i] = (pixels[i] + image.pixels[i]) / 2;
	}
}

int main(int argc, char *argv[])
{
	PPM_image input("lena.ppm");
	
	input.edge();

	input.save("output.ppm");
	
	return 0;
}

  • Partager sur Facebook
  • Partager sur Twitter
28 mars 2010 à 1:00:02

@tatrefthekiller : easyBMP ça casse pas trois pattes à un canard mais ça fonctionne plutôt bien et facilement sinon QImage si tu utilises Qt est pas mal non plus :)
  • Partager sur Facebook
  • Partager sur Twitter
28 mars 2010 à 11:54:02

Ou opencv, pour le traitement et la sauvegarde.
  • Partager sur Facebook
  • Partager sur Twitter
28 mars 2010 à 13:12:29

Je viens de regarder opencv, il permet de faire tout ce que j'ai fait en 2 lignes de code ! Ça perd tout l'intérêt !
Je vais rester sur ma solution à la main, ou bien regarder du côté de easyBMP...
  • Partager sur Facebook
  • Partager sur Twitter
28 mars 2010 à 13:18:35

Tu peux aussi utiliser OpenCV pour le chargement et la sauvegarde des images, et coder les manipulations proprement dites à la main. À mon avis c'est même une très bonne solution, parce que tu peux choisir le niveau d'abstraction auquel tu te places (par exemple, tu peux choisir ta convolution à la main, et utiliser une primitive OpenCV pour l'appliquer).
Si par la suite tu as envie de faire des choses plus poussées, tu pourras choisir ce que tu codes toi-même et ce que tu laisses OpenCV faire parce que c'est moins intéressant (je pense à la conversion d'espace de couleurs par exemple).
  • Partager sur Facebook
  • Partager sur Twitter
28 mars 2010 à 13:20:36

Je suis d'accord avec bluestorm. Opencv te permettra de tout faire en 2 lignes, mais tu peux aussi bien tout coder si tu en as envie. Tu n'es pas obligé d'utiliser toutes les fonctions fournies, mais quand tu en auras envie parce que le travail t'intéresse moins, elles seront là.
  • Partager sur Facebook
  • Partager sur Twitter
28 mars 2010 à 16:52:50

Ok, merci des conseil.

J'ai également une petite question : comme tout le monde ici, j'ai fait un programme pour dessiner l'ensemble de Mandelbrot. Il fonctionne correctement, mais si je tente de créer des images en haute résolution, ou de zoomer sur une partie de l'image, la précision n'est pas au rendez-vous.
Je pense que les double que j'utilise atteignent leur limite, et j'aimerai savoir s'il existe (en C++) un type de données permettant un zoom "infini" (précision arbitraire sur des flottants), ou bien une autre méthode de calcul qui permettrai d'atteindre le même résultat.

Si la réponse ne tient pas en 2 lignes, j'ouvrirai un nouveau sujet :)
  • Partager sur Facebook
  • Partager sur Twitter
28 mars 2010 à 17:19:09

Le plus simple est d'utiliser des bibliothèques de calcul à précision arbitraire. Je ne connais pas très bien les offres en ce sens pour le C++, mais la bibliothèque GMP est assez populaire.
  • Partager sur Facebook
  • Partager sur Twitter
28 mars 2010 à 20:21:43

Citation : tatrefthekiller

J'ai également une petite question : comme tout le monde ici, j'ai fait un programme pour dessiner l'ensemble de Mandelbrot. Il fonctionne correctement, mais si je tente de créer des images en haute résolution, ou de zoomer sur une partie de l'image, la précision n'est pas au rendez-vous.
Je pense que les double que j'utilise atteignent leur limite, et j'aimerai savoir s'il existe (en C++) un type de données permettant un zoom "infini" (précision arbitraire sur des flottants), ou bien une autre méthode de calcul qui permettrai d'atteindre le même résultat.



Je ne pense vraiment pas que ce soit une limitation liée au type double ou même float. J'ai déjà codé une mandelbrot zoomable sans aucun problème (a moins que tu ne zoome vraiment très très beaucoup...).

Je crois plutôt que ton problème est lié a la palette que tu utilise. En effet, plus tu va zoomer sur un détail et plus l'image va devenir floue si tu n'a pas une très large palette pour représenter les nuances présentes. Une des méthodes couramment utilisées, je crois, est d'augmenter progressivement le nombre de couleurs de la palette en meme temps qu'on zoome.

yoch
  • Partager sur Facebook
  • Partager sur Twitter
28 mars 2010 à 21:18:20

Je viens de tester ton programme, mais ça fait la même chose, quand on zoom (même un peu), les ramification sont beaucoup moins nombreuses que sur les images que l'ont peut trouver sur internet.

mandelbrot
  • Partager sur Facebook
  • Partager sur Twitter