接上篇,我们成功的安装了tesseract-OCR工具,这篇我们主要讲解制作jmeter插件VcodeExtractor的过程,并且如何利用这个插件来调用tesseract-OCR实现图片识别。
一、准备jar包
在开发之前我们要先做一下准备工作,需要jmeter/lib和jmeter/lib/ext下的jar包,ApacheJMeter_core.jar,slf4j-api-1.7.25.jar,logkit-2.0.jar(其中前两个在lib和ext目录下有现成的,而logkit-2.0.jar需要下载,可以到链接:https://pan.baidu.com/s/11i5tn0M2kTZMh65XaEVLAw 提取码:rgss 下载)。
另外还需要两个图形处理相关的jar包,在开发的过程中作为依赖库使用,
swingx-1.6.1.jar
下载链接:https://pan.baidu.com/s/1tOC2TgKhkp8_jhua6l5U_A提取码:djdy 。
jai_imageio-1.1.jar
下载链接:https://pan.baidu.com/s/1nMBJ4D4j5TDxPFLBTb2LLQ提取码:8fnx
二、将依赖的jar包添加到工程构建路径
在eclipse中创建VcodeExtractor工程,并创建lib路径。
将jar包拖拽到lib路径下(或直接复制,如果已经存在会提示是否overwrite覆盖掉),如图:
然后右键点击lib文件,添加到构建路径
然后将所有需要的jar包添加即可。
添加完后点击应用并关闭,效果如图,
三、开发插件VcodeExtractor
这里要创建几个类,主要实现两部分功能,一个是调用三方工具tesseract-OCR进行图片识别,另一个是Jmeter插件UI部分。
在src目录下创建ImageIOhelper类,这个类主要包括两部分,前半部分将图片转化为tiff格式,后半部分通过降噪逻辑对图片进行处理,以便进行更好的识别。代码如下:
package com.test.laoli;import java.awt.Color;import java.awt.image.BufferedImage;import java.io.File;import java.io.IOException;import java.util.Iterator;import java.util.Locale; import javax.imageio.IIOImage;import javax.imageio.ImageIO;import javax.imageio.ImageReader;import javax.imageio.ImageWriteParam;import javax.imageio.ImageWriter;import javax.imageio.metadata.IIOMetadata;import javax.imageio.stream.ImageInputStream;import javax.imageio.stream.ImageOutputStream;import com.sun.media.imageio.plugins.tiff.TIFFImageWriteParam; public class ImageIOHelper{ //将图片格式转换为tif格式以更好地进行识别 public static File createImage(File imageFile, String imageFormat) { File tempFile = null; ImageInputStream iis = null; ImageOutputStream ios = null; ImageReader reader = null; ImageWriter writer = null; try { Iterator readers = ImageIO.getImageReadersByFormatName(imageFormat); reader = readers.next(); iis = ImageIO.createImageInputStream(imageFile); reader.setInput(iis); IIOMetadata streamMetadata = reader.getStreamMetadata(); TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.CHINESE); tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED); Iterator writers = ImageIO.getImageWritersByFormatName("tiff"); writer = writers.next(); BufferedImage bi = removeBackgroud(reader.read(0)); IIOImage image = new IIOImage(bi,null,reader.getImageMetadata(0)); tempFile = tempImageFile(imageFile); ios = ImageIO.createImageOutputStream(tempFile); writer.setOutput(ios); writer.write(streamMetadata, image, tiffWriteParam); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if(iis != null){ try { iis.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(ios != null){ try { ios.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(writer != null){ writer.dispose(); } if(reader != null){ reader.dispose(); } } return tempFile; } private static File tempImageFile(File imageFile) { String path = imageFile.getPath(); StringBuffer strB = new StringBuffer(path); return new File(strB.toString().replaceFirst("jpg", "tif")); } //给图片降噪,建立如下降噪规则和方法,提高识别度 public static int isFilter(int colorInt) { Color color = new Color(colorInt); if ((color.getRed() > 85 && color.getRed() < 255) && (color.getGreen() > 85 && color.getGreen() < 255) && (color.getBlue() > 85 && color.getBlue() < 255)) { return 1; } return 0; } public static BufferedImage removeBackgroud(BufferedImage img) throws Exception { int width = img.getWidth(); int height = img.getHeight(); for (int x = 0; x < width; ++x) { for (int y = 0; y < height; ++y) { if (isFilter(img.getRGB(x, y)) == 1) { img.setRGB(x, y, Color.WHITE.getRGB()); } } } return img; } }
然后创建OCR类,通过Process调用安装好的三方工具tesseract-OCR进行图片识别,调用命令的基本形式如下tesseract xxx.tif 1 -l eng,代码如下:
package com.test.laoli;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.List; public class OCR { private final String LANG_OPTION = "-l"; private final String EOL = System.getProperty("line.separator"); //第三方图形图像识别工具tesseract-OCR的安装目录 private String tessPath = "D://Tesseract-OCR"; public String recognizeText(File imageFile,String imageFormat) { File tempImage = ImageIOHelper.createImage(imageFile,imageFormat); File outputFile = new File(imageFile.getParentFile(),"output" + imageFile.getName()); StringBuffer sb = new StringBuffer(); List<String> cmd = new ArrayList<String>(); cmd.add(tessPath+"//tesseract"); cmd.add(""); cmd.add(outputFile.getName()); cmd.add(LANG_OPTION); cmd.add("eng"); ProcessBuilder pb = new ProcessBuilder(); pb.directory(imageFile.getParentFile()); cmd.set(1, tempImage.getName()); pb.command(cmd); pb.redirectErrorStream(true); Process process = null; BufferedReader in = null; int wait; try { process = pb.start(); //通过Process调用已经安装的Tesseract程序,调用命令基本形式为:tesseract xxx.tif 1 -l eng wait = process.waitFor(); if(wait == 0){ in = new BufferedReader(new InputStreamReader(new FileInputStream(outputFile.getAbsolutePath()+".txt"),"UTF-8")); String str; while((str = in.readLine())!=null){ sb.append(str).append(EOL); } in.close(); }else{ tempImage.delete(); } new File(outputFile.getAbsolutePath()+".txt").delete(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if(in != null){ try { in.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } tempImage.delete(); return sb.toString(); }}
然后创建VcodeExtractor类,他主要继承AbstractScopedTestElement类,实现PostProcessor接口的process方法,处理利用tesseract-OCR读取验证码信息的逻辑控制,代码如下:
package com.test.laoli;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.Serializable; import org.apache.jmeter.processor.PostProcessor;import org.apache.jmeter.samplers.SampleResult;import org.apache.jmeter.testelement.AbstractScopedTestElement;import org.apache.jmeter.threads.JMeterContext;import org.apache.jmeter.threads.JMeterVariables;import org.slf4j.Logger;import org.slf4j.LoggerFactory; public class VcodeExtractor extends AbstractScopedTestElement implements PostProcessor, Serializable{ private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(VcodeExtractor.class); @Override public void process() { // TODO Auto-generated method stub JMeterContext context = getThreadContext(); SampleResult previousResult = context.getPreviousResult(); if (previousResult == null) { return; } log.debug("VcodeExtractor processing result"); String status = previousResult.getResponseCode(); int id = context.getThreadNum(); //String imageName = id + ".jpg"; String path = "D:\\1\\1.png"; if(status.equals("200")){ byte[] buffer = previousResult.getResponseData(); FileOutputStream out = null; File file = null; try { file = new File(path); out = new FileOutputStream(file); out.write(buffer); out.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if(out != null){ try { out.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } try { String vcode = new OCR().recognizeText(file, "png"); vcode = vcode.replace(" ", "").trim(); JMeterVariables var = context.getVariables(); var.put("vcode", vcode); var.put("vuser", String.valueOf(id)); } catch (Exception e) { e.printStackTrace(); } } } }
然后就是VcodeExtractorGUI类,主要实现一个可视化界面,可以在Jmeter中直接添加。代码如下:
package com.test.laoli;import org.apache.jmeter.processor.gui.AbstractPostProcessorGui;import org.apache.jmeter.testelement.TestElement; public class VcodeExtractorGUI extends AbstractPostProcessorGui{ private static final long serialVersionUID = 1L; @Override public TestElement createTestElement() { // TODO Auto-generated method stub VcodeExtractor extractor = new VcodeExtractor(); modifyTestElement(extractor); return extractor; } @Override public String getLabelResource() { // TODO Auto-generated method stub return this.getClass().getName(); } @Override public String getStaticLabel() { //设置显示名称 // TODO Auto-generated method stub return "VcodeExtractor"; } @Override public void modifyTestElement(TestElement extractor) { // TODO Auto-generated method stub super.configureTestElement(extractor); }}
最后是主函数,程序启动入口。代码如下:
package com.test.laoli; import java.io.File; public class TestOCR { public static void main(String[] args) { String path = "D:\\1\\1.png"; try { String valCode = new OCR().recognizeText(new File(path), "png"); System.out.println(valCode); } catch (Exception e) { e.printStackTrace(); } } }
这样我们就可以执行程序进行测试,首先我们下载一张图片验证码到代码中设置的路径下,然后执行程序,如图:
可以看到,程序可以执行成功,但是验证码(71422)中的最后一位“2”并未识别出来。这个我们先不管,最后再验证成功率是多少。
四、将开发的组件导出jar包
首先,我们右键点击工程,弹出如下提示对话框,
我们点击导出,并选择导出路径和方式,就可以成功将工程导出为jar包。
然后我们在保存jar包的路径下先下载一张图片验证码,使用win+R输入cmd打开命令行,切换到保存jar包的路径下,执行shell命令,java-jar VcodeExtractor.Jar命令,如图:
这就证明我们生成的jar包可以使用。但是精度还是有问题·····
五、利用Jmeter完成自动化接口测试
首先我们,将生成的jar包和相应的文件夹复制到jmeter程序中的lib/ext路径下,然后启动jmeter程序。
如图,我先是调用了获取图片验证码的接口,然后在他的取样器下添加了我们自己制作的插件VcodeExtractor,这个插件默认将识别的验证码定义为变量${vcode},所以我们添加第二个接口将验证法通过接口发送给后端验证正确性。
这就说明我们制作的VcodeExtractor插件是可以用的,接下来重点来了,老李非常想知道这个OCR工具识别图片验证码的成功率是多少,于是就将线程调到了30,并添加了聚合报告来进行查看。如图:
看到聚合报告的的一刹那,瞬间好失望呀,这,这····还有得救吗?
话说,经过图片训练可以提高识别率,但是关于这方面的知识还需要继续学习啊,继续加油吧。