写在前面

8月底的时候,@阿里巴巴 推出了一款名为“拯救斯诺克”的闯关游戏,作为前端校园招聘的热身,做的相当不错,让我非常喜欢。后来又传出了一条消息,阿里推出了A-star(阿里星)计划,入职阿里的技术培训生,将接受CTO等技术大牛的封闭培训,并被安排到最有挑战的项目中,由技术带头人担任主管。于是那几天关注了一下阿里巴巴的消息,结果看到这么一条微博(http://e.weibo.com/1897953162/A79Lpcvhi):

此刻,@阿里足球队 可爱的队员们已经出征北上。临走前,后防线的队员们留下一段亲切的问候,送给对手,看@新浪足球队 的前锋们如何破解。@袁甲 @蓝耀栋 #阿里新浪足球世纪大战#

目测是一段Base64加密过的信息,但无奈的是这段信息是写在图片里的,我想看到解密后的内容难道还一个字一个字地打出来?这么懒这么怕麻烦的我肯定不会这么做啦→_→想到之前有看到过一篇关于HTML5实现验证码识别的文章,于是顿时觉得也应该动手尝试一下,这才是极客的风范嘛!

Demo与截图

先来一个大家最喜欢的Demo地址(识别过程需要一定时间,请耐心等待,识别结果请按F12打开Console控制台查看):

http://www.clanfei.com/demos/recognition/

再来张效果图:

思路


实现一个算法,思路是最重要的,而实现不过是把思想转化为能够运行的代码。

简单地说,要进行文本识别,自然是拿图片的数据与文字的图形数据进行对比,找到与图片数据匹配程度最高的字符。

首先,先确定图片中文本所用的字体、字号、行距等信息,打开PhotoShop,确定了字体为微软雅黑,16像素,行距为24,Base64文字的开始坐标为(8, 161)。

然后,确定要进行匹配的字库,Base64编码中可能出现的字符为26个字母大小写、10个数字、加号、斜杠,但目测在图片中没有斜杠出现,因此字库应该为:

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+

接着,是确定如何判断字符是否匹配,由于只需要对字型进行匹配,因此颜色值对算法并无用处,因此将其灰度化(详见百度百科),并使用01数组表示,1代表该像素点落在此字符图形上,0反之,而如何确定该某个灰度值在数组中应该表示为0还是1,这个转换公式更是算法中的关键。

最后,将字型的灰度化数据与图片中文字部分的灰度化数据进行对比,将误差最小的字型作为匹配到的字符,然后进行下一个字符的匹配,直到图片中所有字符匹配完毕为止。

递归实现

详细的思路于代码注释中,个人觉得这样结合上下文更为容易理解(注:代码应运行于服务器环境,否则会出现跨域错误,代码行数虽多,但注释就占了大半,有兴趣可以耐心看完,图片资源于上方“写在前面”)。

  1. <!doctype html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>文字识别</title>
  6. </head>
  7. <body>
  8. <canvas id="canvas" width="880" height="1500"></canvas>
  9. <script type="text/javascript">
  10. var image = new Image();
  11. image.onload = recognition;
  12. image.src = 'image.jpg';
  13. function recognition(){
  14. // 开始时间,用于计算耗时
  15. var beginTime = new Date().getTime();
  16. // 获取画布
  17. var canvas = document.getElementById('canvas');
  18. // 字符库
  19. var letters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+';
  20. // 字型数据
  21. var letterData = {};
  22. // 获取context
  23. var context = canvas.getContext('2d');
  24. // 设置字体、字号
  25. context.font = '16px 微软雅黑';
  26. // 设置文字绘制基线为文字顶端
  27. context.textBaseline = 'top';
  28. // 一个循环获取字符库对应的字型数据
  29. for(var i = 0; i < letters.length; ++i){
  30. var letter = letters[i];
  31. // 获取字符绘制宽度
  32. var width = context.measureText(letter).width;
  33. // 绘制白色背景,与图片背景对应
  34. context.fillStyle = '#fff';
  35. context.fillRect(0, 0, width, 22);
  36. // 绘制文字,以获取字型数据
  37. context.fillStyle = '#000';
  38. context.fillText(letter, 0, 0);
  39. // 缓存字型灰度化0-1数据
  40. letterData[letter] = {
  41. width : width,
  42. data : getBinary(context.getImageData(0, 0, width, 22).data)
  43. }
  44. // 清空该区域以获取下个字符字型数据
  45. context.clearRect(0, 0, width, 22);
  46. }
  47. // console.log(letterData);
  48. // 绘制图片
  49. context.drawImage(this, 0, 0);
  50. // 要识别的文字开始坐标
  51. var x = beginX = 8;
  52. var y = beginY = 161;
  53. // 行高
  54. var lineHeight = 24;
  55. // 递归次数
  56. var count = 0;
  57. // 结果文本
  58. var result = '';
  59. // 递归开始
  60. findLetter(beginX, beginY, '');
  61. // 递归函数
  62. function findLetter(x, y, str){
  63. // 找到结果文本,则递归结束
  64. if(result){
  65. return;
  66. }
  67. // 递归次数自增1
  68. ++ count;
  69. // console.log(str);
  70. // 队列,用于储存可能匹配的字符
  71. var queue = [];
  72. // 循环匹配字符库字型数据
  73. for(var letter in letterData){
  74. // 获取当前字符宽度
  75. var width = letterData[letter].width;
  76. // 获取该矩形区域下的灰度化0-1数据
  77. var data = getBinary(context.getImageData(x, y, width, 22).data);
  78. // 当前字符灰度化数据与当前矩形区域下灰度化数据的偏差量
  79. var deviation = 0;
  80. // 一个临时变量以确定是否到了行末
  81. var isEmpty = true;
  82. // 如果当前矩形区域已经超出图片宽度,则进行下一个字符匹配
  83. if(x + width > 440){
  84. continue;
  85. }
  86. // 计算偏差
  87. for(var i = 0, l = data.length; i < l; ++i){
  88. // 如果发现存在的有效像素点,则确定未到行末
  89. if(isEmpty && data[i]){
  90. isEmpty = false;
  91. }
  92. // 不匹配的像素点,偏差量自增1
  93. if(data[i] != letterData[letter].data[i]){
  94. ++deviation;
  95. }
  96. }
  97. // 由于调试时是在猎豹浏览器下进行的,而不同浏览器下的绘图API表现略有不同
  98. // 考虑到用Chrome的读者应该也不少,故简单地针对Chrome对偏差进行一点手动微调
  99. // (好吧,我承认我是懒得重新调整getBinary方法的灰度化、0-1化公式=_=||)
  100. // 下面这段if分支在猎豹浏览器下可以删除
  101. if(letter == 'F' || letter == 'E'){
  102. deviation -= 6;
  103. }
  104. // 如果匹配完所有17行数据,则递归结束
  105. if(y > beginY + lineHeight * 17){
  106. result = str;
  107. break;
  108. }
  109. // 如果已经到了行末,重置匹配坐标
  110. if(isEmpty){
  111. x = beginX;
  112. y += lineHeight;
  113. str += '\n';
  114. }
  115. // 如果偏差量与宽度的比值小于3,则纳入匹配队列中
  116. // 这里也是算法中的关键点,怎样的偏差量可以纳入匹配队列中
  117. // 刚开始是直接用绝对偏差量判断,当偏差量小于某个值的时候则匹配成功,但调试过程中发现不妥之处
  118. // 字符字型较小的绝对偏差量自然也小,这样l,i等较小的字型特别容易匹配成功
  119. // 因此使用偏差量与字型宽度的比值作为判断依据较为合理
  120. // 而这个判断值3的确定也是难点之一,大了递归的复杂度会大为增长,小了很可能将正确的字符漏掉
  121. if(deviation / width < 3){
  122. queue.push({
  123. letter : letter,
  124. width : width,
  125. deviation : deviation
  126. });
  127. }
  128. }
  129. // 如果匹配队列不为空
  130. if(queue.length){
  131. // 对队列进行排序,同样是根据偏差量与字符宽度的比例
  132. queue.sort(compare);
  133. // console.log(queue);
  134. // 从队头开始进行下一个字符的匹配
  135. for(var i = 0; i < queue.length && ! result; ++i){
  136. var item = queue[i];
  137. // 下一步递归
  138. findLetter(x + item.width, y, str + item.letter);
  139. }
  140. }else{
  141. return false;
  142. }
  143. }
  144. // 递归结束
  145. // 两个匹配到的字符的比较方法,用于排序
  146. function compare(letter1, letter2){
  147. return letter1.deviation / letter1.width - letter2.deviation / letter2.width;
  148. }
  149. // 图像数据的灰度化及0-1化
  150. function getBinary(data){
  151. var binaryData = [];
  152. for(var i = 0, l = data.length; i < l; i += 4){
  153. // 尝试过三种方式
  154. // 一种是正常的灰度化公式,无论系数如何调整都无法与绘制的文字字型数据很好地匹配
  155. // binaryData[i / 4] = (data[i] * 0.3 + data[i + 1] * 0.59 + data[i + 2] * 0.11) < 90;
  156. // 一种是自己是通过自己手动调整系数,结果虽然接近但总是不尽人意
  157. // binaryData[i / 4] = data[i] < 250 && data[i + 1] < 203 && data[i + 2] < 203;
  158. // 最后使用了平均值,结果比较理想
  159. binaryData[i / 4] = (data[i] + data[i + 1] + data[i + 2]) / 3 < 200;
  160. }
  161. return binaryData;
  162. }
  163. console.log(result);
  164. // 输出耗时
  165. console.log(count, (new Date().getTime() - beginTime) / 1000 + ' s');
  166. // 将文字绘制到图片对应位置上,以方便查看提取是否正确
  167. context.drawImage(this, this.width, 0);
  168. var textArray = result.split('\n');
  169. for(var i = 0; i < textArray.length; ++i){
  170. context.fillText(textArray[i], this.width + beginX, beginY + lineHeight * i);
  171. }
  172. }
  173. </script>
  174. </body>
  175. </html>

运行环境

Win7 64位,i3-3220 CPU 3.30 GHz,8G内存

运行结果


  1. yv66vgAAADIAHQoABgAPCQAQABEIABIKABMAFAcAF
  2. QcAFgEABjxpbml0PgEAAygpVgEABENvZGUB
  3. AA9MaW5lTnVtYmVyVGFibGUBAARtYWluAQAWKFtMa
  4. mF2YS9sYW5nL1N0cmluZzspVgEAClNvdXJj
  5. ZUZpbGUBAAlNYWluLmphdmEMAAcACAcAFwwAGAA
  6. ZAQBv5paw5rWq6Laz55CD6Zif5a6e5Yqb6LaF
  7. 576k77yM6Zi15a656LGq5Y2O44CC5LmF5Luw5aSn5ZC
  8. N77yM5ZGo5pel5LiA5oiY77yM6L+Y5pyb
  9. 5LiN6YGX5L2Z5Yqb77yM5LiN5ZCd6LWQ5pWZ44CCBw
  10. AaDAAbABwBAARNYWluAQAQamF2YS9sYW5n
  11. L09iamVjdAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdX
  12. QBABVMamF2YS9pby9QcmludFN0cmVhbTsB
  13. ABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgE
  14. AFShMamF2YS9sYW5nL1N0cmluZzspVgAh
  15. AAUABgAAAAAAAgABAAcACAABAAkAAAAdAAEAAQA
  16. AAAUqtwABsQAAAAEACgAAAAYAAQAAAAEACQAL
  17. AAwAAQAJAAAAJQACAAEAAAAJsgACEgO2AASxAAAA
  18. AQAKAAAACgACAAAAAwAIAAQAAQANAAAAAgAO
  19. 715 1.984 s(猎豹)
  20. 772 15.52 s(Chrome)

(递归次数谷歌只比猎豹多几十,耗时却对了十几秒,看来猎豹真的比Chrome快?)

非递归实现

其实非递归实现只是递归实现前做的一点小尝试,只在猎豹下调试完成,因为不舍得删,所以顺便贴出来了,使用Chrome的各位就不要跑了(我真的不是在给猎豹做广告= =||)。

  1. <!doctype html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>文字识别</title>
  6. </head>
  7. <body>
  8. <canvas id="canvas" width="880" height="1500"></canvas>
  9. <script type="text/javascript">
  10. var image = new Image();
  11. image.onload = recognition;
  12. image.src = 'image.jpg';
  13. function recognition(){
  14. // 开始时间,用于计算耗时
  15. var beginTime = new Date().getTime();
  16. // 获取画布
  17. var canvas = document.getElementById('canvas');
  18. // 字符库
  19. var letters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+';
  20. // 字型数据
  21. var letterData = {};
  22. // 获取context
  23. var context = canvas.getContext('2d');
  24. // 设置字体、字号
  25. context.font = '16px 微软雅黑';
  26. // 设置文字绘制基线为文字顶端
  27. context.textBaseline = 'top';
  28. // 一个循环获取字符库对应的字型数据
  29. for(var i = 0; i < letters.length; ++i){
  30. var letter = letters[i];
  31. // 获取字符绘制宽度
  32. var width = context.measureText(letter).width;
  33. // 绘制白色背景,与图片背景对应
  34. context.fillStyle = '#fff';
  35. context.fillRect(0, 0, width, 22);
  36. // 绘制文字,以获取字型数据
  37. context.fillStyle = '#000';
  38. context.fillText(letter, 0, 0);
  39. // 缓存字型灰度化0-1数据
  40. letterData[letter] = {
  41. width : width,
  42. data : getBinary(context.getImageData(0, 0, width, 22).data)
  43. }
  44. // 清空该区域以获取下个字符字型数据
  45. context.clearRect(0, 0, width, 22);
  46. }
  47. // console.log(letterData);
  48. // 绘制图片
  49. context.drawImage(this, 0, 0);
  50. // 要识别的文字开始坐标
  51. var x = beginX = 8;
  52. var y = beginY = 161;
  53. // 行高
  54. var lineHeight = 24;
  55. // 结果文本
  56. var result = '';
  57. // 非递归开始
  58. var count = 0;
  59. while(y <= 569 && ++count < 1000){
  60. // 当前最匹配的字符
  61. var trueLetter = {letter: null, width : null, deviation: 100};
  62. // 循环匹配字符
  63. for(var letter in letterData){
  64. // 获取当前字符宽度
  65. var width = letterData[letter].width;
  66. // 获取该矩形区域下的灰度化0-1数据
  67. var data = getBinary(context.getImageData(x, y, width, 22).data);
  68. // 当前字符灰度化数据与当前矩形区域下灰度化数据的偏差量
  69. var deviation = 0;
  70. // 一个临时变量以确定是否到了行末
  71. var isEmpty = true;
  72. // 如果当前矩形区域已经超出图片宽度,则进行下一个字符匹配
  73. if(x + width > this.width){
  74. continue;
  75. }
  76. // 计算偏差
  77. for(var i = 0, l = data.length; i < l; ++i){
  78. // 如果发现存在的有效像素点,则确定未到行末
  79. if(isEmpty && data[i]){
  80. isEmpty = false;
  81. }
  82. // 不匹配的像素点,偏差量自增1
  83. if(data[i] != letterData[letter].data[i]){
  84. ++deviation;
  85. }
  86. }
  87. // 非递归无法遍历所有情况,因此针对某些字符进行一些微调(这里只针对猎豹,Chrome的没做)
  88. // 因为其实非递归实现只是在递归实现前做的一点小尝试,因为不舍得删,就顺便贴出来了
  89. if(letter == 'M'){
  90. deviation -= 6;
  91. }
  92. // 如果偏差量与宽度的比值小于3,则视为匹配成功
  93. if(deviation / width < 3){
  94. // 将偏差量与宽度比值最小的作为当前最匹配的字符
  95. if(deviation / width < trueLetter.deviation / trueLetter.width){
  96. trueLetter.letter = letter;
  97. trueLetter.width = width;
  98. trueLetter.deviation = deviation;
  99. }
  100. }
  101. }
  102. // 如果已经到了行末,重置匹配坐标,进行下一轮匹配
  103. if(isEmpty){
  104. x = beginX;
  105. y += lineHeight;
  106. result += '\n';
  107. continue;
  108. }
  109. // 如果匹配到的字符不为空,则加入结果字符串,否则输出匹配结果
  110. if(trueLetter.letter){
  111. result += trueLetter.letter;
  112. // console.log(x, y, trueLetter.letter);
  113. }else{
  114. console.log(x, y, result.length);
  115. break;
  116. }
  117. // 调整坐标至下一个字符匹配位置
  118. x += trueLetter.width;
  119. }
  120. // 非递归结束
  121. // 图像数据的灰度化及0-1化
  122. function getBinary(data){
  123. var binaryData = [];
  124. for(var i = 0, l = data.length; i < l; i += 4){
  125. // 尝试过三种方式
  126. // 一种是正常的灰度化公式,无论系数如何调整都无法与绘制的文字字型数据很好地匹配
  127. // binaryData[i / 4] = (data[i] * 0.3 + data[i + 1] * 0.59 + data[i + 2] * 0.11) < 90;
  128. // 一种是自己是通过自己手动调整系数,结果虽然接近但总是不尽人意
  129. // binaryData[i / 4] = data[i] < 250 && data[i + 1] < 203 && data[i + 2] < 203;
  130. // 最后使用了平均值,结果比较理想
  131. binaryData[i / 4] = (data[i] + data[i + 1] + data[i + 2]) / 3 < 200;
  132. }
  133. return binaryData;
  134. }
  135. console.log(result);
  136. // 输出耗时
  137. console.log(count, (new Date().getTime() - beginTime) / 1000 + ' s');
  138. // 将文字绘制到图片对应位置上,以方便查看提取是否正确
  139. context.drawImage(this, this.width, 0);
  140. var textArray = result.split('\n');
  141. for(var i = 0; i < textArray.length; ++i){
  142. context.fillText(textArray[i], this.width + beginX, beginY + lineHeight * i);
  143. }
  144. }
  145. </script>
  146. </body>
  147. </html>

运行结果


  1. yv66vgAAADIAHQoABgAPCQAQABEIABIKABMAFAcAF
  2. QcAFgEABjxpbml0PgEAAygpVgEABENvZGUB
  3. AA9MaW5lTnVtYmVyVGFibGUBAARtYWluAQAWKFtMa
  4. mF2YS9sYW5nL1N0cmluZzspVgEAClNvdXJj
  5. ZUZpbGUBAAlNYWluLmphdmEMAAcACAcAFwwAGAA
  6. ZAQBv5paw5rWq6Laz55CD6Zif5a6e5Yqb6LaF
  7. 576k77yM6Zi15a656LGq5Y2O44CC5LmF5Luw5aSn5ZC
  8. N77yM5ZGo5pel5LiA5oiY77yM6L+Y5pyb
  9. 5LiN6YGX5L2Z5Yqb77yM5LiN5ZCd6LWQ5pWZ44CCBw
  10. AaDAAbABwBAARNYWluAQAQamF2YS9sYW5n
  11. L09iamVjdAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdX
  12. QBABVMamF2YS9pby9QcmludFN0cmVhbTsB
  13. ABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgE
  14. AFShMamF2YS9sYW5nL1N0cmluZzspVgAh
  15. AAUABgAAAAAAAgABAAcACAABAAkAAAAdAAEAAQA
  16. AAAUqtwABsQAAAAEACgAAAAYAAQAAAAEACQAL
  17. AAwAAQAJAAAAJQACAAEAAAAJsgACEgO2AASxAAAA
  18. AQAKAAAACgACAAAAAwAIAAQAAQANAAAAAgAO
  19. 702 1.931 s(猎豹)

真正的结果

找了个在线的Base64解码工具将上面的提取结果进行了一下解码,发现是一个Java编译后的.class文件,大概内容是:“新浪足球队实力超群,阵容豪华。久仰大名,周日一战,还望不遗余力,不吝赐教。”

写在最后

这个只是一个最浅层次的文字识别提取算法,不够通用,性能也一般,权当兴趣研究之用,不过我想,勇于实践、敢于尝试的精神才是最重要的。。

因为最近实习工作略忙,再加上学校开学事情也多,拖了两个星期才把这边文章写出来,除此之外还有不少计划都落下了,还得继续努力啊>_<

还有最近的一些思考的结果和感触也要找个时间写下来。

PS:写这篇博客的时候精神略差,之后有想到什么再作补充吧,如果写的不好还请多多指教!

=======================签 名 档=======================
原文地址(我的博客):http://www.clanfei.com/2013/09/1723.html
欢迎访问交流,至于我为什么要多弄一个博客,因为我热爱前端,热爱网页,我更希望有一个更加自由、真正属于我自己的小站,或许并不是那么有名气,但至少能够让我为了它而加倍努力。。
=======================签 名 档=======================

借@阿里巴巴 耍了个帅——HTML5 JavaScript实现图片文字识别与提取的更多相关文章

  1. HTML5 JavaScript实现图片文字识别与提取

    8月底的时候,@阿里巴巴 推出了一款名为“拯救斯诺克”的闯关游戏,作为前端校园招聘的热身,做的相当不错,让我非常喜欢.后来又传出了一条消息,阿里推出了A-star(阿里星)计划,入职阿里的技术培训生, ...

  2. HTML5+javascript实现图片加载进度动画效果

    在网上找资料的时候,看到网上有图片加载进度的效果,手痒就自己也写了一个. 图片加载完后,隐藏loading效果. 想看加载效果,请ctrel+F5强制刷新或者清理缓存. 效果预览:   0%   // ...

  3. 微信开发之移动手机WEB页面(HTML5)Javascript实现一键拨号及短信发送功能

    在做一个微信的微网站中的一个便民服务电话功能的应用,用到移动web页面中列出的电话号码,点击需要实现调用通讯录,网页一键拨号的拨打电话功能. 如果需要在移动浏览器中实现拨打电话,发送email,美国服 ...

  4. [HTML] 微信开发之移动手机WEB页面(HTML5)Javascript实现一键拨号及短信发送功能

    在做一个微信的微网站中的一个便民服务电话功能的应用,用到移动web页面中列出的电话号码,点击需要实现调用通讯录,网页一键拨号的拨打电话功能. 如果需要在移动浏览器中实现拨打电话,发送email,美国服 ...

  5. 经典 HTML5 & Javascript 俄罗斯方块游戏

    Blockrain.js 是一个使用 HTML5 & JavaScript 开发的经典俄罗斯方块游戏.只需要复制和粘贴一段代码就可以玩起来了.最重要的是,它是响应式的,无论你的显示屏多么宽都能 ...

  6. 【教程】HTML5+JavaScript编写flappy bird

         作者: 风小锐      新浪微博ID:永远de风小锐      QQ:547953539      转载请注明出处 PS:新修复了两个bug,已下载代码的同学请查看一下 大学立即要毕业了. ...

  7. html5shiv 是一个针对 IE 浏览器的 HTML5 JavaScript 补丁,目的是让 IE 识别并支持 HTML5 元素。

    html5shiv 是一个针对 IE 浏览器的 HTML5 JavaScript 补丁,目的是让 IE 识别并支持 HTML5 元素. 各版本html5shiv.js CDN网址:https://ww ...

  8. HTML5+JavaScript动画基础 完整版 中文pdf扫描版

    <HTML5+JavaScript动画基础>包括了基础知识.基础动画.高级动画.3D动画和其他技术5大部分,分别介绍了动画的基本概念.动画的JavaScript基础.动画中的三角学.渲染技 ...

  9. 推荐一些好用的 HTML5 & JavaScript 游戏引擎开发库

    推荐一些好用的 HTML5 & JavaScript 游戏引擎开发库 0. 引言 如果你是一个游戏开发者,并且正在寻找一个可以与 JavaScript 和 HTML5 无缝工作的游戏引擎.那么 ...

随机推荐

  1. Visual Studio Code和Docker开发asp.net core和mysql应用

    Visual Studio Code和Docker开发asp.net core和mysql应用 .net猿遇到了小鲸鱼,觉得越来越兴奋.本来.net猿只是在透过家里那田子窗看外面的世界,但是看着海峡对 ...

  2. 国标电表DLT645转MODBUS TCP协议转换器MRD-5021,工业设备,浪涌三级保护MRD

    DL/T645转ModbusTcp协议转换器 MRD-5021具有1 路RS485及1路以太网接口,最多支持同时采集5个DL/T645-1997或者5个2007协议国标电表设备,支持DL/T645协议 ...

  3. 可爱的 Python : Python中函数式编程,第二部分

    英文原文:Charming Python: Functional programming in Python, Part 2,翻译:开源中国 摘要:  本专栏继续让David对Python中的函数式编 ...

  4. python循环,判断及函数

    python中的for循环 #for循环格式(类似Java中的foreach):for 标识符 in 列表名称 : >>> movies = ["movie1", ...

  5. 全国计算机等级考试二级教程-C语言程序设计_第13章_编译预处理和动态存储分配

    free(p);//释放内存 p = NULL;//软件工程规范,释放内存以后,指针应该赋值为空 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h ...

  6. node配置运行环境变量;

    node express 在开发环境和生产环境运行的代码是不一样的, 通常是先配置好的,在开发环境运行一套代码,在生产环境运行另一套代码, 开发环境 development, 生产环境producti ...

  7. leftpad填充函数;

    <!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. MAX Average Problem(斜率优化dp)

    MAX Average Problem Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Othe ...

  9. android开发之Animations的使用(二)

    android开发之Animations的使用(二) 本博文主要讲述的是android开发中的animation动画效果的使用,和上一篇博文不同的是,此次四种动画效果,主要使用的是xml文件实现的,提 ...

  10. [Phonegap+Sencha Touch] 移动开发36 Phonegap/Cordova项目的图标和启动画面(splashscreen)配置

    原文地址:http://blog.csdn.net/lovelyelfpop/article/details/40780111 Phonegap/Cordova项目中的config.xml文件.里面配 ...