Generate Random Captcha

In many situations, you will need to generate a random captcha to distinguish between humans or robots. For example, it's naturaly to have a captcha when new user register an account. It's luck that python is very suitable for doing that.
                                         —— Graycat

Preparation

Env & OS

The following configuration is suitable for me but not necessary for you. After all, it's important to make a decision which is suitable for yourself.

Key Value
Environment Python2.7
Operating System Ubuntu-LTS-16.04 X64

Dependence

Install python-pip and Pillow if you don't have it.

1
2
$ sudo apt install python-pip
$ pip install Pillow # If your system is 32-bit, `PIL` is available, if not, you'll use `Pillow` instead of `PIL`

Font File

Install font file, take consola as an example.
Download suitable font file from here to linux.

1
2
$ unzip YaHei.Consolas.1.12.zip                                     # unzip font file
$ mv YaHei.Consolas.1.12.ttf consola.ttf

You can add it to the system font file and activate it, even if this is not necessary for this program.

1
2
3
4
$ sudo mkdir /usr/share/fonts/consola/                              # make folder for font file
$ sudo cp Fonts/consola.ttf /usr/share/fonts/consola/
$ cd /usr/share/fonts/consola/
$ sudo mkfontscale && sudo mkfontdir && sudo fc-cache -fv # activate font file

Operation

New File

1
2
3
$ mkdir /home/user/GenerateRandomCaptcha/
$ cd /home/user/GenerateRandomCaptcha/
$ vim captcha.py

Import Package

1
2
3
4
5
from PIL import Image, ImageDraw, ImageFont
import random
import string

FONT_FILE = 'Fonts/consola.ttf'

Class & Func

Self-define a class used to generate the exception we need.

1
2
class SimpleCaptchaException(Exception):  # Just a simple template that self define Exception class
pass

Self-define a class of captcha include some necessary functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
class SimpleCaptcha(object):
def __init__(self, textLength=7, imgSize=(320, 180), fontSize=64, randomText=True, randomBgcolor=True):
self.imgSize = imgSize
self.text = "CAPTCHA"
self.fontSize = fontSize
self.bgColor = 255
self.textLength = textLength
self.image = None # Current captcha image

if randomText:
self.text = self.getRandomText()
if not self.text:
raise SimpleCaptchaException("Field text must not be empty.")
if not self.imgSize:
raise SimpleCaptchaException("Image size must not be empty.")
if not self.fontSize:
raise SimpleCaptchaException("Font size must be defined.")
if randomBgcolor:
self.bgColor = self.getRandomColor()

def getCenterCoords(self, draw, font):
width, height = draw.textsize(self.text, font)
xy = (self.imgSize[0] - width) / 2., (self.imgSize[1] - height) / 2.
return xy

def addNoiseDots(self, draw):
size = self.image.size
for item in range(int(size[0] * size[1] * 0.1)):
draw.point((random.randint(0, size[0]), random.randint(0, size[1])), fill="white")
return draw

def addNoiseLines(self, draw):
size = self.image.size
width = None
for item in range(8):
width = random.randint(1, 2)
start = (0, random.randint(0, size[1] - 1))
end = (size[0], random.randint(0, size[1] - 1))
draw.line([start, end], fill="white", width=width)
for item in range(8):
start = (-50, -50)
end = (size[0] + 10, random.randint(0, size[1] + 10))
draw.arc(start + end, 0, 360, fill="white")
return draw

def getCaptcha(self, imgSize=None, text=None, bgColor=None):
if text is not None:
self.text = text
if imgSize is not None:
self.imgSize = imgSize
if bgColor is not None:
self.bgColor = bgColor
self.image = Image.new('RGB', self.imgSize, self.bgColor) # Create image by instance a class called Image
# Note that the font file must be present
# or point to your OS's system font
# Ex. on Mac the path should be '/Library/Fonts/Tahoma.ttf'
# Ex. on Windows the path should be 'C:/Windows/Fonts/consola.ttf'
font = ImageFont.truetype(FONT_FILE, self.fontSize)
draw = ImageDraw.Draw(self.image)
centerCoords = self.getCenterCoords(draw, font)
draw.text(xy=centerCoords, text=self.text, font=font)
draw = self.addNoiseDots(draw) # Add some dot noise
draw = self.addNoiseLines(draw) # Add some random lines
self.image.show()
return self.image, self.text

def getRandomText(self):
text = string.lowercase + string.uppercase + string.digits # Text of captcha include letter and number is natural
random_text = ""
for item in range(self.textLength):
random_text += random.choice(text)
return random_text

def getRandomColor(self):
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
return (r, g, b)

Main Func

Generate a entrance for program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if __name__ == "__main__":
counter = 0
print '[*] Aim: get 7 consecutive successes.'
while (True):
sc = SimpleCaptcha()
(image, text) = sc.getCaptcha()
typeText = raw_input('[' + str(counter + 1) + '] Type words what you see: ')
if not typeText.lower() == text.lower():
counter = 0
print '[*] False\n[*] Text: ' + text
else:
counter = counter + 1
print '[*] True'

if counter == 7:
break
print '[*] Congratulate that you have a good pair of eyes!'

Result

Conclusion

As well konwn, captcha includeing lower case, upper case and number is normal. And case of typing is ignored is default is also natural.
The above is my biggest change to the program, other little change includes name of method and main method.
That's all. Thanks.