Java快速開發第三方——騰訊人工智能AI接入詳解(大專狗終章)

要想騎自行車,首先不是要學會如何造自行車,而是學會如何騎行html

前言

結合騰訊AI開放平臺羣裏的demo解析,QQ羣號:581197347。
這個項目是我只花費兩天時間作完的,採用的技術是SpringBoot+SpringCloud+MongoDB。
爲啥只要兩天呢,如今是微服務快速開發時代,兩天開發一個小型項目真的本身都以爲花費時間太長。
前端框架採用Layui,若是是微信訪問則會跳到微信端的頁面。微信接入採用GitHub的第三方接入微信
具體功能接入仍是要看微信公衆號開發文檔,架構則是上面所說的SpringBoot+SpringCloud。數據庫方面採用MongoDB,由於我只須要存圖片文件不須要採集用戶信息。前端

項目

這是如今上線的項目,您也能夠微信掃碼訪問,都是同一個連接。點擊訪問項目
在iphone這邊試了下人臉融合好像不行。由於微信jdk要另外設置。沒ios機...
clipboard.pngjava

爲何選擇騰訊

這裏主要詳解如何接入騰訊第三方AI,其餘會簡單帶過:騰訊AI官網
爲何選擇騰訊的?而不是選擇阿里,網易,百度呢?主要他們都要認證,要錢。(貧窮限制個人選擇)。
clipboard.pngios

開玩笑,言歸正傳,這是由於開發簡潔通用,只要會一種,其餘就換湯不換藥。惟一的缺點就是他不保證併發。若是你只須要一種識別,而且是穩定高併發的,我建議阿里哈。
騰訊AI技術文檔這個文檔的接口鑑權也就是獲取sign我是不建議大家去看的,由於都不知道他在說什麼獎盃,我在這裏跟你們講如何得到sign,後面的接入就能夠按照文檔來操做了。
ps:吐槽一下,騰訊包括微信,他們的文檔確實沒有阿里作得好。git

開始接入

首先在這裏,我說明一下,我會將他做爲一個main主函數來運行。我也只講一種方式,其餘你真的看懂了,天然而然就會運用了。github

  • 首先要知道如何得到簽名也就是咱們所謂的Sign和要求計算的參數
    你能夠根據官方文檔看到咱們須要經過這幾個參數來計算出簽名,然而他們並無說明這個text:騰訊AI開放平臺是什麼,這個text就是你要接入哪一種識別另外要加入的參數,就好比這裏我講解人臉融合,人臉融合根據文檔要多傳一個model參數,也就是說這個model也要加入Sign的計算當中。

clipboard.png

  • 那麼咱們知道了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[]數據json

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接入已解釋完畢。若有什麼不懂得能夠來問下我。個人郵箱是519286925@qq.com
爲何說這是大專狗終章呢,由於我已經大三了,這是我最後一篇在大專學校寫的文章。即將面臨的面試工做或者插本,前方的路如何,我將何去何從。請等我下篇文章:【插本狗初章】或者【工做狗初章】

世上無難事,只有你會不會,想不想學。

相關文章
相關標籤/搜索