I actually don't use session anymore, sometimes it gets noisy.
BTM, a static captcha image will do it cuz there's no robot that can read images (not onw worthy of spammer).
I have this captcha in my forms:
<img alt="Captcha" src="captcha.php?hash=<? $tmp=time();echo $tmp;?>" width="80" height="20" style="border:1px solid #808080" align="middle" />
<input type="text" name="captcha" /></td>
<input type="hidden" name="captcha_ori" value="<? echo $tmp;?>" />
if (strtoupper($_POST[captcha])!=substr(strtoupper(md5("a random string".$_POST[captcha_ori])), 0,6)) { die("Captcha error");}
<?php
$im=imagecreatefromgif("../img/captcha.gif"); //bg image
$textcolor = imagecolorallocate($im, 255, 0, 0); //code color
imagestring($im, 5, 15, 2,$_GET[hash] ? substr(strtoupper(md5("a random string".$_GET[hash])),0,6) : "ERROR!", $textcolor);
header('Content-type: image/png');imagepng($im);imagedestroy($im);
exit(0);
?>
What this does:
* The form generates a random number -or timestamp in this case xD- ($tmp) and include an image with that number in GET vars
* The image file (captcha.php, who uses captcha.gif as background) prints 6 numbers/letters bassed on a md5 hash from a string and the random number.
* The form action scripts checks it. The trick is that both the form action script and the image php script does the same operation to get the code.
This one doesn't use sessions, wich means that if you've made a mistake you can go backwards and try again, but it will change on each reload.