main.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "image"
  6. "image/color"
  7. "image/draw"
  8. "image/jpeg"
  9. _ "image/png"
  10. "net/http"
  11. "os"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "github.com/nfnt/resize"
  16. )
  17. /*
  18. 1. 读取验证码
  19. 2. 将验证码图片二值化
  20. 3. 将二值化后的图片进行近似等分切割(可能有更好的方法,对于精度不高要求不高的话等分即可)
  21. 4. 将切割后的图片进行识别(调用train()函数,手动标注验证码,生成指纹数组,也可以写入到一个文件中,然后读取)
  22. 5. 将识别后的结果进行拼接
  23. */
  24. var (
  25. model = flag.String("m", "train", "运行模式【train/predict】")
  26. imageFile = flag.String("img", "./demo.jpg", "要识别的图像文件")
  27. threshold = flag.Uint64("ct", 0xcccc, "颜色过滤阈值")
  28. clearColorThreshold uint32
  29. )
  30. func init() {
  31. flag.Parse()
  32. clearColorThreshold = uint32(*threshold)
  33. if _, err := os.Stat("./view"); err != nil {
  34. os.Mkdir("./view", 0777)
  35. }
  36. }
  37. // 判断r四周是否有任意两个连通白块,如果没有,则认为是噪点
  38. func imgConnect(ru, rd, rl, rr uint32) bool {
  39. if (ru < clearColorThreshold &&
  40. (rl < clearColorThreshold || rr < clearColorThreshold)) ||
  41. (rd < clearColorThreshold && (rl < clearColorThreshold || rr < clearColorThreshold)) ||
  42. (rl < clearColorThreshold && (ru < clearColorThreshold || rd < clearColorThreshold)) ||
  43. (rr < clearColorThreshold && (ru < clearColorThreshold || rd < clearColorThreshold)) {
  44. return true
  45. }
  46. return false
  47. }
  48. // 二值化
  49. func imgBarbarization(img image.Image) image.Image {
  50. binImg := image.NewGray16(img.Bounds())
  51. draw.Draw(binImg, binImg.Bounds(), img, img.Bounds().Min, draw.Over)
  52. rect := binImg.Bounds()
  53. // 遍历点像素点
  54. for x := 0; x < rect.Dx(); x++ {
  55. for y := 0; y < rect.Dy(); y++ {
  56. // 获取颜色
  57. r, _, _, _ := binImg.At(x, y).RGBA()
  58. ru, _, _, _ := img.At(x, y+1).RGBA()
  59. rd, _, _, _ := img.At(x, y-1).RGBA()
  60. rl, _, _, _ := img.At(x-1, y).RGBA()
  61. rr, _, _, _ := img.At(x+1, y).RGBA()
  62. if r < clearColorThreshold && imgConnect(ru, rd, rl, rr) {
  63. binImg.Set(x, y, color.White)
  64. } else {
  65. binImg.Set(x, y, color.Black)
  66. }
  67. }
  68. }
  69. return binImg
  70. }
  71. // 删除图片无用外部轮廓
  72. func imgCutSide(img image.Image) image.Image {
  73. rect := img.Bounds()
  74. // 开始x坐标
  75. leftStartX := 0
  76. for x := 0; x < rect.Dx(); x++ {
  77. lxflag := false
  78. for y := 0; y < rect.Dy(); y++ {
  79. // 获取颜色
  80. r, _, _, _ := img.At(x, y).RGBA()
  81. if r == 0xFFFF {
  82. lxflag = true
  83. break
  84. }
  85. }
  86. if lxflag {
  87. leftStartX = x
  88. break
  89. }
  90. }
  91. // 开始y坐标
  92. leftStartY := 0
  93. for y := 0; y < rect.Dy(); y++ {
  94. lyflag := false
  95. for x := 0; x < rect.Dx(); x++ {
  96. // 获取颜色
  97. r, _, _, _ := img.At(x, y).RGBA()
  98. if r == 0xFFFF {
  99. lyflag = true
  100. break
  101. }
  102. }
  103. if lyflag {
  104. leftStartY = y
  105. break
  106. }
  107. }
  108. // 结束x坐标
  109. rightEndX := 0
  110. for x := rect.Dx(); x > 0; x-- {
  111. rxflag := false
  112. for y := rect.Dy(); y > 0; y-- {
  113. // 获取颜色
  114. r, _, _, _ := img.At(x, y).RGBA()
  115. if r == 0xFFFF {
  116. rxflag = true
  117. break
  118. }
  119. }
  120. if rxflag {
  121. rightEndX = x
  122. break
  123. }
  124. }
  125. // 结束y坐标
  126. rightEndY := 0
  127. for y := rect.Dy(); y > 0; y-- {
  128. ryflag := false
  129. for x := rect.Dy(); x > 0; x-- {
  130. // 获取颜色
  131. r, _, _, _ := img.At(x, y).RGBA()
  132. if r == 0xFFFF {
  133. ryflag = true
  134. break
  135. }
  136. }
  137. if ryflag {
  138. rightEndY = y
  139. break
  140. }
  141. }
  142. // 创建新的图片
  143. rectangle := image.Rectangle{
  144. Min: image.Point{X: 0, Y: 0},
  145. Max: image.Point{X: rightEndX - leftStartX, Y: rightEndY - leftStartY},
  146. }
  147. newSideImg := image.NewGray16(rectangle)
  148. draw.Draw(newSideImg, newSideImg.Bounds(), img, image.Point{X: leftStartX, Y: leftStartY}, draw.Over)
  149. return newSideImg
  150. }
  151. func cutImage(img image.Image) []image.Image {
  152. rect := img.Bounds()
  153. imgs := make([]image.Image, 0)
  154. // 记录当前x的位置
  155. nowCutStartX := 0
  156. for x := 0; x <= rect.Dx(); x++ {
  157. every := rect.Dx() / 4
  158. if x%every == 0 && x != 0 {
  159. // 创建新的图片
  160. rectangle := image.Rectangle{
  161. Min: image.Point{X: 0, Y: 0},
  162. Max: image.Point{X: x + 3 - nowCutStartX, Y: rect.Dy()},
  163. }
  164. newImg := image.NewGray16(rectangle)
  165. draw.Draw(newImg, newImg.Bounds(), img, image.Point{X: nowCutStartX, Y: 0}, draw.Over)
  166. newResizeImg := imgCutSide(newImg)
  167. newResizeImg = resize.Resize(16, 16, newResizeImg, resize.Lanczos3)
  168. newResizeImg = imgCutSide(newImg)
  169. newResizeImg = resize.Resize(16, 16, newResizeImg, resize.Lanczos3)
  170. imgs = append(imgs, newResizeImg)
  171. nowCutStartX = x + 3
  172. }
  173. }
  174. return imgs
  175. }
  176. // 指纹验证
  177. func imgSign(imgBinary string) string {
  178. if imgBinary == "" {
  179. return imgBinary
  180. }
  181. imgBinarySign := strings.Split(imgBinary, "")
  182. // 相似度
  183. maxSimilarity := 0
  184. num := ""
  185. for _, sign := range numSign {
  186. tmpSimilarity := 0
  187. signArr := strings.Split(sign[1], "")
  188. for k, s := range imgBinarySign {
  189. if s == signArr[k] {
  190. tmpSimilarity++
  191. }
  192. }
  193. if maxSimilarity < tmpSimilarity {
  194. maxSimilarity = tmpSimilarity
  195. num = sign[0]
  196. }
  197. }
  198. return num
  199. }
  200. // 将切割后的字符图片转换为二进制, 与指纹库进行比对
  201. func recognition(imgs []image.Image) (string, []string) {
  202. signNum := make([]string, 0)
  203. for _, img := range imgs {
  204. tmpSignNum := make([]string, 0)
  205. rect := img.Bounds()
  206. for x := 0; x < rect.Dx(); x++ {
  207. for y := 0; y < rect.Dy(); y++ {
  208. // 获取颜色
  209. r, _, _, _ := img.At(x, y).RGBA()
  210. if r > 0x7788 {
  211. tmpSignNum = append(tmpSignNum, "1")
  212. } else {
  213. tmpSignNum = append(tmpSignNum, "0")
  214. }
  215. }
  216. }
  217. signNum = append(signNum, strings.Join(tmpSignNum, ""))
  218. }
  219. res := make([]string, 0)
  220. for _, v := range signNum {
  221. res = append(res, imgSign(v))
  222. }
  223. return strings.Join(res, ""), signNum
  224. }
  225. func train() {
  226. stringgen := ""
  227. f, err := os.Create("train.txt")
  228. if err != nil {
  229. fmt.Println(err)
  230. return
  231. }
  232. client := &http.Client{
  233. Timeout: 2 * time.Second,
  234. }
  235. for {
  236. resp, err := client.Get("https://beian.china-eia.com/servlet/validateCodeServlet?" + strconv.FormatInt(time.Now().UnixNano()/1e6, 10))
  237. if err != nil {
  238. fmt.Sprintf("打开图片失败:%+v", err.Error())
  239. continue
  240. }
  241. fmt.Println(time.Now().UnixNano() / 1e6)
  242. img, err := jpeg.Decode(resp.Body)
  243. if err != nil {
  244. fmt.Sprintf("解析图片失败:%+v", err.Error())
  245. continue
  246. }
  247. resp.Body.Close()
  248. dts, err := os.Create("./view/01.原始图片.jpeg")
  249. jpeg.Encode(dts, img, nil)
  250. // 图片二值化
  251. binImg := imgBarbarization(img)
  252. dts1, err := os.Create("./view/02.二值化后的图片.jpeg")
  253. jpeg.Encode(dts1, binImg, nil)
  254. // 图片切割
  255. imgCutSilce := cutImage(binImg)
  256. // 输出切割后的四张图片
  257. dts3, err := os.Create("./view/03.第一个字符.jpeg")
  258. jpeg.Encode(dts3, imgCutSilce[0], nil)
  259. dts4, err := os.Create("./view/04.第二个字符.jpeg")
  260. jpeg.Encode(dts4, imgCutSilce[1], nil)
  261. dts5, err := os.Create("./view/05.第三个字符.jpeg")
  262. jpeg.Encode(dts5, imgCutSilce[2], nil)
  263. dts6, err := os.Create("./view/06.第四个字符.jpeg")
  264. jpeg.Encode(dts6, imgCutSilce[3], nil)
  265. // 验证码识别
  266. result, resu := recognition(imgCutSilce)
  267. fmt.Println("程序识别结果: ", result)
  268. x := ""
  269. fmt.Print("请输入./view/01.原始图片正确结果: ")
  270. fmt.Scan(&x)
  271. if x == "q" {
  272. return
  273. } else if x == "k" {
  274. continue
  275. } else if len(x) != 4 {
  276. fmt.Println("输入的格式不对")
  277. continue
  278. }
  279. inputs := strings.Split(x, "")
  280. stringgen = fmt.Sprintf("{\"%s\", \"%s\"},\n{\"%s\", \"%s\"},\n{\"%s\", \"%s\"},\n{\"%s\", \"%s\"},\n", inputs[0], resu[0], inputs[1], resu[1], inputs[2], resu[2], inputs[3], resu[3])
  281. _, err = f.Write([]byte(stringgen))
  282. if err != nil {
  283. fmt.Println(err)
  284. }
  285. }
  286. }
  287. // 识别预测
  288. func predict(imageFile string) (string, error) {
  289. fi, err := os.Open(imageFile)
  290. if err != nil {
  291. return "", err
  292. }
  293. defer fi.Close()
  294. img, err := jpeg.Decode(fi)
  295. if err != nil {
  296. return "", err
  297. }
  298. // 图片二值化
  299. binImg := imgBarbarization(img)
  300. // 图片切割
  301. imgCutSilce := cutImage(binImg)
  302. // 验证码识别
  303. result, resu := recognition(imgCutSilce)
  304. fmt.Println("程序识别结果: ", result, resu)
  305. return result, nil
  306. }
  307. func main() {
  308. switch *model {
  309. case "train":
  310. train()
  311. default:
  312. predict(*imageFile)
  313. }
  314. }