How to create NFT generative art using Python (full project tutorial)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everyone welcome to this tutorial in this project we're going to use python to create an nft art collection from scratch we're going to start with an empty project and go from that to something looking like this in the next 45 minutes and i'll be showing you how to do this by step with each one adding something new to our image generating process before we start i just want to give you a little bit of a background context on what we're actually building here i recently created an nft art collection where each image is generated by python and then each given a unique title by openai gpd3 i did this as a way to learn about nfts especially from a technical standpoint especially given how much buzz it is generating on the internet right now with some pieces of art selling for hundreds of thousands of dollars and as part of this effort i ended up creating a python script to generate artwork and openai prompt to give each one a unique title a react front end as a gallery and an erc 720 contract to deploy the collection onto the ethereum blockchain although i didn't end up using the actual contract since there is a massive cost to deploy it so i just uploaded these images to openc instead where the cost of publishing to that is free in this video i'll be focusing on the python process used to generate each of these images which i think is the most important or at least the most interesting part of this project and as always i've included the github links to both the original project and the simplified tutorial version of this project in the description below generative artwork is already quite a big deal in the nft scene one of the most famous collections in this category is called art blocks with some pieces from that collection selling for upwards of millions of dollars the generative algorithm on art blocks is a lot more complex than what we're building today and what's more the art blocks images are saved onto the ethereum blockchain itself whereas the ones we're doing here today are only saved as conventional png images so it's not quite the same thing but we have to start somewhere to follow along with this tutorial all you need is some basic python knowledge i'll be using python version 3.6 and vs code as my editor we'll also be needing to use the pillow library so you also need to know how to install packages using a python package manager such as pip by the end of this video you'll know how to create and save images in python draw things onto the image and manipulate the color channels and hopefully that gives you a solid foundation on image generation and processing using python especially if you haven't done it before so i think that's enough background context for now let's go ahead and get started okay so we're just going to get started with a completely empty project first thing to do is obviously create our file generateart.py and we're just going to make a function that runs the whole thing say hello world i always like to start projects with a hello world okay so now that we have this let's just test it real quick make sure it runs all right it works let's change this as something more appropriate generating art run okay still works also we should probably do this properly add this snippet here which this means that this will only run if this file is run directly as a script so if we import this into another file this will not execute with this condition but that shouldn't change things for how we run it here okay okay so now we've got the program we've got the way to run it uh let's actually create an image and save it into the folder first we're gonna need an external library the only one we need for this whole project which is pillow python imaging library we install it like this pip install pillow so to create an image we need to first import the image module and we can just create it like this the arguments are mode size and color pretty sure mode is just the color mode and rgb should just be the default one we use so that's the red green blue channel size is a tuple guessing width and height and then color also a tuple probably the rgb values um yeah let's just try that so let's make it 128 by 128 and then the color can be uh well i think the rgbs usually come in two forms they either max out at one so the values are between zero and one and it's a floating decimal point or it's each value is one byte which means it has a max number of 256 values so 255 would be the maximum in this case this should give us a white image so if we run it again though nothing happens because we just generate this image in memory and we don't actually display or save it so let's just go ahead and save it real quick and that's just the file path we want to use if we don't put anything like you know at the top then that just means that we save it to the directory that we run it from so give that a shot and now it appears and if i click that we should see our white square i'm gonna just see if i can put this on the side actually so we can watch it change as we are working on this okay um but before we go further let's just clean this up a bit and get all these values out so that it's a little bit easier to read let's go with image size pixels b128 this and then image background color can be okay okay so let's run it again make sure it's still the same and it is okay so that looks pretty good we've got our image we've got our values extracted here i'll put that on a different line so we can actually see it and i think these are keyword yeah these are keyword arguments as well so might as well just declare them so that we can look at these list of arguments and it's immediately clear what each one is referring to okay so we've created our base image the next thing i want to do is add some details to this so um very easy way is just to draw some lines on top of it to draw lines on the image though we have to create something called a drawing interface with pillow so there's another class for that and we do it like this [Applause] actually i'm just gonna draw some lines [Applause] okay this is basically just an object we can interact with to modify the image here and if we look at the functions it has available it's got line text point polygon we just want to use the line and the arguments it has x y fill which is the same as color i think and the width of the line so x y is the position so i think if we try it out we could just do something like this well a line has two points it's drawing from one point to another so it needs four values because each point has an x and a y coordinate i don't think it matters if it's flattened into one tuple like this or whether it's separate like explicitly into two points like this i think this is probably better though so let's say we wanted to draw a line from the top left corner which is zero zero um i'm assuming this is how pythons pillows coordinating system works this would be 0 0 so this would be whatever the value size of our image is which is handily right here so to draw a line from this corner to this corner we'll just do this i don't know if it actually has a default color or thickness value let's give that a shot and see if anything happens you don't really see anything change here we probably need to fill so if the image is white let's make the line black so that it stands out okay so now it shows up great in fact let's put that into a variable as well because we want to change that later all right okay we still have our single line drawn across um okay now one line is really interesting already but i think we're probably gonna need a little bit more so let's go ahead and run a loop here to make it draw uh a few more lines i think we only need to create the drawing interface once so we don't have to put this into the loop we'll start the loop from here for i in range say 10 we're just going to keep drawing lines but if we don't change it then we're just going to draw the same line over and over and i don't think anything will happen yeah it's just on top of each other here so for each point i'm going to pick a random point in this size this image size um i could do it like this i guess random point well we're going to need a new library okay we'll import the random library and we'll go with random rand int between two integers a and b including both endpoints that's fine so this is how we get a random point between zero and the max image size but it is a point so we'll need to do it twice we actually need two points so we'll do this twice all right already not starting to like where this is going but let's just get it done for now okay so we've got two random points now and then we're gonna draw a line between those two and we're gonna do this ten times we don't even need this i so i'm just gonna do an underscore here usually that's a notation for saying that oh we need a variable placeholder but we don't actually intend to use it all right i've got a bunch of random lines looks a little bit better but still a bit too random so the next thing is i probably want to give this a little bit more intention i can do that by connecting each line so instead of just drawing a bunch of different lines randomly i can uh make each one join to the next line that is being drawn so there's at least some kind of shape coming out of it all right so let's give that a go to do that i probably instead of wanting to generate random points each time i want to generate just a set of random points and then i'm going to connect those points with lines and then when i reach the last point i might make it join back with the first point so let's give that a go we actually might need two loops here yeah just go ahead and copy this [Applause] create our points array we don't need two random points we just need one because we're putting in one at a time then i'll do [Applause] points append random point okay so now i'll have 10 random points in this array and let's label this actually generate the points and in this one we'll actually draw the points well here we don't need to actually we need an iterator now because we need to loop through this point array so i'm going to do this i'm going to call these p1 and p2 so we're going to draw from p1 to p2 p1 is obviously going to be our point that we're using here and uh p2 is going to be the next point in the list except when we're at the end of the list in which case it's the first point because we want it to connect back so we need to first check if we're at the end of the list we can check if i equals to the length of points minus 1. if it is then our p2 is the very first point otherwise p2 is the next point so now the points should be connected let's give that a go okay looking good well now we have some kind of shape i kind of don't like how it's sort of really cramped at the edges here though it doesn't look like good spacing to me so i want a bit more negative space we're going to add a bit of a padding and say that the valid boundary to draw is within a certain distance away from the edge so we can do that by coming up with a padding number and then just setting this randomization to be in between those numbers all right let's go ahead and do that padding pixels let's say 10 i guess it's about 12. and we can now only random between 12 and the size -12 so that brings in the edge a little bit okay see that works [Applause] so now we should no longer have something that's just like really close to the edge they should all be fairly centered [Applause] okay actually i might see what it looks like if i increase it a little bit more let's give that a go it's good enough okay cool make this a bit smaller so we can see more all right um okay so we've got a shape we've got it away from the edges uh let's make the shape a little bit more interesting so an idea i have is to actually if you remember drawing the line here we also have this width parameter so i actually want to see if i can change the thickness of the line as we're drawing it just to make it look like the shape is more dynamic if we start with thickness one here and use that uh we should still see the same results but now on each loop of this i want the thickness to go up so actually let's initialize it up here and then here make it gain one thickness i guess we should do a zero if we're gonna add thickness here and we wanna start at one we should start this one at zero all right okay that's interesting all right so the shape looks a bit more dynamic now there's kind of like it gains uh weight as it's moving around the canvas okay so now i've got the thickness of the line changing i've got the canvas it's starting to look pretty interesting to me um next step is let's add a bit of color to this and see what that looks like so if you recall the color is here and i can make each line have a different color so what if i just do a random color thing here like this now actually i'll make that into a function because i might use that a little bit more than just once so this will return a random color and if you remember the color is between zero and two five five we just need to do that three times one for each channel okay let's give that a go so now each one should have a random color okay that looks pretty funky um but if you have too many colors i think it just looks a little bit clashing so what i kind of want to do is have it start from one color and then change as it goes slowly fade into another color so i want a starting color and an ending color and i want to be able to change between them as i'm going through this shape so let's go ahead and do that we'll do it up here actually since this is where we're all setting the colors and everything so we're going to say start color equals random color and color equals random color okay so now if i add the start color to here um well it's all just going to be a solid color and that's fine but what i wanted to do is as it's going through the shape fade from my start color towards my end color um okay so i'm going to need a function to interpolate between these two color values let's just make that here interpolate then we're just going to do c1 and c2 and we're going to do a factor which is going to be a float okay so the factor is going to be between 0 and 1 and if it's 0 it's going to be completely color 1 and if it's 1 it's going to be completely the end color actually just call it like this make it a little bit more clear okay um and if it's 0.5 it's going to be halfway between start color and end color so this way if we can work out the factor which is just linear from zero to one we'll be able to get a nice gradient but first we have to figure out how to do this between each colors it's actually quite simple i think um for each channel oh actually we need to first work out the reciprocal of factor i always don't manage to spell this word so i'm just going to write reset um so reset reciprocal and factor always add up to one and for each channel we can just do this start color times uh hold on if we want factor if we we want this to be full when this is zero okay so this is times the reciprocal plus and color times factor okay and we just repeat that for each of the channels we have three channels so three times red green and blue all right and now if i use interpolate down here i should be able to get my color so what's our factor going to be actually we have this iterator here which is going to go from 0 to the length of this minus 1. in fact we already have the length of this minus 1 here so i'm going to extract this here if we divide this by the length we should get a number between 0 and 1 which we can use as our factor okay so i'm going to store that here since we're using it twice i'll have to perform the same calculation twice and this is just going to be i minus one color factor oh also i forgot we probably need to convert all of these back to integer because when we do this multiplication it's going to turn it into a float i don't think our framework will be happy about that so let's put these brackets back on all right let's run this okay there we go so now there is a little bit of a fade going on okay that's pretty good already um something i've noticed as well is that sometimes these images aren't exactly centered let me see if i can find an example like this one here it's because we generate random points there's no guarantee that it's gonna be evenly distributed on the canvas so we end up with something that can just like sort of be lying in a corner here uh so what we're gonna do next is try to center this image before we center it let's first generate a bounding box around the image because we want to see where if we drew a box around it what it actually looks like because sometimes like the shapes it's kind of hard to tell where the edges actually are so next this process is to draw a bounding box and to draw the bounding box we'll have to find the minimum and maximum uh value of all of the points and we'll draw use that to draw a box okay so we have to do that after we generate the points and we need the minimum and maximum of each value so start with x so to do this we can use the min function we just have to pass in an array of all the possible x values in here which we can get by doing this for p in points it's not just p though it is p0 because x is in the zero index and we also need max and we need to do the same for y positions okay so we've got our points now i actually want to draw the rectangle ah i think i could just pass in all of these points not sure let's give it a go i think they might have to alternate though it's just my feeling fill oh we don't need to fill we want an outline okay let's make the outline red all right see if it works oh wow okay so we've got our bounding box um and you can already see that yeah it's not very centered there's uh this one is a little bit too high so we want to move that down and maybe also a little bit to the left to do that we have to work out what the difference is between its distances from the edge x and y respectively and then move it halfway um in the direction that it's overcompensated in so yeah yeah i think that's so let's first work on getting these gaps from away from the edge if we have one that's really off center okay like this one here uh okay so if i want this distance i want this distance here i also want this distance here this is going to be well this is just going to be min x because this is zero so this is yeah this distance is literally min x and this distance is the size of the image minus max x and i want the difference between them so i could do this minus this okay okay um what do i want to do with the delta x though so in this case this minus this is going to be a negative number because this one's bigger but i want it to move to the right so i need to negate it again so basically i need to subtract delta x from all of my x coordinates to move it to the right because the delta x here is going to be negative right this is smaller than this so a small number minus a bigger number that's going to be negative but i want to move it to the right which is a positive number so i'll have to subtract it from each of the x positions here but i only want to subtract half because i don't want to move it all the way i just want to move it halfway between these two points okay so let's give that a try um for i point and enumerate points [Applause] minus delta x we're going to leave y unchanged for now we'll go back and deal with it later okay so if this works then the y position is just going to be the same let's give that a go uh okay so i think this is working but if you just look at the box you can still see that it's off center that's because we draw the bounding box before we center the points so let's make it so that we draw well actually no let's keep this one and let's draw another set of bounding boxes after we center it and i'm going to make this one um i'm going to make this one a light gray i'm just going to turn all these values to really close to white it's ready to go okay and then i'm gonna make keep the new one red so we can see how that looks yeah okay so you can see now that it used to be a little bit off-center and just shift it over slightly and the y position is still obviously not fixed because we haven't implemented that for y yet okay so i'm pretty happy with the x formulation let's do the y one as well delta y is gonna be yeah similar well it's literally just the same thing but with all the y positions okay [Applause] let's give that a go yep you can see now all our images are happily centered okay cool well we don't need this stuff anymore so i'm just gonna remove it i don't need this anymore okay all right now we're getting somewhere um i kind of don't like how the colors just overwrite each other i kind of wanted to make it look like it's blended a little bit better almost like each line was drawn like a streak of light so it kind of lights up everything that's underneath it and the way we're gonna do that is instead of just overwriting the pixel values here with the color that we're drawing with i want to overlay it onto the pixels behind it so add it together with those values um the first thing that means is that we have to make the background black because if we add anything to white it's already at its brightest and it's just going to stay white so if i want to make this look like a piece of lighting art i need to first start off with a dark canvas so let's go ahead and convert this to black all right it's a bit more interesting um now i want to add each of these ones on but i can't just the drawing interface i don't think it has an option for us to choose how to add these things on so what i'm going to have to do is draw these lines on a separate black canvas which i then add the whole two canvases together as i'm going so let's see what that looks like um i'll actually have to create the new canvas every time for each line because every time i add it i'm going to throw that one away and i'm going to create a new one for the next line so that it's always it's not adding the same lines over and over otherwise those lines didn't become very bright as time goes on so let's give that a go i'm gonna do the same thing here create our own image [Applause] everything is just the same background is still black we are also going to need an overlay drawing interface [Applause] okay so instead of drawing the lines onto my original image i'm gonna draw it onto the overlay image everything else can stay the same for now if i run this my art should disappear because i don't actually do anything to the original image itself so i've got all of these overlay images in memory now but i need to add that to the original image the way to do that there's another class from pillow called image chops which actually is kind of a cool name but it just stands for channel operations and that will let us add those two things together so we're gonna do this here image chops add and there you go image one and image two and if you run this again still see nothing that's because this one doesn't actually add it to it doesn't add it to the original image it does but it doesn't modify the original image it returns a new image so we actually have to keep setting the reference of our original image to the result of this run it again all right now we got something okay that's pretty cool okay all right so you've kind of noticed that every time i run this i kind of want to test the change i have to run it many times just to see a different varieties of the generation to save myself a bit of time i want to actually make this generate 10 images at once so let's go ahead and do that let's first make generate art accept a path parameter for where we're going to save the image and then we're going to loop through it so we're going to go here like this path string and instead of saving it to this hard-coded spot i'm going to now put in like that so if i actually do this test image.png my program should still work exactly the same way just make sure it does okay that's good now i can loop it let's say we want to do 10 at a time we'll just change the name make this a format string then add in our index so now i should have 10 images when i run this there we go yep pretty interesting not sure why my arrow key doesn't work for this okay um so let's open this here on the side just so we can see it the color generation is good but in some cases i think they can be quite plain as well like for example this one here it's i want brighter colors whereas this one's a little bit saturated desaturated so closer to gray and this one this is okay but maybe i wanted to have more dramatic changes and not just be sort of like the same color this one's just really desaturated i mean it looks kind of cool but it's a little bit too pale so what i want to do next is come up with a random color in a way that it's always bright um now each of these channel values r g and b we can randomize between zero and two five five but um i only want the uh hue of the color to change which is like what shade of the color it is i don't want the brightness or the saturation to change so in order to do that i need to use a different way to define the color instead of red green and blue i'm going to use something called hsv which stands for hue saturation and value so if i keep saturation and value at max which is 100 they're always going to be bright and vivid and if i just change the hue then i can have different colors that also are bright and vivid as opposed to darker colors or less saturated colors like this but the problem is if we do at hsv we have to convert it back to rgb because that's what we've been using for the rest of this program uh that's okay though because python actually has some inbuilt tools to do that conversion for us so let's go ahead and try that so now instead of randomizing rg and b i am going to randomize h s and v actually i think in this color system we are yeah all the out inputs and outputs are in the range zero to one so this is different to what we're using here where it's between zero and two five five we're going to have to convert that as well but for now let's just do this random already returns a value between zero and one so this will be good for us here and as i mentioned for s and v we're going to keep it max 100 okay so we have to now convert hsv to rgb but also to 255. so there's a tool for that i believe here hsv rgb give that a go but this is going to be a float output as well okay so all we have to do is just multiply each element by 255 and also cast it to an integer so we should be able to do that pretty easily using list comprehension x for x and float rgb we want to do multiply by 255 and also convert it to an integer we can just return that well actually this might complain because we're returning a list but actually wants a tuple so i'm just gonna cast it to tuple as well [Applause] all right so now if i run this my goal is to just have vivid and bright colors being generated here yep seems to do the job everything i have is very bright there's no more sort of this one looks a little bit desaturated see well for the most part it works i'm not sure when it's like really bright if it's just overlaid on top of each other so much that it sort of becomes white so that could be happening as well but the colors themselves seem to be a lot brighter than we had it before and if we look at through all our samples that appears to be the case as well okay that's pretty cool so now the last thing i want to do um you may have noticed these images are really pixelated for their size uh this is 128 pixels uh and i think that by drawing these lines it just draws you know just like pixel wise really sharp there's no smoothing or blurring or anything so i want to fix that because this just looks a bit too lo-fi for me um i looked online on how to do this with a pillow and it seems like the only way to do this like quite easily is literally just to um down sample it in half so reduce the size by half and choose an anti-aliasing smoothing method but the problem with that is that now we're going to have an image that's twice as small and i still want it to be quite big so what i'm going to do is i'm going to pick the actual size that i want and then create a scale factor which multiplies everything by that scale factor so that i will create an image that's bigger on purpose just so i can resize it down so let's give that a shot so let's say i want it at 128 pixels as a final result but i have to multiply everything by two all right let's give that a go this applies to my padding as well and also the line thickness because that's yeah i guess we could just do this okay so now i should make the image twice as big but i haven't resized it yet let's just see if that works first yeah well it seems to work twice as big now okay so we'll just do this exactly right before we save it because we don't have to do it before image resize uh well we already have this number don't we target size okay that's a tuple isn't it okay and then for the sample that's how we anti-aliase it it says a keyword there okay image.anti-alias right there we go let me run that again and see what happens oops got an unexpected keyword okay maybe that's not the one resample okay so let's run this again um still really not that smooth and again that's because this image resize doesn't actually resize it in place it creates a new image yeah it returns a new image resizing the original one so if we want to save the resized image we again have to assign it back into this variable let's try that okay now i think that's smooth it's also pretty blurry let's increase the size of our output a little bit instead of 128 let's make it 256. okay i think that looks a lot better yep and yeah that's pretty much it um you can probably extend this to do a bunch of other cool stuff so it's just a starting point just a simple way to get started with some generative art in python or if you're new to image manipulation as well this is yeah probably just a good way to get your feed wet we've reached the end of the tutorial i hope you found it helpful and i hope that by this point you now know how to create images like these using python if there are any other topics or tutorial projects you'd like me to cover please send me a message or let me know in the comments the source code for this tutorial and the original nftr collection project i mentioned earlier are in the description below thank you for watching
Info
Channel: Pixegami
Views: 21,171
Rating: undefined out of 5
Keywords: Python, Tutorial, Coding, NFT
Id: BMq2Jrvp9AA
Channel Id: undefined
Length: 48min 14sec (2894 seconds)
Published: Sun Sep 19 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.