我們的前端智能化框架:http://github.com/alibaba/pipcook 內(nèi)置實驗,可以方便的進行手寫數(shù)字識別和圖像分類任務(wù),這里按照環(huán)境準(zhǔn)備、快速實驗、實戰(zhàn)方法、原理解析的順序,分四個部分進行介紹。完成本教程,你可以開始進行自己的前端智能化項目,用機器學(xué)習(xí)解決編程過程中遇到的問題。
首先,初學(xué)者我更推薦筆記本,因為其便攜性和初期實驗的運算量并不是很大,可以保證在咖啡館或戶外立即開始學(xué)習(xí)和實踐。其次,現(xiàn)在的輕薄筆記本如小米的 Pro 款配備了 Max 150 滿血版,基本可以滿足常用的機器學(xué)習(xí)實驗。Mac Book Pro 的用戶可以考慮帶 AMD 顯卡的筆記本,因為在 PlaidML(intel提供的機器學(xué)習(xí)后端)支持下,Keras 的大部分 OP 都是具備 GPU 硬件加速的。需要注意,PlaidML 對很多神經(jīng)網(wǎng)絡(luò)支持不太好,比如對 RNN 的支持就不好,具體可以看 Issue。在我的16寸 Mac Book Pro上,PlaidML 對 RNN 無硬件加速效果,GPU 監(jiān)視器未有負載且模型編譯過程冗長。
最后,對于有條件的朋友建議準(zhǔn)備臺式機,因為在學(xué)習(xí)實驗中將會遇到越來越多復(fù)雜模型,這些模型一般都需要訓(xùn)練數(shù)天,臺式機能夠提供更好的散熱性能來保證運行的穩(wěn)定性。
組裝臺式機的時候?qū)PU的主頻要求不用太高,一般 AMD 的中低端 CPU 即可勝任,只要核心數(shù)達到6個以上的AMD 12 線程CPU基本就夠用了。內(nèi)存方面最好是 32GB ,16GB 只能說夠用,對海量數(shù)據(jù)尤其是圖片類型進行加工處理的時候,最容易爆的就是內(nèi)存。
GPU方面由于ROCM的完善,喜歡折騰的人選擇 AMD GPU 完全沒問題,不喜歡折騰可以選擇 Nvidia GPU,需要指出的是顯存容量和顯存帶寬在預(yù)算允許的范圍內(nèi)越大越好,尤其是顯存容量,海量參數(shù)的大模型沒有大顯存根本無法訓(xùn)練。
硬盤方面選擇高速 SSD 作為系統(tǒng)盤 512GB 起步,掛載一個混合硬盤作為數(shù)據(jù)存儲和模型參數(shù)存儲即可。電源盡量選擇大一點兒,除了考慮峰值功耗之外,未來可能要考慮多 GPU 來加速訓(xùn)練過程、應(yīng)對海量參數(shù)。機箱作為硬件的家,電磁屏蔽性能好、板材厚重、空間大便于散熱即可,用水冷打造性能小鋼炮的除外。
選擇的依據(jù)很簡單:喜歡折騰的按上述內(nèi)容 DIY ,喜歡簡單的按上述內(nèi)容買帶售后的品牌機。兩者的區(qū)別就是花時間省點兒錢?還是花錢省點兒時間?
對于筆記本自帶 Windows 操作系統(tǒng)的,直接使用 Windows 并沒有問題,Anaconda 基本可以搞定和研發(fā)環(huán)境的所有問題,而且其自帶的 NPM 管理工具很方便。有條件愛折騰的上一個 Ubuntu Linux 系統(tǒng)最好,因為在 Linux 下能夠更加原生支持機器學(xué)習(xí)相關(guān)技術(shù)生態(tài),幾乎不會遇到兼容性問題。
對于臺式機建議安裝 Ubuntu Linux 系統(tǒng),否則,這么好的顯卡很容易裝個 Windows 玩游戲去了……Ubuntu 的安裝盤制作很簡單,一個U盤搞定,一路回車安裝即可。裝好系統(tǒng)后在自己的“~”根目錄下建一個“Workspace”存放代碼文件,制作一個軟鏈接把混合硬盤作為數(shù)據(jù)盤引入即可,未來還可以把 Keras、NLTK 等框架的數(shù)據(jù)集文件夾也以軟鏈接的方式保存在數(shù)據(jù)盤里。
Ubuntu 會自動進行更新,這個很重要,很多框架和庫的 Bug 在這個過程中被修復(fù),需要注意的是在這個過程中出現(xiàn)長時間無響應(yīng)或網(wǎng)絡(luò)問題的情況,可以考慮用阿里云的源來進行加速,然后在命令行手動執(zhí)行更新。
Python教程:https://docs.python.org/zh-cn/3.8/tutorial/index.html
MacOS:https://www.python.org/ftp/python/3.7.7/python-3.7.7-macosx10.9.pkg
Windows:https://www.python.org/ftp/python/3.7.7/python-3.7.7-embed-amd64.zip
https://docs.python.org/zh-cn/3.8/installing/index.html
Node教程:https://nodejs.org/zh-cn/
MacOS:https://nodejs.org/dist/v12.16.2/node-v12.16.2.pkg
Windows:
https://nodejs.org/dist/v12.16.2/node-v12.16.2-x64.msi
https://nodejs.org/dist/v12.16.2/node-v12.16.2-x86.msi
Linux:https://nodejs.org/dist/v12.16.2/node-v12.16.2-linux-x64.tar.xz
下載頁面:https://nodejs.org/zh-cn/download/
模塊網(wǎng)站:https://www.npmjs.com/
安裝方法:
$ npm install -g @pipcook/pipcook-cli
確保你的 Python 版本為 > 3.6 ,你的 Node.js 版本為 > 12.x 的最新穩(wěn)定版本,執(zhí)行上面的安裝命令,就可以在電腦里擁有 Pipcook 的完整開發(fā)環(huán)境了。
命令行:
$ mkdir pipcook-example && cd pipcook-example
$ pipcook init
$ pipcook board
輸出:
> @pipcook/pipcook-board-server@1.0.0 dev /Users/zhenyankun.zyk/work/node/pipcook/example/.server
> egg-bin dev
[egg-ts-helper] create typings/app/controller/index.d.ts (2ms)
[egg-ts-helper] create typings/config/index.d.ts (9ms)
[egg-ts-helper] create typings/config/plugin.d.ts (2ms)
[egg-ts-helper] create typings/app/service/index.d.ts (1ms)
[egg-ts-helper] create typings/app/index.d.ts (1ms)
2020-04-16 11:52:22,053 INFO 26016 [master] node version v12.16.2
2020-04-16 11:52:22,054 INFO 26016 [master] egg version 2.26.0
2020-04-16 11:52:22,839 INFO 26016 [master] agent_worker#1:26018 started (782ms)
2020-04-16 11:52:24,262 INFO 26016 [master] egg started on http://127.0.0.1:7001 (2208ms)
想進行手寫數(shù)字識別實驗,選擇 MNIST Handwritten Digit Recognition (手寫數(shù)字識別)點擊 Try Here 按鈕。想進行圖像分類實驗,選擇 Image Classifiaction for Front-end Assets。
從瀏覽器內(nèi)進入手寫數(shù)字識別的實驗:
按照:
1、鼠繪;
2、點擊預(yù)測按鈕“Predict”;
3、查看預(yù)測結(jié)果“7” 的順序進行實驗,就能看到模型預(yù)測出手寫的圖像是數(shù)字 “7” 。
從瀏覽器進入圖像分類的實驗:
選擇一張圖片后可以看到:
提示正在進行預(yù)測,這個過程會加載模型并進行圖像分類的預(yù)測,當(dāng)選擇 “依更美” 的商標(biāo)圖片并等待一小會兒后,在 Result 區(qū)域可以看到 Json 結(jié)構(gòu)的預(yù)測結(jié)果:
模型可以識別出這個圖像是 “brandLogo” (品牌logo)。
偷天換日:改造現(xiàn)有的工程。就像學(xué)畫畫、學(xué)書法……,從臨摹開始可以極大平滑學(xué)習(xí)曲線。因此,先從改造一個現(xiàn)有的 Pipcook mnist pipline 開始,借助這個過程來實現(xiàn)一個自己的控件識別模型。完成后續(xù)的教程后,你就擁有了一個可以從圖片中識別出 “button” 的模型。
如果你之前看過我的一些文章,基本可以了解 imgcook.com 的原理:通過機器視覺對設(shè)計稿進行前端代碼重構(gòu)。這里定義的問題就是:用機器視覺對設(shè)計稿進行代碼重構(gòu)。但是這個問題太大,作為實戰(zhàn)入門可以簡化一下:用機器視覺對控件進行識別。
為了讓模型可以進行控件識別,首先要定義什么是控件:在計算機編程當(dāng)中,控件(或部件,widget或control)是一種圖形用戶界面元素,其顯示的信息排列可由用戶改變,例如視窗或文本框。控件定義的特點是為給定數(shù)據(jù)的直接操作(direct manipulation)提供單獨的互動點。控件是一種基本的可視構(gòu)件塊,包含在應(yīng)用程序中,控制著該程序處理的所有數(shù)據(jù)以及關(guān)于這些數(shù)據(jù)的交互操作。(引用自維基百科)
根據(jù)問題定義,控件屬于:圖形用戶界面,層級:元素,邊界:提供單獨的互動點。因此,在圖形界面中找到的,提供單獨的互動點的元素,就是控件。對于機器視覺的模型來說,“在圖形界面中找到元素”類似于“在圖像中找到元素”的任務(wù),“在圖像中找到元素”的任務(wù)可以用:目標(biāo)檢測模型來完成。
“Segmenting Nuclei in Microscopy Images”
這里推薦使用 MaskRCNN 地址在:https://github.com/matterport/Mask_RCNN ,可以看到細胞的語義化分割再對分割后的圖像進行分類,就完成了目標(biāo)檢測任務(wù)。總結(jié)一下 Mask RCNN 的目標(biāo)檢測過程是:使用PRN網(wǎng)絡(luò)產(chǎn)生候選區(qū)(語義化分割),再對候選區(qū)進行圖像分類(掩碼預(yù)測多任務(wù)損失)。所謂的語義化,其實就是以語義為基礎(chǔ)來確定數(shù)據(jù)之間的關(guān)系。比如用機器學(xué)習(xí)摳圖,不能把人的胳膊腿、頭發(fā)絲兒扣掉了,這里就應(yīng)用到語義化來確定人像的組成部分。
做個語義分割的機器視覺任務(wù)可能有點兒復(fù)雜,手寫數(shù)字識別這種圖像分類相對簡單。Mask RCNN 只是用 Bounding Box 把圖像切成一塊兒、一塊兒的,然后對每一塊兒圖像進行分類,如果把圖像分類做好了就等于做好了一半兒,讓我們開始吧。
數(shù)據(jù)組織就是根據(jù)問題定義和訓(xùn)練任務(wù)給模型準(zhǔn)備“標(biāo)注樣本”。之前在《前端智能化:思維轉(zhuǎn)變之路》里介紹過,智能化開發(fā)的方法就是告訴機器正確答案(正樣本)、錯誤答案(負樣本)這種標(biāo)注數(shù)據(jù),機器通過對數(shù)據(jù)的分析理解,學(xué)習(xí)到形成答案的解題思路。因此,數(shù)據(jù)組織非常關(guān)鍵,高質(zhì)量的數(shù)據(jù)才能讓機器學(xué)到正確的解題思路。
通過分析 mnist 數(shù)據(jù)集的數(shù)據(jù)組織方式,可以快速復(fù)用 mnist 的例子:
可以看到,Mnist 手寫數(shù)字識別的訓(xùn)練樣本,其實就真的是手寫了一些數(shù)字,給他們打上對應(yīng)的標(biāo)簽(label),寫了“0”就標(biāo)注“0”、寫了“1”就標(biāo)注“1”……這樣,模型訓(xùn)練之后就能夠知道標(biāo)簽“0”對應(yīng)的圖像長什么樣?
其次,要探求一下 Pipcook 在訓(xùn)練模型的時候,對數(shù)據(jù)組織的要求是怎樣的?可以在:https://github.com/alibaba/pipcook/blob/master/example/pipelines/mnist-image-classification.json 里看到
{
"plugins": {
"dataCollect": {
"package": "@pipcook/plugins-mnist-data-collect",
"params": {
"trainCount": 8000,
"testCount": 2000
}
},
根據(jù)線索:@pipcook/plugins-mnist-data-collect 找到:https://github.com/alibaba/pipcook/blob/master/packages/plugins/data-collect/mnist-data-collect/src/index.ts 里:
const mnist=require('mnist');
于是,在:https://github.com/alibaba/pipcook/blob/master/packages/plugins/data-collect/mnist-data-collect/package.json 里找到了:
"dependencies": {
"@pipcook/pipcook-core": "^0.5.9",
"@tensorflow/tfjs-node-gpu": "1.7.0",
"@types/cli-progress": "^3.4.2",
"cli-progress": "^3.6.0",
"jimp": "^0.10.0",
"mnist": "^1.1.0"
},
在:https://www.npmjs.com/package/mnist 里看到了相關(guān)的信息。
從npm包的信息來到:https://github.com/cazala/mnist 源碼站點,在README里找到:
The goal of this library is to provide an easy-to-use way for training and testing MNIST digits for neural networks (either in the browser or node.js). It includes 10000 different samples of mnist digits. I built this in order to work out of the box with Synaptic.
You are free to create any number (from 1 to 60 000) of different examples c via MNIST Digits data loader
這里提到:想要創(chuàng)建不同的樣本可以使用 MNIST Digits datta loader,點進去一探究竟:https://github.com/ApelSYN/mnist_dl 這里有詳細的步驟:
Installation
for node.js: npm install mnist_dl
Download from LeCun’s website and unpack two files:
train-images-idx3-ubyte.gz: training set images (9912422 bytes)
train-labels-idx1-ubyte.gz: training set labels (28881 bytes)
You need to place these files in the "./data" directory.
先去Clone項目:
git clone https://github.com/ApelSYN/mnist_dl.git
正克隆到 'mnist_dl'...
remote: Enumerating objects: 36, done.
remote: Total 36 (delta 0), reused 0 (delta 0), pack-reused 36
展開對象中: 100% (36/36), 完成.
對項目做一下:npm install,然后創(chuàng)建數(shù)據(jù)源和數(shù)據(jù)集目標(biāo)目錄:
# 數(shù)據(jù)源目錄,用來下載 LeCun 大神的數(shù)據(jù)
$ mkdir data
# 數(shù)據(jù)集目錄,用來存放 mnist_dl 處理后的 Json 數(shù)據(jù)
$ mkdir digits
然后在機器學(xué)習(xí)大牛 LeCun 的網(wǎng)站上下載數(shù)據(jù),保存到"./data"目錄下:
http://yann.lecun.com/exdb/mnist/
Mnist的訓(xùn)練樣本圖片數(shù)據(jù):train-images-idx3-ubyte.gz
Mnist的訓(xùn)練樣本標(biāo)簽數(shù)據(jù):train-labels-idx1-ubyte.gz
然后用 mnist_dl 進行測試:
node mnist_dl.js --count 10000
DB digits Version: 2051
Total digits: 60000
x x y: 28 x 28
60000
47040000
Pass 0 items...
Pass 1000 items...
Pass 2000 items...
Pass 3000 items...
Pass 4000 items...
Pass 5000 items...
Pass 6000 items...
Pass 7000 items...
Pass 8000 items...
Pass 9000 items...
Finish processing 10000 items...
Start make "0.json with 1001 images"
Start make "1.json with 1127 images"
Start make "2.json with 991 images"
Start make "3.json with 1032 images"
Start make "4.json with 980 images"
Start make "5.json with 863 images"
Start make "6.json with 1014 images"
Start make "7.json with 1070 images"
Start make "8.json with 944 images"
Start make "9.json with 978 images"
接著 Clone mnist項目進行數(shù)據(jù)集替換測試:
$ git clone https://github.com/cazala/mnist.git
正克隆到 'mnist'...
remote: Enumerating objects: 143, done.
remote: Total 143 (delta 0), reused 0 (delta 0), pack-reused 143
接收對象中: 100% (143/143), 18.71 MiB | 902.00 KiB/s, 完成.
處理 delta 中: 100% (73/73), 完成.
$ npm install
$ cd src
$ cd digits
$ ls
0.json 1.json 2.json 3.json 4.json 5.json 6.json 7.json 8.json 9.json
下面先試試原始數(shù)據(jù)集,使用:mnist/visualizer.html 文件在瀏覽器中打開可以看到:
下面,把數(shù)據(jù)文件替換成剛才處理的文件:
# 進入工作目錄
$ cd src
# 先備份一下
$ mv digits digits-bk
# 再拷貝之前處理的json數(shù)據(jù)
$ cp -R ../mnist_dl/digits ./
$ ls
digits digits-bk mnist.js
強制刷新一下瀏覽器里的:mnist/visualizer.html 文件,可以看到生成的文件完全可用,因此,一個解決方案漸漸浮現(xiàn):替換原始Mnist文件里的內(nèi)容和Mnist標(biāo)簽的內(nèi)容來實現(xiàn)自己的圖片分類檢測模型。
為了能夠替換文件:
Mnist的訓(xùn)練樣本圖片數(shù)據(jù):train-images-idx3-ubyte.gz
Mnist的訓(xùn)練樣本標(biāo)簽數(shù)據(jù):train-labels-idx1-ubyte.gz
成為我們自定義的數(shù)據(jù)集,首先需要了解這兩個文件的格式。通過文件名里 xx-xx-idx3-ubyte 可以看出,文件是按照 idx-ubyte 的方式組織的:
在train-images.idx3-ubyte文件中,偏移量0位置32位的整數(shù)是魔數(shù)(magic number),偏移量位置4為圖片總數(shù)(圖片樣本數(shù)量),偏移量位置8、12為圖片尺寸(存放圖片像素信息的高、寬),偏移量位置16之后的都是像素信息(存放圖片像素值,值域為0~255)。經(jīng)過分析后,只需要依次獲取魔數(shù)和圖片的個數(shù),然后獲取圖片的高和寬,最后逐個像素讀取就可以了。因此,在 MNIST_DL 項目的 lib 文件夾中的 digitsLoader.js 內(nèi)容:
stream.on('readable', function () {
let buf=stream.read();
if (buf) {
if (ver !=2051) {
ver=buf.readInt32BE(0);
console.log(`DB digits Version: ${ver}`);
digitCount=buf.readInt32BE(4);
console.log(`Total digits: ${digitCount}`);
x=buf.readInt32BE(8);
y=buf.readInt32BE(12);
console.log(`x x y: ${x} x ${y}`);
start=16;
}
for (let i=start; i< buf.length; i++) {
digits.push(buf.readUInt8(i));
}
start=0;
}
});
就非常容易理解了,需要做的就是把圖片按照這個過程進行 “逆運算” ,反向把準(zhǔn)備好的圖片樣本組織成這個格式即可。知道如何組織數(shù)據(jù),那么如何生產(chǎn)樣本呢?
在問題分析里,我們了解到 “圖像分類” 是做好控件識別的基礎(chǔ),就像手寫的數(shù)字 “0” 的圖像被標(biāo)記上數(shù)字 “0” 一樣,我們也要對控件進行樣本標(biāo)注。因為樣本標(biāo)注是一個繁瑣冗長的工作,所以機器學(xué)習(xí)的興起催生了一個全新的職業(yè):樣本標(biāo)注工程師。樣本標(biāo)注工程師人工對圖片打標(biāo)簽:
標(biāo)注之后的樣本就可以組織成數(shù)據(jù)集(Dataset)給模型進行訓(xùn)練,因此,良好的標(biāo)注質(zhì)量(準(zhǔn)確傳遞信息給模型)和豐富(從不同視角和不同條件下描述信息)的數(shù)據(jù)集是優(yōu)質(zhì)模型的基礎(chǔ)。后續(xù)會介紹 pipcook 里的樣本制造機,我們會很快開源這部分內(nèi)容,現(xiàn)在,先把樣本制造過程分享一下。
Web 控件以 HTML 標(biāo)簽的形式書寫,然后 HTML 頁面被瀏覽器渲染成圖像,可以利用這個過程和前端流行的 Puppeteer 工具,完成樣本的自動化生成。為了方便,這里用 bootstrap 寫一個簡單的Demo:
<link rel="stylesheet" href="t1.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<div align="middle">
<p>
<button class="btn btn-primary">Primary</button>
</p>
<p>
<button class="btn btn-info">Info</button>
</p>
<p>
<button class="btn btn-success">Success</button>
</p>
<p>
<button class="btn btn-warning">Warning</button>
</p>
<p>
<button class="btn btn-danger">Danger</button>
</p>
<p>
<button class="btn btn-lg btn-primary" type="button">Large button</button>
</p>
<p>
<button class="btn btn-primary" type="button">Default button</button>
</p>
<p>
<button class="btn btn-sm btn-primary" type="button">Mini button</button>
</p>
<p>
<a href="#" class="btn btn-xs btn-primary disabled">Primary link disabled state</a>
</p>
<p>
<button class="btn btn-lg btn-block btn-primary" type="button">Block level button</button>
</p>
<p>
<button type="button" class="btn btn-primary">Primary</button>
</p>
<p>
<button type="button" class="btn btn-secondary">Secondary</button>
</p>
<p>
<button type="button" class="btn btn-success">Success</button>
</p>
<p>
<button type="button" class="btn btn-danger">Danger</button>
</p>
<p>
<button type="button" class="btn btn-warning">Warning</button>
</p>
<p>
<button type="button" class="btn btn-info">Info</button>
</p>
<p>
<button type="button" class="btn btn-light">Light</button>
</p>
<p>
<button type="button" class="btn btn-dark">Dark</button>
</p>
</div>
在瀏覽器打開 HTML 用調(diào)試工具模擬Mobile iPhoneX顯示:
可以從:https://startbootstrap.com/themes/ 里找到很多 Themes,用這些不同的主題來使我們的樣本具備 “多樣性”,讓模型更加容易從圖像中找到 “Button” 的特征。
這樣手工截圖效率太差還不精準(zhǔn),下面就輪到 Puppeteer 工具出場了。首先是初始化一個 node.js 項目并安裝:
$ mkdir pupp && cd pupp
$ npm init --yes
$ npm i puppeteer --save
# or "yarn add puppeteer"
為了能夠處理圖像,需要安裝 https://www.npmjs.com/package/gm 在 http://www.graphicsmagick.org/ 有GM的安裝方法。
$ brew install graphicsmagick
$ npm i gm --save
安裝完成后打開 IDE 添加一個 shortcut.js 文件(依舊會在文末附上全部源碼):
const puppeteer=require("puppeteer");
const fs=require("fs");
const Q=require("Q");
function delay(ms) {
var deferred=Q.defer();
setTimeout(deferred.resolve, ms);
return deferred.promise;
}
const urls=[
"file:///Users/zhenyankun.zyk/work/node/pipcook/pupp/htmlData/page1.html",
"file:///Users/zhenyankun.zyk/work/node/pipcook/pupp/htmlData/page2.html",
"file:///Users/zhenyankun.zyk/work/node/pipcook/pupp/htmlData/page3.html",
"file:///Users/zhenyankun.zyk/work/node/pipcook/pupp/htmlData/page4.html",
"file:///Users/zhenyankun.zyk/work/node/pipcook/pupp/htmlData/page5.html",
];
(async ()=> {
// Launch a headful browser so that we can see the page navigating.
const browser=await puppeteer.launch({
headless: true,
args: ["--no-sandbox", "--disable-gpu"],
});
const page=await browser.newPage();
await page.setViewport({
width: 375,
height: 812,
isMobile: true,
}); //Custom Width
//start shortcut every page
let counter=0;
for (url of urls) {
await page.goto(url, {
timeout: 0,
waitUntil: "networkidle0",
});
await delay(100);
let btnElements=await page.$$("button");
for (btn of btnElements) {
const btnData=await btn.screenshot({
encoding: "binary",
type: "jpeg",
quality: 90,
});
let fn="data/btn" + counter + ".jpg";
Q.nfcall(fs.writeFileSync, fn, btnData);
counter++;
}
}
await page.close();
await browser.close();
})();
通過上述腳本,可以循環(huán)把五種Themes的Button都渲染出來,并利用Puppeteer截圖每個Button:
生成的圖片很少,只有80多張,這里就輪到之前安裝的GM: https://www.npmjs.com/package/gm 出場了:
用GM庫把圖片進行處理,讓它和Mnist的手寫數(shù)字圖片一致,然后,通過對圖片上添加一些隨機文字,讓模型忽略這些文字的特征。這里的原理就是“打破規(guī)律”,模型記住Button特征的方式和人識別事物的方式非常相似。人在識別事物的時候,會記住那些重復(fù)的部分用于分辨。比如我想記住一個人,需要記住這個人不變的特征,例如:眼睛大小、瞳孔顏色、眉距、臉寬、顴骨……,而不會去記住他穿什么衣服、什么鞋子,因為,如果分辨一個人是依靠衣服鞋子,換個衣服鞋子就認不出來了,無異于:刻舟求劍。
下面,看一下具體處理圖片的代碼,請注意,這里并沒有增強,真正使用的時候需要“舉一反三”,用一張圖片生成更多圖片,這就是“數(shù)據(jù)增強”的方法:
const gm=require("gm");
const fs=require("fs");
const path=require("path");
const basePath="./data/";
const chars=[
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"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",
];
let randomRange=(min, max)=> {
return Math.random() * (max - min) + min;
};
let randomChars=(rangeNum)=> {
let tmpChars="";
for (let i=0; i < rangeNum; i++) {
tmpChars +=chars[Math.ceil(Math.random() * 35)];
}
return tmpChars;
};
//獲取此文件夾下所有的文件(數(shù)組)
const files=fs.readdirSync(basePath);
for (let file of files) {
let filePath=path.join(basePath, file);
gm(filePath)
.quality(100)
.gravity("Center")
.drawText(randomRange(-5, 5), 0, randomChars(5))
.channel("Gray")
// .monochrome()
.resize(28)
.extent(28, 28)
.write(filePath, function (err) {
if (!err) console.log("At " + filePath + " done! ");
else console.log(err);
});
}
我們對以下代碼稍加修改就可以達到增強的效果:
for (let file of files) {
for (let i=0; i < 3; i++) {
let rawfilePath=path.join(basePath, file);
let newfilePath=path.join(basePath, i + file);
gm(rawfilePath)
.quality(100)
.gravity("Center")
.drawText(randomRange(-5, 5), 0, randomChars(5))
.channel("Gray")
// .monochrome()
.resize(28)
.extent(28, 28)
.write(newfilePath, function (err) {
if (!err) console.log("At " + newfilePath + " done! ");
else console.log(err);
});
}
}
這樣就把圖片數(shù)量增強擴展到三倍了。
完成了數(shù)據(jù)增強,下一步將圖片組織乘 idx-ubyte 文件,保證 mnist-ld 能夠正常處理。為了組織 idx-ubyte 文件,需要對圖片進行一些特殊處理:提取像素信息、加工成類似 Mnist 數(shù)據(jù)集一樣的數(shù)據(jù)向量等工作。在 JavaScript 里處理會比較痛苦,Python 卻很擅長處理這類問題,那么,用 Python 的技術(shù)生態(tài)來解決問題就需要請 Boa 出場了:https://zhuanlan.zhihu.com/p/128993125 (具體可以看這里的介紹)。
Boa 是我們?yōu)?Pipcook 開發(fā)的底層核心功能,負責(zé)在 JavaScript 里 Bridge Python 技術(shù)生態(tài),整個過程幾乎是性能無損耗的:
首先是安裝:
$ npm install @pipcook/boa --save
其次是安裝 opencv-python :
$ ./node_modules/@pipcook/boa/.miniconda/bin/pip install opencv-python
最后,分享一下如何在 JavaScript 里使用 Boa bridge Python 的能力:
const boa=require("@pipcook/boa");
// 引入一些 python 語言內(nèi)置的數(shù)據(jù)結(jié)構(gòu)
const { int, tuple, list }=boa.builtins();
// 引入 OpenCV
const cv2=boa.import("cv2");
const np=boa.import("numpy");
const Image=boa.import("PIL.Image");
const ImageFont=boa.import("PIL.ImageFont");
const ImageDraw=boa.import("PIL.ImageDraw");
let img=np.zeros(tuple([28, 28, 3]), np.uint8);
img=Image.fromarray(img);
let draw=ImageDraw.Draw(img);
draw.text(list([0, 0]), "Shadow");
img.save("./test.tiff");
來對比一下 Python 的代碼:
import numpy as np
import cv2
from PIL import ImageFont, ImageDraw, Image
img=np.zeros((150,150,3),np.uint8)
img=Image.fromarray(img)
draw=ImageDraw.Draw(img)
draw.text((0,0),"Shadow")
img.save()
可以看到 Python 的代碼和 JavaScript 代碼的差異點主要是:
1、引入包的方式:
Python:import cv2
JavaScript:const cv2=boa.import("cv2");
Python:from PIL import ImageFont, ImageDraw, Image
JavaScript:
const Image=boa.import("PIL.Image");
const ImageFont=boa.import("PIL.ImageFont");
const ImageDraw=boa.import("PIL.ImageDraw");
2、使用 Tuple 等數(shù)據(jù)結(jié)構(gòu):
Python:(150,150,3)
JavaScript:tuple([28, 28, 3])
可以看到,從 github.com 開源機器學(xué)習(xí)項目,移植到 Pipcook 和 Boa 是一件非常簡單的事兒,只要掌握上述兩個方法即可。
課后習(xí)題:
#/usr/bin/env python2.7
#coding:utf-8
import os
import cv2
import numpy
import sys
import struct
DEFAULT_WIDTH=28
DEFAULT_HEIGHT=28
DEFAULT_IMAGE_MAGIC=2051
DEFAULT_LBAEL_MAGIC=2049
IMAGE_BASE_OFFSET=16
LABEL_BASE_OFFSET=8
def usage_generate():
print "python mnist_helper generate path_to_image_dir"
print "\t path_to_image_dir/subdir, subdir is the label"
print ""
pass
def create_image_file(image_file):
fd=open(image_file, 'w+b')
buf=struct.pack(">IIII", DEFAULT_IMAGE_MAGIC, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT)
fd.write(buf)
fd.close()
pass
def create_label_file(label_file):
fd=open(label_file, 'w+b')
buf=struct.pack(">II", DEFAULT_LBAEL_MAGIC, 0)
fd.write(buf)
fd.close()
pass
def update_file(image_file, label_file, image_list, label_list):
ifd=open(image_file, 'r+')
ifd.seek(0)
image_magic, image_count, rows, cols=struct.unpack(">IIII", ifd.read(IMAGE_BASE_OFFSET))
image_len=rows * cols
image_offset=image_count * rows * cols + IMAGE_BASE_OFFSET
ifd.seek(image_offset)
for image in image_list:
ifd.write(image.astype('uint8').reshape(image_len).tostring())
image_count +=len(image_list)
ifd.seek(0, 0)
buf=struct.pack(">II", image_magic, image_count)
ifd.write(buf)
ifd.close()
lfd=open(label_file, 'r+')
lfd.seek(0)
label_magic, label_count=struct.unpack(">II", lfd.read(LABEL_BASE_OFFSET))
buf=''.join(label_list)
label_offset=label_count + LABEL_BASE_OFFSET
lfd.seek(label_offset)
lfd.write(buf)
lfd.seek(0)
label_count +=len(label_list)
buf=struct.pack(">II", label_magic, label_count)
lfd.write(buf)
lfd.close()
def mnist_generate(image_dir):
if not os.path.isdir(image_dir):
raise Exception("{0} is not exists!".format(image_dir))
image_file=os.path.join(image_dir, "user-images-ubyte")
label_file=os.path.join(image_dir, "user-labels-ubyte")
create_image_file(image_file)
create_label_file(label_file)
for i in range(10):
path=os.path.join(image_dir, "{0}".format(i))
if not os.path.isdir(path):
continue
image_list=[]
label_list=[]
for f in os.listdir(path):
fn=os.path.join(path, f)
image=cv2.imread(fn, 0)
w, h=image.shape
if w and h and (w <> 28) or (h <> 28):
simg=cv2.resize(image, (28, 28))
image_list.append(simg)
label_list.append(chr(i))
update_file(image_file, label_file, image_list, label_list)
print "user data generate successfully"
print "output files: \n\t {0}\n\t {1}".format(image_file, label_file)
pass
上面是用 python 寫的一個工具,可以組裝 idx 格式的 mnist 數(shù)據(jù)集,用之前的 mnist-ld 進行處理,就可以替換成我們生成的數(shù)據(jù)集了。
使用樣本平臺更方便:
特征分析和處理可以幫助我們更好的優(yōu)化數(shù)據(jù)集,為了得到圖像的特征,可以采用 Keypoint、SIFT 等特征來表征圖像,這種高階的特征具有各自的優(yōu)勢,例如 SIFT 可以克服旋轉(zhuǎn)、Keypoint 可以克服形變……等等。
Pipline配置:
{
"plugins": {
"dataCollect": {
"package": "@pipcook/plugins-mnist-data-collect",
"params": {
"trainCount": 8000,
"testCount": 2000
}
},
"dataAccess": {
"package": "@pipcook/plugins-pascalvoc-data-access"
},
"dataProcess": {
"package": "@pipcook/plugins-image-data-process",
"params": {
"resize": [28,28]
}
},
"modelDefine": {
"package": "@pipcook/plugins-tfjs-simplecnn-model-define"
},
"modelTrain": {
"package": "@pipcook/plugins-image-classification-tfjs-model-train",
"params": {
"epochs": 15
}
},
"modelEvaluate": {
"package": "@pipcook/plugins-image-classification-tfjs-model-evaluate"
}
}
}
模型訓(xùn)練:
$ pipcook run examples/pipelines/mnist-image-classification.json
模型預(yù)測:
$ pipcook board
回顧整個工程改造的過程:理解 Pipline 的任務(wù)、理解 Pipline 工作原理、了解數(shù)據(jù)集格式、準(zhǔn)備訓(xùn)練數(shù)據(jù)、重新訓(xùn)練模型、模型預(yù)測,下面分別介紹這些關(guān)鍵步驟:
對于 Pipcook 內(nèi)置的 Example ,分為三類:機器視覺、自然語言處理、強化學(xué)習(xí)。機器視覺和自然語言處理,代表“看見”和“理解”,強化學(xué)習(xí)代表決策和生成,這些內(nèi)容可類比于一個程序員,從看到、理解、編寫代碼的過程。在不同的編程任務(wù)中組合使用不同的能力,這就是 Pipline 的使命。
對于 mnist 手寫數(shù)字識別這種簡單的任務(wù),只需要使用部分機器視覺的能力即可,對于 imgcook.com 這種復(fù)雜的應(yīng)用場景,就會涉及很多復(fù)雜的能力。針對不同的任務(wù),通過 Pipline 管理機器學(xué)習(xí)能力使用的方式,就可以把不同的機器學(xué)習(xí)能力組合起來。
最后,需要理解 “機器學(xué)習(xí)應(yīng)用工程” 和 “機器學(xué)習(xí)算法工程” 的區(qū)別。機器學(xué)習(xí)算法工程中,主要是算法工程師在設(shè)計、調(diào)整、訓(xùn)練模型。機器學(xué)習(xí)應(yīng)用工程中,主要是選擇、訓(xùn)練模型。前者是為了創(chuàng)造、改造模型,后者是為了應(yīng)用模型和算法能力。未來,在研讀機器學(xué)習(xí)資料和教材時,可針對上述原則側(cè)重于模型思想和模型應(yīng)用,不要被書里的公式嚇到,那些公式只是用數(shù)學(xué)方法描述模型思想而已。
這就是Pipline的工作原理,主要由圖中 7 類插件構(gòu)成了整個算法工程鏈路。由于引入了 plugin 的開放模式,對于自己的前端工程,可以在遇到問題的時候,自己開發(fā) plugin 來完成工程接入。Plugin 開發(fā)文檔在:https://alibaba.github.io/pipcook/#/tutorials/how-to-develop-a-plugin
了解數(shù)據(jù)集格式是為了讓 Pipline 跑起來,更具體一點兒是讓模型可以識別并使用數(shù)據(jù)。不同的任務(wù)對應(yīng)不同類型的模型,不同的模型對應(yīng)不同類型的數(shù)據(jù)集,這種對應(yīng)關(guān)系保證了模型能夠正確被訓(xùn)練。在 Pipcook 里定義的數(shù)據(jù)集格式也針對了不同的任務(wù)和模型,對于機器視覺的數(shù)據(jù)集是 VOC,對于 NLP Pipcook 定義的數(shù)據(jù)集是 CSV 。具體的數(shù)據(jù)集格式,可以按照文檔:https://alibaba.github.io/pipcook/#/spec/dataset 的說明來分析和理解。也可以采用本文介紹的方法,從相關(guān)處理程序和代碼里進行分析。
數(shù)據(jù)為什么是最重要的部分?因為數(shù)據(jù)的準(zhǔn)確性、分布合理性、數(shù)據(jù)對特征描述的充分性……直接決定了最終的模型效果。為了準(zhǔn)備高質(zhì)量的數(shù)據(jù),還需要掌握 Puppeteer 等工具和爬蟲……等。還可以在傳統(tǒng)機器學(xué)習(xí)理論和工具基礎(chǔ)上,借助 PCA 算法等方式評估數(shù)據(jù)質(zhì)量。還可以用數(shù)據(jù)可視化工具,來直觀的感受數(shù)據(jù)分布情況:
具體可以查看:https://www.yuque.com/zhenzishadow/tx7xtl/xhol3k 我的這篇文章。
訓(xùn)練模型沒有太多可說的,因為今天的模型超參數(shù)并不想以前那么敏感,調(diào)參不如調(diào)數(shù)據(jù)。那么,參數(shù)在訓(xùn)練的時候還有什么意義呢? 遷就GPU和顯存大小。因為訓(xùn)練的時候,除了模型的復(fù)雜度外,超參數(shù)適當(dāng)?shù)恼{(diào)小雖然會犧牲訓(xùn)練速度(也可能影響模型準(zhǔn)確率),但起碼可以保證模型能夠被訓(xùn)練。因此,在 Pipcook 的模型配置中,一旦發(fā)現(xiàn)顯卡OOM了,可以通過調(diào)整超參數(shù)來解決。
預(yù)測的時候唯一需要注意的是:輸入模型訓(xùn)練的數(shù)據(jù)格式和輸入模型預(yù)測的格式必須一致。
部署的時候需要注意的是對容器的選擇,如果只是簡單的模型,其實 CPU 容器足夠用了,畢竟預(yù)測不像訓(xùn)練那樣消耗算力。如果部署的模型很復(fù)雜,預(yù)測時間很長無法接受,則可以考慮 GPU 或 異構(gòu)運算容器。GPU 容器比較通用的是 NVIDIA 的 CUDA 容器,可以參考:https://github.com/NVIDIA/nvidia-docker。如果要使用異構(gòu)運算容器,比如阿里云提供的賽靈思容器等,可以參考阿里云相關(guān)的文檔。
這篇文章斷斷續(xù)續(xù)寫了很久,主要還是平時比較忙,后續(xù)會努力帶來更多文章,分享更多自己在實踐中的一些方法和思考。下一篇會系統(tǒng)完整的介紹一下 NLP 自然語言處理的方法,也會按照:快速實驗、實踐方法、原理解析這種模式來做,敬請期待。
整代碼,忽略樣式:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title></title> <script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script> </head> <body> <div id="app"> <free-tree @select="onselect" :alldata="mydata" class="trees"></free-tree> </div> <script> var freeTreeList={ template: ` <li> <span @click="toggle(model.id)"> <i v-if="isFolder" class="icon" :class="[open ? 'folder-open': 'folder']"></i> <i v-if="!isFolder" class="icon file-text"></i> {{ model.menuName }} </span> <ul style="padding-left: 16px" :class="{slideDown:!open,slideUp:open}" v-show="open" v-if="isFolder"> <free-tree-list @select="onselect" v-for="item in model.children" :model="item" :key="item.id"></free-tree-list> </ul> </li>`, name: 'free-tree-list', // 遞歸組件,name屬性不能少 props: ['model'], data() { return { open: false } }, computed: { isFolder: function() { return this.model.children && this.model.children.length } }, methods: { toggle: function(id) { this.$emit('select', id) if (this.isFolder) { this.open=!this.open } }, onselect(id) { this.$emit('select', id); } } }; var freeTree={ props: { alldata: { type: Array, // default: [] } }, data() { return { } }, template: ` <div class="tree-menu"> <ul v-for="menuItem in alldata" :key="menuItem.id"> <free-tree-list @select="onselect" :model="menuItem"></free-tree-list> </ul> </div>`, components: { freeTreeList }, methods: { onselect(id) { this.$emit('select', id); } } }; var mydata=[{ 'id': '1', 'menuName': '一級-01', 'menuCode': '10', 'children': [{ 'menuName': '二級-01', 'menuCode': '11', 'id': '11' }, { 'menuName': '二級-02', 'menuCode': '12', 'id': '12', 'children': [{ 'menuName': '三級-02', 'menuCode': '121', 'id': '121' }, { 'menuName': '三級-03', 'menuCode': '122', 'id': '122' } ] }, { 'menuName': '二級-03', 'menuCode': '13', 'id': '13' } ] }, { 'id': '2', 'menuName': '一級-02', 'menuCode': '' }, { 'id': '3', 'menuName': '一級-03', 'menuCode': '30', 'children': [{ 'menuName': '二級-01', 'menuCode': '31', 'id': '31' }, { 'menuName': '二級-02', 'menuCode': '32', 'id': '32', 'children': [] } ] }, { 'id': '4', 'menuName': '一級-04', 'menuCode': '', 'children': [] } ]; new Vue({ el: '#app', data: { mydata: mydata }, methods: { onselect(id) { console.log(id); } }, components: { freeTree } }) </script> </body> </html>
點擊每一項,如果有數(shù)據(jù)的,則展開。
一級樹:
二級樹:
三級樹:
于項目的需要,需要在項目的WinForm系統(tǒng)的一個模塊中集成手寫簽名的功能,一開始對這塊不是很了解,只是了解他能夠替代鼠標(biāo)進行簽名。既然是簽名,一般就是需要記錄手稿圖片,作為一個記錄核實的憑證,因為有效的簽名是很難模擬的。市場上也存在很多類型的電子簽名筆,一時間還真不知道那種適合。
WinForm應(yīng)用的界面開發(fā)有個很好用的界面控件推薦——DevExpress WinForm,擁有180+組件和UI庫,能為Windows Forms平臺創(chuàng)建具有影響力的業(yè)務(wù)解決方案,點擊下方按鈕鏈接可直接獲取產(chǎn)品體驗!
DevExpress WinForms Subscription官方最新版免費下載試用,歷史版本下載,在線文檔和幫助文件下載-慧都網(wǎng)
電子手寫簽名,其實就是模擬真實的筆進行簽名的過程,我這里主要是介紹使用外部設(shè)備來記錄手稿圖片的需求,這樣其實就是類似于把我們真實在紙張上的簽名內(nèi)容,放到了電腦記錄面板上進行操作了,而這個操作模擬,其實就是利用了類似鼠標(biāo)功能的接觸筆來實現(xiàn)的。
有些筆是在紙上或者電腦屏幕上進行模擬簽名,通過一個接收器方式接受筆的接觸信號,一般要先設(shè)定紙張或者屏幕的范圍,然后進行簽名書寫。
由于簽名筆淘寶上也有很多,開始淘到的就是類似這種,不過效果不理想,好像總是定位不準(zhǔn),而且和鼠標(biāo)發(fā)生嚴(yán)重沖突,基本上操作不了,商家客服說很少有這種現(xiàn)象發(fā)生,但是卻發(fā)生在我身上,于是只有退貨。然后淘到的是一款漢王手寫板筆,開始用的還可以,其實就是代替了鼠標(biāo)進行操作,試過可以后,就擱置起來。
一直用鼠標(biāo)模擬簽名進行開發(fā),寫該篇隨筆的時候,本來想用來展示下效果,可惜又用不了,不知道什么原因。
做簽名功能開發(fā)的時候,其實我是不關(guān)注手寫筆功能的,因為我想其實如果鼠標(biāo)能操作就可以,手寫筆其實也就應(yīng)該可以操作。因此只需要在輸入的地方記錄鼠標(biāo)操作的痕跡,類似手寫簽名的效果即可,大概如下所示。
從上圖可以看到,只需要提供一個類似繪圖的面板即可記錄鼠標(biāo)的軌跡,也就是功能有點類似Windows自帶的白板(或者繪圖板)軟件即可。
其實要模擬鼠標(biāo)簽名的效果,只需要利用功能強大的GraphicsPath對象就差不多了,剩下的就是記錄點和繪制點,設(shè)置繪圖筆的寬度和顏色等方面。下面我們看看具體的實現(xiàn)代碼吧。
首先要申明幾個必要的對象,來承載相關(guān)的信息。
//記錄直線或者曲線的對象
private System.Drawing.Drawing2D.GraphicsPath mousePath=new System.Drawing.Drawing2D.GraphicsPath();
//畫筆透明度
private int myAlpha=100;
//畫筆顏色對象
private Color myUserColor=new Color();
//畫筆寬度
private int myPenWidth=3;
//簽名的圖片對象
public Bitmap SavedBitmap;
從上圖效果圖上,我們看到,我們在其中放置了一個繪圖面板,其實就是一個PictureBox對象而已,我們只需要在PictureBox對象,記錄鼠標(biāo)的移動、鼠標(biāo)按下,以及對象刷新操作事件即可實現(xiàn)模擬簽名的效果了,如下代碼所示。
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button==System.Windows.Forms.MouseButtons.Left)
{
try
{
mousePath.AddLine(e.X, e.Y, e.X, e.Y);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
pictureBox1.Invalidate();
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button==System.Windows.Forms.MouseButtons.Left)
{
mousePath.StartFigure();
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
try
{
myUserColor=System.Drawing.Color.Blue;
myAlpha=255;
Pen CurrentPen=new Pen(Color.FromArgb(myAlpha, myUserColor), myPenWidth);
e.Graphics.DrawPath(CurrentPen, mousePath);
}
catch { }
}
保存及清空操作,其實很簡單,清空的時候,記得把繪圖面板清空,并重置路徑對象即可。保存也是記錄PictureBox對象的大小寬度,把圖片存儲到圖片對象里面,供控件使用。
具體實現(xiàn)如下所示。
private void btnClear_Click(object sender, EventArgs e)
{
pictureBox1.CreateGraphics().Clear(Color.White);
mousePath.Reset();
}
private void btnSave_Click(object sender, EventArgs e)
{
SavedBitmap=new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.DrawToBitmap(SavedBitmap, new Rectangle(0, 0, pictureBox1.Width, pictureBox1.Height));
this.DialogResult=System.Windows.Forms.DialogResult.OK;
}
上面實現(xiàn)還不能完成一個最終的簽名效果,有時候,我們需要把這些圖片放到數(shù)據(jù)庫里面,如果是把上面的操作的圖片記錄下來,發(fā)現(xiàn)很大,一般我們簽名的效果顯示,不會很大,一方面只需要辨認其筆跡即可,另外一方面也不會過于增大數(shù)據(jù)庫的存儲空間。那么我們就需要對圖片大小進行一定的處理了。其實可能在詳細信息里面,我們就這樣記錄顯示即可。那么就一定要裁剪圖片的大小。
以上的醫(yī)生簽名,我們觸發(fā)的操作就是彈出一個簽名窗體,在其中繪制簽名,確認后返回,并把圖片進行顯示在詳細信息窗體里面。
private void btnDoctorSign_Click(object sender, EventArgs e)
{
FrmSignicture dlg=new FrmSignicture();
if (dlg.ShowDialog()==System.Windows.Forms.DialogResult.OK)
{
this.picDoctor.Image=dlg.SavedBitmap;
}
}
為了安裝最終顯示的PictureBox的大小保存圖片,我們需要裁減,裁減就是重新根據(jù)圖片大小進行保存Bitmap對象,這種可以從全屏最初的幾M縮小到十幾K,方便存儲。
至于圖片對象存儲到數(shù)據(jù)庫,這個由于利用了我Winform開發(fā)框架里面的數(shù)據(jù)庫基礎(chǔ)對象,基本上不需要特別對待及處理,只需要把圖片字段的Byte數(shù)組獲得即可了。這里就不在贅述Winform開發(fā)框架的功能及相關(guān)的基類處理了。
public Bitmap SaveImage(PictureBox pictureBox1)
{
Bitmap SavedBitmap=new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.DrawToBitmap(SavedBitmap, new Rectangle(0, 0, pictureBox1.Width, pictureBox1.Height));
return SavedBitmap;
}
最后,有時候,簽名還需要在列表里面顯示,這樣方便對一些關(guān)鍵信息進行查看核對。如下圖所示:
那么對于列表中顯示圖片,我們在DevExpress界面的分頁控件中應(yīng)該如何處理呢,這估計也是DevExpress開發(fā)中很多常見問題之一?
其實也很簡單,就是在DataSourceChanged 變化的事件中改變單元格的對象屬性即可。
this.winGridViewPager1.OnRefresh +=new EventHandler(winGridViewPager1_OnRefresh);
this.winGridViewPager1.ShowLineNumber=true;
this.winGridViewPager1.gridView1.Appearance.Row.TextOptions.HAlignment=DevExpress.Utils.HorzAlignment.Center;
this.winGridViewPager1.gridView1.Appearance.HeaderPanel.TextOptions.HAlignment=DevExpress.Utils.HorzAlignment.Center;
this.winGridViewPager1.AppendedMenu=this.contextMenuStrip1;
this.winGridViewPager1.gridView1.DataSourceChanged +=new EventHandler(gridView1_DataSourceChanged);
this.winGridViewPager1.gridView1.RowHeight=44;
void gridView1_DataSourceChanged(object sender, EventArgs e)
{
RepositoryItemPictureEdit pic1=new RepositoryItemPictureEdit();
pic1.SizeMode=DevExpress.XtraEditors.Controls.PictureSizeMode.Zoom;
pic1.NullText=" ";
pic1.CustomHeight=44;
pic1.BestFitWidth=100;
this.winGridViewPager1.gridView1.Columns["Signature"].ColumnEdit=picFlight;
this.winGridViewPager1.gridView1.Columns["Signature"].MaxWidth=100;
this.winGridViewPager1.gridView1.Columns["Signature"].MinWidth=100;
RepositoryItemPictureEdit picDoctor=new RepositoryItemPictureEdit();
picDoctor.SizeMode=DevExpress.XtraEditors.Controls.PictureSizeMode.Zoom;
picDoctor.NullText=" ";
picDoctor.CustomHeight=44;
picDoctor.BestFitWidth=100;
this.winGridViewPager1.gridView1.Columns["DoctorSignature"].ColumnEdit=picDoctor;
this.winGridViewPager1.gridView1.Columns["DoctorSignature"].MaxWidth=100;
this.winGridViewPager1.gridView1.Columns["DoctorSignature"].MinWidth=100;
}
以上就基本上解決了,簽名,以及圖片保存,以及列表顯示圖片效果的問題了。
本文轉(zhuǎn)載自:博客園 - 伍華聰
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。