Java快速开发第三方——腾讯人工智能AI接入详解(大专狗终章)
要想骑自行车,首先不是要学会如何造自行车,而是学会如何骑行
前言
结合腾讯AI开放平台群里的demo解析,QQ群号:581197347。
这个项目是我只花费两天时间做完的,采用的技术是SpringBoot+SpringCloud+MongoDB。
为啥只要两天呢,现在是微服务快速开发时代,两天开发一个小型项目真的自己都觉得花费时间太长。
前端框架采用Layui,如果是微信访问则会跳到微信端的页面。微信接入采用GitHub的第三方接入微信,
具体功能接入还是要看微信公众号开发文档,架构则是上面所说的SpringBoot+SpringCloud。数据库方面采用MongoDB,因为我只需要存图片文件不需要采集用户信息。
项目
这是现在上线的项目,您也可以微信扫码访问,都是同一个链接。点击访问项目
在iphone这边试了下人脸融合好像不行。因为微信jdk要另外设置。没ios机...
为什么选择腾讯
这里主要详解如何接入腾讯第三方AI,其他会简单带过:腾讯AI官网
为什么选择腾讯的?而不是选择阿里,网易,百度呢?主要他们都要认证,要钱。(贫穷限制我的选择)。
开玩笑,言归正传,这是因为开发简洁通用,只要会一种,其他就换汤不换药。唯一的缺点就是他不保证并发。如果你只需要一种识别,并且是稳定高并发的,我建议阿里哈。
腾讯AI技术文档这个文档的接口鉴权也就是获取sign我是不建议你们去看的,因为都不知道他在说什么奖杯,我在这里跟大家讲如何获得sign,后面的接入就可以按照文档来操作了。
ps:吐槽一下,腾讯包括微信,他们的文档确实没有阿里做得好。
开始接入
首先在这里,我说明一下,我会将他作为一个main主函数来运行。我也只讲一种方式,其他你真的看懂了,自然而然就会运用了。
- 首先要知道如何获得签名也就是我们所谓的Sign和要求计算的参数
你可以根据官方文档看到我们需要通过这几个参数来计算出签名,然而他们并没有说明这个text:腾讯AI开放平台是什么,这个text就是你要接入哪种识别另外要加入的参数,就比如这里我讲解人脸融合,人脸融合根据文档要多传一个model参数,也就是说这个model也要加入Sign的计算当中。
- 那么我们知道了sign需要的参数,我们要怎么计算呢。
直接看文档对于初学者来说,看了等于白看,文档说要
1. 获得参数对列表N(字典升级排序)。 2. 按URL键值拼接字符串T,参数对列表N的参数对进行URL键值拼接,值使用URL编码,URL编码算法用大写字母。 3. 拼接应用密钥,得到字符串S。 4. 计算MD5摘要,得到签名字符串。
说实话,第一次我看这个文档我的main函数运行的不少于100遍的出错。那要怎么接入呢? 如果你看的懂文档,请直接略过这端Sign签名
获得Sign
时间戳time_stamp和随机字符串nonce_str我们直接可以自己生成的。
String time_stamp = System.currentTimeMillis()/1000+""; String nonce_str = TencentAISign.getRandomString(10);
还有image属性也就是你上传的照片,腾讯限定了图片大小(根据文档)和要求原始图片的base64编码数据
如何限定图片大小就是你的事了,在这里将如何将图片进行base64编码,编码这里我讲两种,一种是本地路径编码,另一种根据Url网络资源编码。
这里我写一个工具类,可以根据本地或者Url分别进行base64编码
public class UrlMethodUtil { public static byte[] local2byte(String url)throws Exception{ //由本地路径得到byte byte [] imageData = FileUtil.readFileByBytes(url); return imageData; } public static byte[] url2byte(String url)throws Exception { //由url得到byte byte [] imageData = IoUtil.getImageFromNetByUrl(url); return imageData; } }
FileUtil 就是从本地路径获得byte[]数据
public class FileUtil { /** * 根据文件路径读取byte[] 数组 */ public static byte[] readFileByBytes(String filePath) throws IOException { File file = new File(filePath); if (!file.exists()) { throw new FileNotFoundException(filePath); } else { ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length()); BufferedInputStream in = null; try { in = new BufferedInputStream(new FileInputStream(file)); short bufSize = 1024; byte[] buffer = new byte[bufSize]; int len1; while (-1 != (len1 = in.read(buffer, 0, bufSize))) { bos.write(buffer, 0, len1); } byte[] var7 = bos.toByteArray(); return var7; } finally { try { if (in != null) { in.close(); } } catch (IOException var14) { var14.printStackTrace(); } bos.close(); } } } }
IoUtil就是从Url获得byte[]数据
public class IoUtil { /** * 根据地址获得数据的字节流 * @param strUrl 网络连接地址 * @return */ public static byte[] getImageFromNetByUrl(String strUrl){ try { URL url = new URL(strUrl); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(10 * 1000); InputStream inStream = conn.getInputStream();//通过输入流获取图片数据 byte[] btImg = readInputStream(inStream);//得到图片的二进制数据 return btImg; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 从输入流中获取数据 * @param inStream 输入流 * @return * @throws Exception */ public static byte[] readInputStream(InputStream inStream) throws Exception{ ByteArrayOutputStream outStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while( (len=inStream.read(buffer)) != -1 ){ outStream.write(buffer, 0, len); } inStream.close(); return outStream.toByteArray(); } }
然后就可以很轻松的调用了:
byte [] imageData = UrlMethodUtil.local2byte("E:/demo.png");//本地图片
byte [] imageData = UrlMethodUtil.url2byte("网络资源路径Url");
得到byte数组了我们就可以根据Base64的工具类转化,这里我贴出来,百度一堆
public class Base64Util { private static final char last2byte = (char) Integer.parseInt("00000011", 2); private static final char last4byte = (char) Integer.parseInt("00001111", 2); private static final char last6byte = (char) Integer.parseInt("00111111", 2); private static final char lead6byte = (char) Integer.parseInt("11111100", 2); private static final char lead4byte = (char) Integer.parseInt("11110000", 2); private static final char lead2byte = (char) Integer.parseInt("11000000", 2); private static final char[] encodeTable = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; public Base64Util() { } public static String encode(byte[] from) { StringBuilder to = new StringBuilder((int) ((double) from.length * 1.34D) + 3); int num = 0; char currentByte = 0; int i; for (i = 0; i < from.length; ++i) { for (num %= 8; num < 8; num += 6) { switch (num) { case 0: currentByte = (char) (from[i] & lead6byte); currentByte = (char) (currentByte >>> 2); case 1: case 3: case 5: default: break; case 2: currentByte = (char) (from[i] & last6byte); break; case 4: currentByte = (char) (from[i] & last4byte); currentByte = (char) (currentByte << 2); if (i + 1 < from.length) { currentByte = (char) (currentByte | (from[i + 1] & lead2byte) >>> 6); } break; case 6: currentByte = (char) (from[i] & last2byte); currentByte = (char) (currentByte << 4); if (i + 1 < from.length) { currentByte = (char) (currentByte | (from[i + 1] & lead4byte) >>> 4); } } to.append(encodeTable[currentByte]); } } if (to.length() % 4 != 0) { for (i = 4 - to.length() % 4; i > 0; --i) { to.append("="); } } return to.toString(); } }
这样image的Base64编码搞定,终于拿到了image属性了,接下来就可以准备开始签名啦
我们现在拥有的属性有:appid,appKey,time_stamp,nonce_str,image。再根据我们要实现的功能需要的参数终于可以实现签名啦。感觉好麻烦有木有。确实是挺麻烦的,但这也是一种安全验证的过程。
那我们怎么将这么多参数放在一起计算出Sign呢?答案可想而知,用Map。
Map<String,String> person_Id_body = new HashMap<>(); person_Id_body.put("app_id", String.valueOf(TencentAPI.APP_ID_AI)); person_Id_body.put("time_stamp",time_stamp); person_Id_body.put("nonce_str", nonce_str); person_Id_body.put("image", img64); person_Id_body.put("model","1"); //这是人脸融合需要的参数,1表示模板1.
在这里我将用在腾讯公众平台群一位大神发出来的通用签名工具类来签名
public class TencentAISignSort { /** * SIGN签名生成算法-JAVA版本 通用。默认参数都为UTF-8适用 * @param HashMap<String,String> params 请求参数集,所有参数必须已转换为字符串类型 * @return 签名 * @throws IOException */ public static String getSignature(Map<String,String> params) throws IOException { // 先将参数以其参数名的字典序升序进行排序 Map<String, String> sortedParams = new TreeMap<>(params); Set<Map.Entry<String, String>> entrys = sortedParams.entrySet(); // 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起 StringBuilder baseString = new StringBuilder(); for (Map.Entry<String, String> param : entrys) { //sign参数 和 空值参数 不加入算法 if(param.getValue()!=null && !"".equals(param.getKey().trim()) && !"sign".equals(param.getKey().trim()) && !"".equals(param.getValue().trim())) { baseString.append(param.getKey().trim()).append("=").append(URLEncoder.encode(param.getValue().trim(),"UTF-8")).append("&"); } } System.err.println("未拼接APPKEY的参数:"+baseString.toString()); if(baseString.length() > 0 ) { baseString.deleteCharAt(baseString.length()-1).append("&app_key="+TencentAPI.APP_KEY_AI); } System.err.println("拼接APPKEY后的参数:"+baseString.toString()); // 使用MD5对待签名串求签 try { String sign = MD5.getMD5(baseString.toString()); return sign; } catch (Exception ex) { throw new IOException(ex); } } /** * SIGN签名生成算法-JAVA版本 针对于基本文本分析接口要求text为GBK的方法 * @param HashMap<String,String> params 请求参数集,所有参数必须已转换为字符串类型 * @return 签名 * @throws IOException */ public static String getSignatureforNLP(HashMap<String,String> params) throws IOException { // 先将参数以其参数名的字典序升序进行排序 Map<String, String> sortedParams = new TreeMap<>(params); Set<Map.Entry<String, String>> entrys = sortedParams.entrySet(); // 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起 StringBuilder baseString = new StringBuilder(); for (Map.Entry<String, String> param : entrys) { //sign参数 和 空值参数 不加入算法 if(param.getValue()!=null && !"".equals(param.getKey().trim()) && !"sign".equals(param.getKey().trim()) && !"".equals(param.getValue().trim())) { if(param.getKey().equals("text")){ baseString.append(param.getKey().trim()).append("=").append(URLEncoder.encode(param.getValue().trim(),"GBK")).append("&"); }else{ baseString.append(param.getKey().trim()).append("=").append(URLEncoder.encode(param.getValue().trim(),"UTF-8")).append("&"); } } } if(baseString.length() > 0 ) { baseString.deleteCharAt(baseString.length()-1).append("&app_key="+TencentAPI.APP_KEY_AI); } // 使用MD5对待签名串求签 try { String sign = MD5.getMD5(baseString.toString()); return sign; } catch (Exception ex) { throw new IOException(ex); } } /** * 获取拼接的参数 * @param params * @return * @throws IOException */ public static String getParams(HashMap<String,String> params) throws IOException { // 先将参数以其参数名的字典序升序进行排序 Map<String, String> sortedParams = new TreeMap<>(params); Set<Map.Entry<String, String>> entrys = sortedParams.entrySet(); // 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起 StringBuilder baseString = new StringBuilder(); for (Map.Entry<String, String> param : entrys) { //sign参数 和 空值参数 不加入算法 baseString.append(param.getKey().trim()).append("=").append(URLEncoder.encode(param.getValue().trim(),"UTF-8")).append("&"); } return baseString.toString(); } }
这样简单一句话就实现了签名啦:
String sign = TencentAISignSort.getSignature(person_Id_body);
向API发送请求
person_Id_body.put("sign", sign); //将Sign也放入Map中 //这我就不用说了吧,这是头信息需要的 Map<String, String> headers = new HashMap<>(); //headers头 headers.put("Content-Type", "application/x-www-form-urlencoded");
HttpResponse responseBD = HttpsUtil4Tencent.doPostTencentAI(TencentAPI.FACEMERGE, headers, person_Id_body); String json = EntityUtils.toString(responseBD.getEntity()); System.out.println(json); //这个就是我们的要的数据了
ps:一堆工具类对不对啊哈哈哈,这才叫快速开发。
//HttpsUtil4Tencent工具类: public class HttpsUtil4Tencent { private static HttpClient wrapClient(String host) { HttpClient httpClient = new DefaultHttpClient(); if (host.startsWith("https://")) { sslClient(httpClient); } return httpClient; } private static void sslClient(HttpClient httpClient) { try { SSLContext ctx = SSLContext.getInstance("TLS"); X509TrustManager tm = new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] xcs, String str) { } public void checkServerTrusted(X509Certificate[] xcs, String str) { } }; ctx.init(null, new TrustManager[] { tm }, null); SSLSocketFactory ssf = new SSLSocketFactory(ctx); ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); ClientConnectionManager ccm = httpClient.getConnectionManager(); SchemeRegistry registry = ccm.getSchemeRegistry(); registry.register(new Scheme("https", 443, ssf)); } catch (KeyManagementException ex) { throw new RuntimeException(ex); } catch (NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } } /** * * @Title doPostBD * @param url 接口地址 * @param method 请求方式 * @param headers * @param bodys * @return response * @throws Exception * @author 小帅帅丶 * @date 2017-3-20 * */ public static HttpResponse doPostTencentAI(String url, Map<String, String> headers, Map<String, String> bodys) throws Exception { HttpClient httpClient = wrapClient(url); HttpPost request = new HttpPost(url); for (Map.Entry<String, String> e : headers.entrySet()) { request.addHeader(e.getKey(), e.getValue()); } if (bodys != null) { List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>(); for (String key : bodys.keySet()) { nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key))); } UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList); formEntity.setContentType("application/x-www-form-urlencoded;charset=UTF-8"); request.setEntity(formEntity); } return httpClient.execute(request); } }
得到的json就是腾讯给我们的数据。那我们要怎么把json数据转为对象呢。
我的另一篇文章就写了:Java 跨域 Json字符转类对象
在人脸识别这块,他回应的是一串base64的数据,我们应该怎么转为图片呢
String xmlImg = persion_id.getImage(); response.setContentType("image/*"); // 设置返回的文件类型 OutputStream toClient = response.getOutputStream(); IoUtil.GenerateImage(xmlImg,toClient);
没错,还是工具类IoUtil。
public class TencentAPI { //自己的APPID public static final Integer APP_ID_AI = 0; //自己的APPKEY public static final String APP_KEY_AI = "******"; public static final String PERSON_ID = "https://api.ai.qq.com/fcgi-bin/ocr/ocr_idcardocr"; //身份证识别 public static final String PHOTO_SPEAK = "https://api.ai.qq.com/fcgi-bin/vision/vision_imgtotext"; //看图说话 public static final String SCENE_RECOGNITION = "https://api.ai.qq.com/fcgi-bin/vision/vision_scener"; //场景识别:对图行进行场景识别,快速找出图片中包含的场景信息 public static final String OBJECT_RECOGNITION = "https://api.ai.qq.com/fcgi-bin/vision/vision_objectr"; //物体识别:对图行进行物体识别,快速找出图片中包含的物体信息 public static final String IMAGE_LABEL = "https://api.ai.qq.com/fcgi-bin/image/image_tag"; //图像标签识别:识别一个图像的标签信息,对图像分类。 public static final String FACEMERGE="https://api.ai.qq.com/fcgi-bin/ptu/ptu_facemerge"; //人脸融合 }
结尾
这一块腾讯人工智能AI接入已解释完毕。如有什么不懂得可以来问下我。我的邮箱是[email protected]
为什么说这是大专狗终章呢,因为我已经大三了,这是我最后一篇在大专学校写的文章。即将面临的面试工作或者插本,前方的路如何,我将何去何从。请等我下篇文章:【插本狗初章】或者【工作狗初章】
世上无难事,只有你会不会,想不想学。