Microsoft’s “Seadragon” seems to finally be ready, or at least it’s ready for public beta. The idea is you can quickly load, view, and zoom-in on extremely large images. The image you see below is something I quickly ginned up, it’s 4500×4500 pixels, about a 4mb image. The tech. isn’t anything new, Google maps does something similar with a granularity of images but this one seems a bit more seamless. Anyways take a look at the photo below and enjoy.
Going back to old source code can be both a blessing and a curse. (If you want to get to the computer vision code just scroll past my preamble)
The negatives:
Can be hard to read
Logic might not make sense any more
Out of date
Embarrassing, as in it’s embarrassing how bad you might have been.
The positives:
Reusable code can speed up new projects
Nostalgia. “Aw look at that 10 level deep if/then nest. Isn’t it cute.” **Also a negative
Ego boost. Typically you don’t remember really old projects all that well, it’s sometimes surprising to find you aren’t as bad as you’d originally thought
[/preamble]
What’s my point? No point really, I just found some old computer vision code that I thought I’d post. The following code makes use of the CImg 1.3.0 library.
staticfloat My[3][3],Mx[3][3];//Global Filtersvoid set_mx(float ar[]){/*set mx from 1D array*/int r,c,ar_idx=0;for(r=0;r<3;r++){for(c=0;c<3;c++){
Mx[c][r]= ar[ar_idx++];}}}void EdgeDetection::set_my(float ar[]){/*set my from 1D array*/int r,c,ar_idx=0;for(r=0;r<3;r++){for(c=0;c<3;c++){
My[c][r]= ar[ar_idx++];}}}//Apply filters to grayscale image.
CImg<unsignedchar> apply(const CImg<unsignedchar>&img, int complexity/*=0*/){
CImg<unsignedchar> ret(img.dimx(),img.dimy(),1,3);int r,c;int dx,dy;int dx_dy;/*main loop := apply Mx and My to input image.*/
cimg_forXY(img,x,y){
dx = dy =0;if(x ==0|| x == img.dimx()-1) dx_dy =0;elseif(y ==0|| y == img.dimy()-1) dx_dy =0;else{/*dx*/for(r =-1; r <2; r++){for(c =-1; c <2; c++){
dx += img(x+c,y+r,0)* Mx[r+1][c+1];}}/*dy*/for(r =-1; r <2; r++){for(c =-1; c <2; c++){
dy += img(x+c,y+r,0)* My[r+1][c+1];}}/*Complexity. 0:=Approximation | 1-Inf:=No approximation*/if(complexity ==0){
dx_dy =abs(dx)+abs(dy);}else{
dx_dy =(dx * dx)+(dy * dy);
dx_dy =sqrt((float)(dx_dy));}/*some values exceed range of 0-255, clamp these down.*/if(dx_dy >255) dx_dy =255;if(dx_dy <0) dx_dy =0;}/*invert colors to make nicer output*///ret(x,y,0) = ret(x,y,1) = ret(x,y,2) = 255 - sum;
ret(x,y,0)= ret(x,y,1)= ret(x,y,2)= dx_dy;}return ret;}/*
prewitt()
input :=
const CImg<unsigned char> &img - Constant address to an image file.
int complexity - Complexity sets whether we use full sqrt(dx^2 + dy^2) or simply an approximation.
Note: Any non-zero value for complexity results on the more complex solution
output :=
CImg<unsigned char> - Image with prewitt edge detection applied.
descrip := prewitt() applies edge detection based on the prewitt masks.
*/
CImg<unsignedchar> prewitt(const CImg<unsignedchar>&img,int complexity/* = 0*/){float mx[9]={-1,0,1,\
-1,0,1,\
-1,0,1};float my[9]={-1,-1,-1,\
0,0,0,\
1,1,1};
set_mx(mx);
set_my(my);
CImg<unsignedchar> ret = apply(img,complexity);return ret;}void example_usage(char*image){
CImg<unsignedchar> ret(image);
ret = luminancei(ret);for(int i =0; i <5; i++) ret = prewitt(ret);}
Easy peasy, but what if your image is noisy?(Most are and only artificial images have clean backgrounds.)
Simple, blur the image first then apply the edge detection:
double gaussian(int x, int y, float d){double x2 = x*x;double y2 = y*y;double d2 = d*d;double val =-1*((x2+y2)/(2*d2));double denom =2*3.14159264*(d*d);return std::exp(val)/denom;}
CImg<unsignedchar> gaussian_blur(const CImg<unsignedchar>&img, int delta, int range){
CImg<unsignedchar> ret(img.dimx(),img.dimy(),1,3);int gauss[5][5];int r,c;/*calculate gaussian filter matrix.
Technically 'range(rows/cols)' should be a function of 'delta' but we're approximating here... plus
I have no desire to convolve something larger than 5x5.*/for(r =-2; r <3; r++){for(c=-2;c <3;c++){
gauss[c+2][r+2]=(int)((gaussian(r,c,delta)*273)+0.5);}}double R,G,B;int denom;
cimg_forXY(img,x,y){
R = G = B =0;
denom =273;for(r =-2; r <3; r++){for(c=-2;c <3;c++){if(isinrange(&img,x+c,y+r)){
R+=gauss[c+2][r+2]*img(x+c,y+r,0);
G+=gauss[c+2][r+2]*img(x+c,y+r,1);
B+=gauss[c+2][r+2]*img(x+c,y+r,2);}else{
denom-=gauss[c+2][r+2];}}}
ret(x,y,0)= R/denom;
ret(x,y,1)= G/denom;
ret(x,y,2)= B/denom;}return ret;}void example_usage(char*image){//First we blur the image 5 times.
CImg<unsignedchar> ret(image);for(int i =0; i <5; i++) ret = gaussian_blur(ret);//Convert to gray after blur
ret = luminancei(ret);//Now apply the edge detection
ret = prewitt(ret);}
In practice I’ve found 4-6 iterations through the filter are good.
Occasionally something happens when I play games that reminds me why I continue to do so.
In an odd bit of ’6-Degrees of Separation’ my brother was trolling the IO web forums and came across this link. IO being a Team Fortress 2 clan.
Anyways whenever anyone complains about a pub stack** I either claim I’m the only human player and the rest of the players are my aimbots or that I’m running a multi-box rig and that I am controlling every player simultaneously. Amusingly it caught on and others in my match named themselves “Aimbot” something or other. I’m the player in the red circle on my version of the screenshot. Pretty funny stuff and kinda weird that my brother stumbled across it and recognized my steam avatar(the blue D&D dice). I don’t know why this tickles my fancy so much.
Too funny.
** pub stack: When good players align themselves with 1 team instead of spreading their skill, thus stacking a particular side. It is considered a douchey thing to do in public servers.
Content: I make the assumption you have content or the means to make content.
For a basic tutorial on rendering LightMaps in 3DS Max you should watch the following:
First things first, copy the macro scripts in to the \Autodesk\3ds Max 2010\ui\macroscripts folder. You’ll then need to open up 3DS Max and select ‘Customize->Customize User Interface’ from the toolbar. Switch to the ‘Menus’ tab and drag both Bakersfield and multiObjectsUnwrap to whatever menu you wish, I put them under the ‘Rendering’ menu item and will reference them as such.
OgreMax comes with it’s own self installer so that should be easy enough, just make sure you get the right version for your copy of 3DS Max.
There are a few ways to make a LightMap but the 2 I dealt with were per-object LightMaps and per-scene LightMaps. This tutorial is for creating a single scene map. I highly recommend using per-scene unless you’ve got an amazing amount of processing power. My rig chugged under the weight of 40 512×512 lightmaps overlayed on the textures. I also recommend you save often.
My method isn’t pretty. I don’t claim to be an artist, this is what worked for me. In fact I just got it working about an hour ago. There are some little hacks I had to do to get the OgreMax output I required, more on that in a bit. According to the OgreMax author I wasn’t setting my scene up properly. I don’t necessarily disagree but I did everything by the book and wasn’t getting results.
Setting Up the Scene:
Open your content.
Highlight all objects in the scene and go to ‘Rendering->multiUnwrap’
In ‘Work On Channel’ select 2
Click ‘unwrap UV’s’ when ready.
Go get some coffee if your scene is big.
The ‘Edit UVW’s’ window should appear, if it doesn’t click on your newly merged object, go to properties, and click ‘Edit’ under ‘Parameters’
In the ‘Edit UVW’s’ window select all of the faces and go to Mapping->Flatting Mapping on the menu.
I set ‘Spacing’ to 0 to give it the tightest packing (edit: which it seems is causing some edge problems, I’d recommend some spacing in the pack.)
Click OK
Wait for flattening
At this point I’m guessing the real artists will make sure the UV’s unwrapped in a visually pleasing and correct manner. I don’t relax the mapping at all, I just take it as is.
You should see a nice flattened UV map. Next go to your 3DS Max modifier stack and see that you have a ‘Unwrap UVW’ modifier. Click on it and under Parameters->Map Channel select 2.
Next we need to add some lighting. For a very simple scene:
Go to Create->Lights->StandardLights->SkyLight. Create it wherever you like
Go to Create->Lights->StandardLights->OmniLight. Create it where you want the light to come from.
Click on the light you just made and go to the Properties menu to the right.
Turn Shadows ‘On’
Under ‘Shadow Map Params’ I recommend changing the Bias to 0 otherwise your shadows will stand off from their geometry.
Go To Rendering->Render Setup
In the ‘Advanced Lighting’ tab select ‘Light Tracer’ from the drop down menu. Default settings work but I would recommend changing Bounce to 1 or 2. Feel free to play around a bit, these settings affect the end result. Don’t go too crazy though or it will take forever and a year to render.
Test your lighting by going to ‘Rendering->Render’ There should be nice soft shadows in your map. If not, check your lights and make sure the Omni light is set to cast shadows.
Setting up OgreMax:
Highlight all objects in the scene
Go to OgreMax->Convert To OgreMax Materials. This step converts all material and submaterials to the OgreMax material type. Whereas normally you’d render the lightmap to something like the diffuse channel we need to modify things to allow for OgreMax to export the texture to Ogre properly
*Optionally if you have a shared textures and are doing the per-object lightmap method you need to go to OgreMax->Create Master/Slave materials
Go to OgreMax->Set Render To Texture Destinations and change Technique to 2.
Rendering to Texture:
Gods finally. Click on your merged object
Go to ‘Rendering->Render To Texture…’ in the main 3DS Max window.
Under ‘Objects To Bake->Mapping Coordinates’ choose ‘Use Existing Channel’ and set the channel to 2.
Under ‘Output’ click ‘Add’ and add a Lightmap. In ‘Target Map Slot’ choose ‘Technique 2′
Under ‘Baked Material->Baked Material Settings’ click on ‘Output Into Source’
Render
Done… sorta. So this is where things get a bit hacky. You can’t directly export the entire scene, or at least I couldn’t. For some reason Ogremax didn’t distigiush between all of the original meshes and the new mesh so it output them all to the scene file. In other words we have our combined mesh AND the meshes we merged to make that mesh, thus creating duplicate geometry. To avoid this problem manually select the things you’d like to export to Ogre and click OgreMax->Export Selected Items. There might be an easier way to do this, I don’t know.
If you go to the export folder you should see your new .scene and .material files. If you didn’t specify a Render To Texture output folder it is probably in MyDocuments->3DSMax->sceneassets. Copy this and any original textures for your scene in to a single folder.
The last little hacky bit deals with the material files. You should see something like the one below:
material Road_Ar3_SG_OgreMax
{
technique Map#58{
pass Map#59{
ambient 0001
specular 00018
texture_unit Map#60{
texture_alias Map#60
texture Road_Ar3.bmp
filtering linear linear linear
}}}
technique Map#198{
pass Map#199{
ambient 0.6980390.6980390.6980391
diffuse 0.6980390.6980390.6980391
specular 0.8980390.8980390.898039120
texture_unit Map#291{
texture_alias Map#291
texture multiObjectsUnwrapLightingMap.tga
tex_coord_set 1
filtering linear linear linear
}}}}
In the first material->Technique we see the original texture is listed and in the second material->technique we see our scene lightmap texture. Notice the tex_coord_set is 1 for the scene lightmap. Each Ogre mesh can store multiple sets of UV’s (the ‘channels’ you create in 3DS Max.) In my example there are 2 channels, 1 for the original texture sets and 1 for the lightmap.
I wrote a small bit of code(end of my post) to convert the above material to this material:
Notice that we modified it to use scene blending. And we’re left with the final scene:
Lastly lets not forget my code. Keep in mind I wrote this in about 15 minutes to work exactly with what I’ve done above. It would probably be easier to use Ogre’s built in Material classes to parse the data and then modify as needed. You might also notice that I pull the image name from the material name. If you follow OgreMax’s lightmapping tutorial you end up overwriting the original texture channel information. This channel contains the original image name. But OgreMax does export that name to the material name so I pulled it from there. (**This functionality isn’t needed with my tutorial, we make sure to write the values to a second texture alias. Feel free to modify the code to simply read the original texture name.)
The code should load any material file but look closely at the output function. The output structure is hardcode. Yes yes, I know it isn’t pretty.
Edit: UV Multi Unwrap has some error-catching flaws, namely it can’t handle deleted nodes or dummy objects. I’m posting my updated version of the script. Honestly I don’t know if my changes have any long term effects but in the near term I was able to successfully shade my new content. You can find my additions to the script by searching for “–jeremy”, you’ll see what I’ve done.
New multiObjectsUnwrapMacro If direct installation doesn’t work just copy and past my .mcr in to the current multiObjectsUnwrapMacro.mcr using the 3DS Max macro editor.