一、寫在前面 最近做項目需要用到識別圖片中文字的功能,本來用的Tesseract這個寫的,不過效果不是很理想。 隨後上網搜了一下OCR介面,就準備使用騰訊雲、百度的OCR介面試一下效果。不過這個騰訊雲OCR就折騰了一天! 二、OCR-通用印刷體識別 首先附上文檔地址:OCR-通用印刷體識別 有兩種調 ...
一、寫在前面
最近做項目需要用到識別圖片中文字的功能,本來用的Tesseract這個寫的,不過效果不是很理想。
隨後上網搜了一下OCR介面,就準備使用騰訊雲、百度的OCR介面試一下效果。不過這個騰訊雲OCR就折騰了一天!
二、OCR-通用印刷體識別
首先附上文檔地址:OCR-通用印刷體識別
有兩種調用方式:Url和本地圖片。
1、使用 url 的請求包
POST /ocr/general HTTP/1.1 Authorization: FCHXdPTEwMDAwMzc5Jms9QUtJRGVRZDBrRU1yM2J4ZjhRckJi== Host: recognition.image.myqcloud.com Content-Length: 187 Content-Type: application/json { "appid":"123456", "bucket":"test", "url":"http://test-123456.image.myqcloud.com/test.jpg" }
看到這種格式,心裡大概有數了,開始敲代碼:
HttpClient client = new HttpClient(); var para = new { appid = "123456", bucket = "test", url = "http://test-123456.image.myqcloud.com/test.jpg" }; var jsonPara = JsonConvert.SerializeObject(para); StringContent content = new StringContent(jsonPara); content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); content.Headers.ContentLength = jsonPara.Length; content.Headers.Add("Host", "recognition.image.myqcloud.com"); content.Headers.Add("Authorization", aut); var taskHrm = client.PostAsync(url, content); taskHrm.Wait(); var taskStr = taskHrm.Result.Content.ReadAsStringAsync(); taskStr.Wait(); var result = taskStr.Result;
F5運行,結果報錯了:
Host和Authorization不能這樣添加到Headers中。於是乎我自作聰明的把它們放到參數中了:
var para = new { appid = "123456", bucket = "test", url = "http://test-123456.image.myqcloud.com/test.jpg", Host = "recognition.image.myqcloud.com", Authorization = aut };
這樣運行代碼時沒有報錯,不過後臺返回“has no sign or sign is empty”,沒有簽名。
再回頭看看參數要求,Host和Authorization必須添加在請求包Header中。於是百度一下,如何使用Httpclient設置Authorization。最終解決方案如下:
client.DefaultRequestHeaders.Host = "recognition.image.myqcloud.com"; client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", aut);
成功運行!
使用url的幾率畢竟少,項目中基本上都是上傳一張本地圖片,然後識別出來文字。
2、使用 image 的請求包
POST /ocr/general HTTP/1.1 Authorization: FCHXdPTEwMDAwMzc5Jms9QUtJRGVRZDBrRU1yM2J4ZjhRckJi== Host: recognition.image.myqcloud.com Content-Length: 735 Content-Type: multipart/form-data;boundary=--------------acebdf13572468 ----------------acebdf13572468 Content-Disposition: form-data; name="appid"; 123456 ----------------acebdf13572468 Content-Disposition: form-data; name="bucket"; test ----------------acebdf13572468 Content-Disposition: form-data; name="image"; filename="test.jpg" Content-Type: image/jpeg xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ----------------acebdf13572468--
說實話看到這種格式,一開始真是一臉懵逼,前面幾個參數還好,後面這一大串不知道怎麼傳遞過去,後來百度了一下,這種格式符合RFC 2045協議。
具體解釋一下:
第5行:聲明Content Type類型,並定義邊界字元串。邊界符可以自定義,不過最好是用破折號等數據中一般不會出現的字元;
第6、9、13以及18行是換行,傳遞的時候使用‘\r\n';
第7、11以及15行是‘--’加上第5行的boundary即邊界字元串。這裡要註意是一定要加上前面的連字元‘--’,我開始沒註意寫的是和boundary一樣,結果一直報錯。
第8、10、12、14就是傳遞的Key_Value類型的數據。“appid”和“bucket”就是要傳遞的key,而“123456”以及“test”就是分別對應的value。
第16、17行代表另外一個數據,key是image,filename是“test.jpg”。
最後兩行就是結束了。註意最後一行是boundary加上‘--’。
弄清楚是什麼意思了,就可以開始寫代碼了。這裡我們用WebRequest,至於為什麼不用HttpClient,研究ing。不知哪位仁兄使用HttpClient寫過,請不吝賜教。
具體實現
HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(new Uri(url)); Stream memStream = new MemoryStream(); webReq.Method = "POST"; string boundary = "--------------" + DateTime.Now.Ticks.ToString("x");// 邊界符 webReq.ContentType = "multipart/form-data; boundary=" + boundary;
接下來是一個換行符,對應第6行:
byte[] enter = Encoding.ASCII.GetBytes("\r\n"); //換行 memStream.Write(enter, 0, enter.Length);
傳遞key_value的時候格式都是一樣,於是我們寫在一個迴圈裡面:
Dictionary<string, string> dic = new Dictionary<string, string>() { {"appid","1255710379"} , {"bucket","test1"} }; //寫入文本欄位 string inputPartHeaderFormat = "--" + boundary + "\r\n" + "Content-Disposition:form-data;name=\"{0}\";" + "\r\n\r\n{1}\r\n"; foreach (var kv in dic) { string inputPartHeader = string.Format(inputPartHeaderFormat, kv.Key, kv.Value); var inputPartHeaderBytes = Encoding.ASCII.GetBytes(inputPartHeader); memStream.Write(inputPartHeaderBytes, 0, inputPartHeaderBytes.Length); }
接著該寫入image了,這裡我們在bin/debug裡面有一張名為1.jpg的圖片:
var fileStream = new FileStream("1.jpg", FileMode.Open, FileAccess.Read); // 寫入文件 string imagePartHeader = "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\n" + "Content-Type: image/jpeg\r\n\r\n"; var header = string.Format(imagePartHeader, "image", "1.jpg"); var headerbytes = Encoding.UTF8.GetBytes(header); memStream.Write(headerbytes, 0, headerbytes.Length); var buffer = new byte[1024]; int bytesRead; while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) { memStream.Write(buffer, 0, bytesRead); }
最後就是結束符了:
// 最後的結束符 byte[] endBoundary = Encoding.ASCII.GetBytes("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "\r\n" + boundary + "--\r\n"); memStream.Write(endBoundary, 0, endBoundary.Length);
接下來設置其他Header參數:
webReq.ContentLength = memStream.Length; webReq.Headers.Add(HttpRequestHeader.Authorization, aut); webReq.Host = "recognition.image.myqcloud.com";
這裡需要註意的一點是設置Host值的時候不能使用
webReq.Headers.Add(HttpRequestHeader.Host, "recognition.image.myqcloud.com");
這個方法,否則會有異常。
var requestStream = webReq.GetRequestStream(); memStream.Position = 0; memStream.CopyTo(requestStream); HttpWebResponse response = (HttpWebResponse)webReq.GetResponse(); StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8); var ret = sr.ReadToEnd(); sr.Close(); response.Close(); requestStream.Close(); memStream.Close();
完美運行!
3、計算Authorization授權簽名
簽名分為多次有效簽名和單次有效簽名。它們拼接成的簽名串格式為:
a=[appid]&b=[bucket]&k=[SecretID]&e=[expiredTime]&t=[currentTime]&r=[rand]&u=[userid]&f=[fileid]
具體每個欄位的含義請參見官方文檔:簽名和鑒權文檔
需要註意的有兩點:
1、使用 HMAC-SHA1 演算法對請求進行加密;
2、簽名串需要使用 Base64 編碼(首先對orignal使用HMAC-SHA1演算法進行簽名,然後將orignal附加到簽名結果的末尾,再進行Base64編碼,得到最終的sign。)。
/// <summary> /// HMAC-SHA1加密演算法 /// </summary> /// <param name="secret_key">密鑰</param> /// <param name="orignalStr">源文</param> /// <returns></returns> public static string HmacSha1Sign(string secret_key, string orignalStr) { var hmacsha1 = new HMACSHA1(Encoding.UTF8.GetBytes(secret_key)); var orignalBytes = Encoding.UTF8.GetBytes(orignalStr); var hashBytes = hmacsha1.ComputeHash(orignalBytes); List<byte> bytes = new List<byte>(); bytes.AddRange(hashBytes); bytes.AddRange(orignalBytes); return Convert.ToBase64String(bytes.ToArray()); }
三、一些其他的註意點
文中使用的appid、bucket、secret_id、
secret_key需要註冊萬象優圖後,才能得到。至於如何得到,文檔中說的很清楚,有詳細的步驟。
四、最後
希望你在調用騰訊雲-OCR通用印刷體識別Api的時候可以少走些彎路,少踩一些坑。當然了這些可能算不上坑,可能是個人一些基礎知識沒掌握。不管怎麼樣,如果你在使用OCR的時候,本文對你有一點幫助,那它就發揮了應有的作用。
本文的源代碼有興趣的可以下載。