好好學習,天天向上
本文已收錄至我的Github倉庫DayDayUP:github.com/RobodLee/DayDayUP,歡迎Star
前言
我這幾天在做一個東西,就是一張像二維碼這樣的 n*n 的只有兩種顏色的點陣圖,識別出哪個方塊是深色的,哪個方塊是淺色的。就像下面這張圖👇
我一開始想的是,既然是圖像識別,那不是OpenCV嘛。但是我不會呀,所以就開始研究,發現Android要使用OpenCV還涉及到JNI,NDK。算了算了好復雜,還是直接在網上找現成的輪子吧。找了一圈后,發現沒有找到...既然沒有輪子,那就自己造💪
然后想來想去,發現被自己蠢哭了,這個貌似不需要用到OpenCV吧,是我把問題想復雜了。然后開始第二種方案:比如這張圖是 24*24 的,那我就把這樣圖縮小到24px * 24px,那不就正好一個像素對應一個格子嘛,哈哈哈✌。但最后還是淘汰了這個方案,因為我最終要實現的是拍照再裁切成肉眼的1:1的照片進行識別,因為肉眼裁切或者拍照的時候手機歪了一點點,很難做到正好1:1。這樣的話壓縮到一個格子對應一個像素這樣高的精度很大可能性會造成誤差。
然后方案就進化到了第三代,不去進行壓縮了,直接計算出每個格子中心像素的坐標,然后判斷色值就可以得到這個格子是什么顏色的了。因為不壓縮的話每個格子就會占很多個像素,就算圖片不是正好的1:1,也不太會出現誤差。
正文
廢話了一大堆,介紹完方案就來說一下具體實現吧。首先需要明確一個問題:如何去計算某個格子的中心像素的位置,用一個公式就可以得出:
(2 * n+1) * length /(2 * num)
簡單解釋一下:當知道一條邊的長度和格子數后就可以計算出每個格子占多少個像素 length/num,求某一個格子的中心坐標,比如第10個格子,因為從0開始的。所以就是 (2*(10-1)+1) 個半個格子的長度。
先灰度圖片,目的是減少誤差,然后將Bitmap圖片用一個二維數組表示,二維數組的每個元素的值就是對應圖片中點的色值。然后通過計算每個方格中心點的坐標,將值從二維數組中取出,放入存放每個方格中心點色值的數組newPx中。最后再遍歷一遍newPx,當大於某個值就說明是該方塊是白色的,小於某個值則說明該方塊是黑色的。那么這個某個值是多少呢,就是所有方格中心像素色值最大值與最小值的平均值。
原理介紹完了,完整代碼如下:
/**
* 展示圖片
* @param imagePath 圖片的路徑
*/
private void displayImage(String imagePath) {
if (imagePath != null) {
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
Bitmap greyBitmap = convertGreyImg(bitmap);
String pixels = getBitString(greyBitmap, gridNum);
picturePixels.setText(pixels);
} else {
Toast.makeText(this, "failed to get image", Toast.LENGTH_SHORT).show();
}
}
/**
* 獲取轉化后的表示方格顏色的字符串,黑色用 ● 表示,白色用 ○ 表示
* @param img
* @param num 方格數,總方格數為num * num
* @return
*/
private String getBitString(Bitmap img, int num) {
int width = img.getWidth();//原圖像寬度
int height = img.getHeight();//原圖像高度
int[] oldPx = new int[width * height];//用來存儲原圖每個像素點的顏色信息
int[] newPx = new int[num * num]; //用來存放每個方格中心點像素顏色值
int index = 0;
img.getPixels(oldPx, 0, width, 0, 0, width, height);//獲取原圖中的像素信息
int[][] oldPxTwo = twoArray(oldPx,width,height); //原圖每個像素點顏色信息的二維數組
int minValue = Integer.MAX_VALUE;
int maxValue = Integer.MIN_VALUE;
//循環
for (int i = 0; i < num; i++) {
int row = getCoordinate(i,width,num);
for (int j = 0; j < num; j++) {
int col = getCoordinate(j,height,num);
minValue = Math.min(minValue,oldPxTwo[row][col]);
maxValue = Math.max(maxValue,oldPxTwo[row][col]);
newPx[index++] = oldPxTwo[row][col];
}
}
StringBuilder pixels = new StringBuilder();
int middleValue = (minValue + maxValue)/2;
for (int i = 0; i < newPx.length; i++) {
if (i>0 && i%num==0) pixels.append("\n");
if (newPx[i] > middleValue) {
pixels.append("○");
} else {
pixels.append("●");
}
}
return pixels.toString();
}
/**
* 獲取方塊中心點 橫/縱 坐標
* @param n 第幾個方塊,從0開始
* @param length 橫向或者縱向的長度
* @param num 橫向或者縱向方塊數
* @return 坐標值
*/
private int getCoordinate(int n,int length,int num) {
return (2*n+1)*length /(2*num);
}
/**
* 一維數組轉化為二維數組
* @param arr
* @param width 縱向的方塊數,多少行
* @param height
* @return
*/
public int[][] twoArray(int[] arr,int width,int height) {
int[][] result = new int[height][width];
int k = 0;
for (int i = 0;i<height;i++) {
for (int j = 0;j<width;j++) {
result[i][j] = arr[k++];
}
}
return result;
}
/**
* 將彩色圖轉換為灰度圖
* @param img 位圖
* @return 返回轉換好的位圖
*/
public Bitmap convertGreyImg(Bitmap img) {
int width = img.getWidth(); //獲取位圖的寬
int height = img.getHeight(); //獲取位圖的高
int[] pixels = new int[width * height]; //通過位圖的大小創建像素點數組
img.getPixels(pixels, 0, width, 0, 0, width, height);
int alpha = 0xFF << 24;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int grey = pixels[width * i + j];
int red = ((grey & 0x00FF0000) >> 16);
int green = ((grey & 0x0000FF00) >> 8);
int blue = (grey & 0x000000FF);
grey = (int) ((float) red * 0.3 + (float) green * 0.59 + (float) blue * 0.11);
grey = alpha | (grey << 16) | (grey << 8) | grey;
pixels[width * i + j] = grey;
}
}
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
result.setPixels(pixels, 0, width, 0, 0, width, height);
return result;
}
這個轉化為灰度圖的方法是我在網上找的。
展示一下效果:
總結
好了,到這里就介紹完了,聽我介紹完是不是覺得挺簡單的。因為本篇文章主要是講如何講如何識別點陣圖,所以調用相冊等內容就沒有說,其實我這個Demo調用相冊的代碼也是直接從《第一行代碼》中拿來用的,不是自己寫的。代碼還可以再優化一下的,比如一位數組轉二維數組可以和灰度圖片一起實現,不過這些就留給小伙伴們自己去實現啦!
都看完了,來個 "
贊
" "贊
" "贊
" 鼓勵一下我唄...本文已收錄至我的Github倉庫DayDayUP:github.com/RobodLee/DayDayUP,歡迎Star