融合Zxing和Zbar庫,對條形碼二維碼高兼容,掃碼效率奇高!
開發背景
前段采用Zxing和Zbar掃碼庫掃碼時,發現針對二維碼和條形碼,兩個庫有不同的效果,Zxing能高效精準識別二維碼,但在條形碼上卻不盡人意,在某些手機死活識別不出來;而后采用Zbar庫,在條形碼上效果杠杠的,而二維碼相對Zxing就遜色很多了。根據這些情況,我就覺得有必要搞個'萬金油',把兩者優勢融合起來,于是結合Zxing二維碼算法和Zbar條形碼算法的QrCodeScanner掃碼工具就憑空而出。
開發工作
比對了Zxing和Zbar兩個掃碼Demo,Zxing的相機掃描部分算是做得比較精良,Zbar就不想說了,最后決定采用Zxing掃描部分的代碼,但是也有很多地方與需求不符,所以結合網上資料和自己的分析,修改了其中的代碼。然后整個識別算法代碼是放在C層的。下面就將幾個核心部分列舉出來。
1.調整掃描采樣區域,優化取圖速度
在CameraManager類中不改變掃碼框大小,但是增加采樣區域大小,我這里是根據屏幕寬度作為邊長,截取中間正方形為取圖區域。
public Rect getRealFramingRect() {
if (realFramingRect == null) {
//獲取屏幕大小,然后根據屏幕寬度由中間截取于寬度等長的正方形
Point screenResolution = configManager.getScreenResolution();
int leftOffset = 0;
int topOffset = (screenResolution.y - screenResolution.x) / 2;
Rect rect = new Rect(leftOffset, topOffset, screenResolution.x,
screenResolution.x+topOffset);
//根據圖片分辨率和屏幕分辨率截取實際大小的圖片區域
Point cameraResolution = configManager.getCameraResolution();
rect.left = rect.left * cameraResolution.y / screenResolution.x;
rect.right = rect.right * cameraResolution.y / screenResolution.x;
rect.top = rect.top * cameraResolution.x / screenResolution.y;
rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
realFramingRect = rect;
}
return realFramingRect;
}
2.獲取適配的攝像頭預覽圖片,防止圖片拉伸
在CameraConfigurationManager類中獲取攝像頭所有預覽尺寸,根據屏幕分辨率選取最適合的預覽尺寸。
private static Point findBestPreviewSizeValue(
CharSequence previewSizeValueString, Point screenResolution) {
int bestX = 0;
int bestY = 0;
int diff = Integer.MAX_VALUE;
//previewSizeValueString為包含所有預覽尺寸的字符串
for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {
previewSize = previewSize.trim();
int dimPosition = previewSize.indexOf('x');
if (dimPosition < 0) continue;
try {
int newX = Integer.parseInt(previewSize.substring(0, dimPosition));
int newY = Integer.parseInt(previewSize.substring(dimPosition + 1));
int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y);
if (newDiff == 0) {
bestX = newX;bestY = newY;
break;
} else if (newDiff < diff) {
bestX = newX;bestY = newY;diff = newDiff;
}
} catch (NumberFormatException nfe) {
continue;
}
}
if (bestX > 0 && bestY > 0) {
return new Point(bestX, bestY);
}
return null;
}
3.旋正攝像頭獲取的YUV圖片數據,供識別庫識別
攝像頭獲取的圖片都是橫屏的,所以對圖片識別前,我們必須把圖片旋正過來,否則就無法正確識別。這個的圖片是YUV格式數據,其中"Y"表示明亮度(Lumina nce或Luma),也就是灰階值;而"U"和"V"表示的則是色度(Chrominance或Chroma),而在識別時,只取Y部分的數據就可以了,相當于把圖片灰度化。而為了提高計算效率,我把這部分代碼放在C層中實現了。
char* buffer = (char*) env->GetByteArrayElements(data, JNI_FALSE);
char*rotateData = new char[dataWidth * dataHeight];
for(int y = 0; y < dataHeight; y++) {
for (int x = 0; x < dataWidth; x++) {
rotateData[x * dataHeight + dataHeight - y - 1] = buffer[x + y * dataWidth];
}
}
int tmp = dataWidth;
dataWidth = dataHeight;
dataHeight = tmp;
4.融合ZBar和Zxing庫,整個算法部分用NDK實現。
同樣是為了提高運算效率,我把兩部分的算法代碼都放在C層實現了,Zbar采用官方的C庫,Zxing采用C++庫,去掉兩個庫多余的代碼,精簡代碼結構,封裝公共部分代碼,從而實現識別運算上的優化。
關于整個項目的優化點還有好多,這里就不一一列舉了,這里已經將全部代碼放上去了,可以直接通過代碼來查看。
使用說明
1.使用Android Studio開發,配置Cmake環境
這里通過Cmake來編譯Native部分代碼,所以編譯時需要配置Cmake工具。如果不修改jni接口(DecodeEntry.cpp),也可以直接使用本項目提供的.so庫,把extraLib文件夾中的.so文件復制到項目lib目錄下即可。
2.修改識別模式
目前識別模式有三種,只識別二維碼,只識別條形碼,識別二維碼加條形碼(默認)??梢愿鶕约盒枨筮x擇不同模式,有利于提高識別效率。通過BarcodeFormat類進行修改。
barcodeFormat = new BarcodeFormat();
barcodeFormat.add(BarcodeFormat.BARCODE);
barcodeFormat.add(BarcodeFormat.QRCODE);
然后作為參數傳進CaptureActivityHandler對象中。
handler = new CaptureActivityHandler(this, barcodeFormat);
源碼地址:
https://github.com/heiBin/QrCodeScanner
Demo地址:
https://github.com/heiBin/QrCodeScanner/raw/master/QrCodeScanner.apk
(操作補充說明):
直接使用編譯好的.so文件:
如上圖所示,在app-main下加入jniLibs目錄,把extraLib下編譯好的.so文件全部復制過來;然后如上圖把build.gradle下的cmake相關代碼注釋掉。