教你開啟二維碼和條形碼的正確識別方式
今天老師給了張圖片,裡面有很多二維碼和條形碼,然後說不管大家用什麼辦法,試試看能不能用個程式碼儘可能多的把裡面的二維碼和條形碼資訊讀出來!腦海突然閃現出之前做過的微信自動回覆過程中自動生成的二維碼。但似乎沒太大關係(略略略),二維碼不也是一張圖片嘛,那就影象識別?通過解析來提取裡面的資訊??那啥今天去超市買快樂肥宅茶的時候,小姐姐滴一下掃了瓶子上的條形碼電腦上就獲取到了快樂肥宅茶的相關資訊(編號,價格....),不也正是讀取了條形碼的資訊嘛。
首先,我是這樣想的:圖片其實就是一個二維陣列,又無數個畫素點構造,那麼可以把每個畫素點都讀取出來,儲存到一個檔案當中,然後通過各種演算法把它們解析成檔案(這個過程就是把一堆0101位元組解析出我們能看懂的字元數字等,就好像是把一種一看不懂的語言翻譯成中文),然後就能獲得二維碼或條形碼上面的資訊了。重點是在解析的過程中,該如何解析呢?最快的辦法,當然是趕快去看看哪個庫可以用?
以下是解析程式碼,呼叫的是java中的QRCodeDecoder.jar介面
然後獲取圖片大小和畫素點,必要時還可以將圖片放大。
import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import jp.sourceforge.qrcode.QRCodeDecoder; import jp.sourceforge.qrcode.data.QRCodeImage; import jp.sourceforge.qrcode.exception.DecodingFailedException; public class ErWeiMaJieMa { /** * 解碼二維碼 * @param imgPath * @return String */ public String decoderQRCode(String imgPath) { // QRCode 二維碼圖片的檔案 File imageFile = new File(imgPath); BufferedImage bufImg = null; String decodedData = null; try { bufImg = ImageIO.read(imageFile); QRCodeDecoder decoder = new QRCodeDecoder(); decodedData = new String(decoder.decode(new J2SEImage(bufImg))); // try { // System.out.println(new String(decodedData.getBytes("gb2312"), // "gb2312")); // } catch (Exception e) { // // TODO: handle exception // } } catch (IOException e) { System.out.println("Error: " + e.getMessage()); e.printStackTrace(); } catch (DecodingFailedException dfe) { System.out.println("Error: " + dfe.getMessage()); dfe.printStackTrace(); } return decodedData; } /** * @param args the command line arguments */ public static void main(String[] args) { ErWeiMaJieMa handler = new ErWeiMaJieMa(); String imgPath = "F:\\Desktop\\2.png"; String decoderContent = handler.decoderQRCode(imgPath); System.out.println("解析結果如下:"); System.out.println(decoderContent); System.out.println("========decoder success!!!"); } class J2SEImage implements QRCodeImage { BufferedImage bufImg; public J2SEImage(BufferedImage bufImg) { this.bufImg = bufImg; } //獲取寬 public int getWidth() { return bufImg.getWidth(); } //獲取高 public int getHeight() { return bufImg.getHeight(); } //獲取畫素點 public int getPixel(int x, int y) { return bufImg.getRGB(x, y); } } }
圖片:
我只截取了裡面的一個二維碼來解析,跑出來的結果:
其實可以開啟QRCodeDecoder類中的decode()方法看,發現它也是把圖片轉成點來解析,最後返回一個byte型別的陣列。
public byte[] decode(QRCodeImage qrCodeImage) throws DecodingFailedException { //獲取調整的點 Point[] adjusts = getAdjustPoints(); Vector results = new Vector(); numTryDecode = 0; while (numTryDecode < adjusts.length) { try { DecodeResult result = decode(qrCodeImage, adjusts[numTryDecode]); if (result.isCorrectionSucceeded()) { return result.getDecodedBytes(); } else { results.addElement(result); canvas.println("Decoding succeeded but could not correct"); canvas.println("all errors. Retrying.."); } } catch (DecodingFailedException dfe) { if (dfe.getMessage().indexOf("Finder Pattern") >= 0) throw dfe; } finally { numTryDecode += 1; } } if (results.size() == 0) throw new DecodingFailedException("Give up decoding"); int minErrorIndex = -1; int minError = Integer.MAX_VALUE; for (int i = 0; i < results.size(); i++) { DecodeResult result = (DecodeResult)results.elementAt(i); if (result.getNumCorrectuionFailures() < minError) { minError = result.getNumCorrectuionFailures(); minErrorIndex = i; } } canvas.println("All trials need for correct error"); canvas.println("Reporting #" + (minErrorIndex)+" that,"); canvas.println("corrected minimum errors (" +minError + ")"); canvas.println("Decoding finished."); return ((DecodeResult)results.elementAt(minErrorIndex)).getDecodedBytes(); } Point[] getAdjustPoints() { // note that adjusts affect dependently // i.e. below means (0,0), (2,3), (3,4), (1,2), (2,1), (1,1), (-1,-1) // Point[] adjusts = {new Point(0,0), new Point(2,3), new Point(1,1), // new Point(-2,-2), new Point(1,-1), new Point(-1,0), new Point(-2,-2)}; Vector adjustPoints = new Vector(); for (int d = 0; d < 4; d++) adjustPoints.addElement(new Point(1, 1)); int lastX = 0, lastY = 0; for (int y = 0; y > -4; y--) { for (int x = 0; x > -4; x--) { if (x != y && ((x+y) % 2 == 0)) { adjustPoints.addElement(new Point(x-lastX, y-lastY)); lastX = x; lastY = y; } } } Point[] adjusts = new Point[adjustPoints.size()]; for (int i = 0; i < adjusts.length; i++) adjusts[i] = (Point)adjustPoints.elementAt(i); return adjusts; } DecodeResult decode(QRCodeImage qrCodeImage, Point adjust) throws DecodingFailedException { try { if (numTryDecode == 0) { canvas.println("Decoding started"); //將圖片轉為二維陣列 int[][] intImage = imageToIntArray(qrCodeImage); //讀取圖片 imageReader = new QRCodeImageReader(); //獲取二維碼特徵 qrCodeSymbol = imageReader.getQRCodeSymbol(intImage); } else { canvas.println("--"); canvas.println("Decoding restarted #" + (numTryDecode)); qrCodeSymbol = imageReader.getQRCodeSymbolWithAdjustedGrid(adjust); } } catch (SymbolNotFoundException e) { throw new DecodingFailedException(e.getMessage()); } canvas.println("Created QRCode symbol."); canvas.println("Reading symbol."); canvas.println("Version: " + qrCodeSymbol.getVersionReference()); canvas.println("Mask pattern: " + qrCodeSymbol.getMaskPatternRefererAsString()); // blocks contains all (data and RS) blocks in QR Code symbol int[] blocks = qrCodeSymbol.getBlocks(); canvas.println("Correcting data errors."); // now blocks turn to data blocks (corrected and extracted from original blocks) blocks = correctDataBlocks(blocks); try { byte[] decodedByteArray = getDecodedByteArray(blocks, qrCodeSymbol.getVersion(), qrCodeSymbol.getNumErrorCollectionCode()); return new DecodeResult(decodedByteArray, numLastCorrectionFailures); } catch (InvalidDataBlockException e) { canvas.println(e.getMessage()); throw new DecodingFailedException(e.getMessage()); } }
將圖片轉為二維陣列的函式:
int[][] imageToIntArray(QRCodeImage image) {
int width = image.getWidth();
int height = image.getHeight();
int[][] intImage = new int[width][height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
intImage[x][y] = image.getPixel(x,y);
}
}
return intImage;
}
獲取二維碼特徵的函式:大致思路是通過與樣式校準來得到特徵的。
public QRCodeSymbol getQRCodeSymbol(int[][] image)
throws SymbolNotFoundException {
int longSide = (image.length < image[0].length) ? image[0].length : image.length;
QRCodeImageReader.DECIMAL_POINT = 23 - QRCodeUtility.sqrt(longSide / 256);
bitmap = filterImage(image);
canvas.println("Drawing matrix.");
canvas.drawMatrix(bitmap);
canvas.println("Scanning Finder Pattern.");
FinderPattern finderPattern = null;
try {
finderPattern = FinderPattern.findFinderPattern(bitmap);
} catch (FinderPatternNotFoundException e) {
canvas.println("Not found, now retrying...");
bitmap = applyCrossMaskingMedianFilter(bitmap, 5);
canvas.drawMatrix(bitmap);
try {
finderPattern = FinderPattern.findFinderPattern(bitmap);
} catch (FinderPatternNotFoundException e2) {
throw new SymbolNotFoundException(e2.getMessage());
} catch (VersionInformationException e2) {
throw new SymbolNotFoundException(e2.getMessage());
}
} catch (VersionInformationException e) {
throw new SymbolNotFoundException(e.getMessage());
}
canvas.println("FinderPattern at");
String finderPatternCoordinates =
finderPattern.getCenter(FinderPattern.UL).toString() +
finderPattern.getCenter(FinderPattern.UR).toString() +
finderPattern.getCenter(FinderPattern.DL).toString();
canvas.println(finderPatternCoordinates);
int[] sincos = finderPattern.getAngle();
canvas.println("Angle*4098: Sin " + Integer.toString(sincos[0]) + " " + "Cos " + Integer.toString(sincos[1]));
int version = finderPattern.getVersion();
canvas.println("Version: " + Integer.toString(version));
if (version < 1 || version > 40)
throw new InvalidVersionException("Invalid version: " + version);
AlignmentPattern alignmentPattern = null;
try {
alignmentPattern = AlignmentPattern.findAlignmentPattern(bitmap, finderPattern);
} catch (AlignmentPatternNotFoundException e) {
throw new SymbolNotFoundException(e.getMessage());
}
int matrixLength = alignmentPattern.getCenter().length;
canvas.println("AlignmentPatterns at");
for (int y = 0; y < matrixLength; y++) {
String alignmentPatternCoordinates = "";
for (int x = 0; x < matrixLength; x++) {
alignmentPatternCoordinates += alignmentPattern.getCenter()[x][y].toString();
}
canvas.println(alignmentPatternCoordinates);
}
//for(int i = 0; i < 500000; i++) System.out.println("");
canvas.println("Creating sampling grid.");
//[TODO] need all-purpose method
//samplingGrid = getSamplingGrid2_6(finderPattern, alignmentPattern);
samplingGrid = getSamplingGrid(finderPattern, alignmentPattern);
canvas.println("Reading grid.");
boolean[][] qRCodeMatrix = null;
try {
qRCodeMatrix = getQRCodeMatrix(bitmap, samplingGrid);
} catch (ArrayIndexOutOfBoundsException e) {
throw new SymbolNotFoundException("Sampling grid exceeded image boundary");
}
//canvas.drawMatrix(qRCodeMatrix);
return new QRCodeSymbol(qRCodeMatrix);
}
我們只需要呼叫幾個函式就能搞定的事情,其實是因為底層幫我們實現了很多的細節與處理過程。看似簡單的影象識別與人臉識別應用程式,其實如果只是單純的獲取每個畫素點來處理和識別,那麼上億的圖片集是很難處理的。所以對於每一張圖片的識別都需要計算得到特徵值。
特徵值分解可以得到特徵值與特徵向量,特徵值表示的是這個特徵到底有多重要,而特徵向量表示這個特徵是什麼。不過,特徵值分解也有很多的侷限,比如說變換的矩陣必須是方陣。這時就要奇異值分解上場才能搞定了。
給你一副影象,要從影象庫中得到匹配的影象,怎麼弄?如果是兩兩做畫素點比較是不可能完成的任務,耗時好空間。如果用其他特徵點代替也許可以,但容易漏檢吧。我們必須對影象資料的協方差矩陣進行降維,所以用到了PCA。
主成分分析(PrincipalComponents Analysis。即PCA,也稱為K-L變換),是影象壓縮中的一種最優正交變換。PCA用於統計特徵提取構成了子空間法模式識別的基礎。它從影象整體代數特徵出發,基於影象的總體資訊進行分類識別。PCA的核心思想是利用較少數量的特徵對樣本進行描述以達到降低特徵空間維數的目的。(思考:較少數量的特徵來描述會不會讓識別不準確?其實是會的,所以這裡也需要一定的權衡。)
而具體如何實現PCA呢?關鍵是特徵值及相應特徵向量的求取。matlab有個eig函式,opencv也有相應的函式,QR演算法也可以用來求實對稱矩陣的全部特徵值和特徵向量。
更多關於奇異值分解、PCA理論和OR演算法實現可以參考下面文章: