Android 图片相识度比较(pHash)-代码分享:

test_img_diff.xml 布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/rlRoot">
    <ListView android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

ListView 的item 布局: item_img_diff.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView android:id="@+id/ivLeft"
        android:layout_width="128dp"
        android:layout_height="72dp"/>

    <ImageView android:id="@+id/ivRight"
        android:layout_width="128dp"
        android:layout_height="72dp"
        android:layout_alignParentRight="true"/>

    <TextView android:id="@+id/tvRes"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textSize="18sp"
        android:textColor="#FFFFFFFF"/>
</RelativeLayout>

主界面Activity: ImgDiffTester.java

public class ImgDiffTester extends Activity implements View.OnClickListener {
	final String TAG = "ImgDiffTester";
	ListView lv;
	ImgListAdapter adapter;
	@Override
	protected void onCreate(@Nullable Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.test_img_diff);
		findViewById(R.id.rlRoot).setOnClickListener(this);
		lv = (ListView) findViewById(R.id.lv);
		adapter = new ImgListAdapter();
		lv.setAdapter(adapter);
		startCompare();
	}

	void startCompare(){
		new Thread(){
			@Override
			public void run() {
				File[] fs = new File("/sdcard/Download/").listFiles(new FileFilter() {
					@Override
					public boolean accept(File file) {
						return file.getName().endsWith(".png");
					}
				});
				for(File f : fs){
					Bitmap bm = BitmapFactory.decodeFile(f.getAbsolutePath());
					compareBitmapAndShow(bm);
				}

				lv.post(new Runnable() {
					@Override
					public void run() {
						adapter.notifyDataSetChanged();
					}
				});
			}
		}.start();
	}
	void compareBitmapAndShow(Bitmap bm){
		if(bm != null && bm.getWidth() > 0 && bm.getHeight() > 0) {

			final Bitmap bm1 = BitmapUtils.clipBitmapWidthBounds(bm, new Rect(0, 0, bm.getWidth() / 2, bm.getHeight()));
			//bm1 = BitmapFactory.decodeFile("/sdcard/l.png");
			final Bitmap bm2 = BitmapUtils.clipBitmapWidthBounds(bm, new Rect(bm.getWidth() / 2, 0, bm.getWidth(), bm.getHeight()));
			//bm2 = BitmapFactory.decodeFile("/sdcard/r.png");
			try {
				Bitmap[] scaled = new Bitmap[2];
				//scaled[0] = Bitmap.createBitmap(pHash.DCT_LENGTH, pHash.DCT_LENGTH, Bitmap.Config.ARGB_8888);
				//scaled[1] = Bitmap.createBitmap(pHash.DCT_LENGTH, pHash.DCT_LENGTH, Bitmap.Config.ARGB_8888);
				//int cmp = pHash.compareBitmap(bm1, bm2, scaled, false);
				long st = SystemClock.uptimeMillis();
				final int cmp = ImagePHash.compareBitmap(bm1, bm2);
				long et = SystemClock.uptimeMillis();
				Log.d(TAG, "compare " + cmp + " spend " + (et - st) + " ms");
				Item item = new Item();
				item.l = bm1;
				item.r = bm2;
				item.res = "Result: " + cmp + ", spend " + (et - st) + " ms";
				adapter.items.add(item);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}
	public static class ImagePHash {

		// 默认使用 32x32 大小
		private static final int SIZE = 32;
		// DCT 截取的大小(例如 8x8)
		private static final int SMALLER_SIZE = 8;

		public static int compareBitmap(Bitmap bm1, Bitmap bm2){
			String h1 = getHash(bm1);
			String h2 = getHash(bm2);
			return hammingDistance(h1, h2);
		}
		@SuppressLint("NewApi")
		public static String getHash(Bitmap img) {
			long st = SystemClock.uptimeMillis();
			// 1. 转换为灰度图像
			Bitmap grayImg = toGrayscale(img);

			// 2. 缩小图片
			Bitmap smallImg = Bitmap.createScaledBitmap(grayImg, SIZE, SIZE, false);

			// 3. 转换为二维数组
			double[][] vals = new double[SIZE][SIZE];
			for (int x = 0; x < SIZE; x++) {
				for (int y = 0; y < SIZE; y++) {
					vals[x][y] = Color.red(smallImg.getPixel(x, y));
				}
			}

			long ct1 = SystemClock.uptimeMillis();
			// 4. 对图像执行离散余弦变换(DCT)
			double[][] dctVals = applyDCT(vals);
			long ct2 = SystemClock.uptimeMillis();

			// 5. 截取 DCT 左上角的 8x8 部分
			double[] dctLowFreq = new double[SMALLER_SIZE * SMALLER_SIZE];
			for (int x = 0; x < SMALLER_SIZE; x++) {
				for (int y = 0; y < SMALLER_SIZE; y++) {
					dctLowFreq[x * SMALLER_SIZE + y] = dctVals[x][y];
				}
			}

			// 6. 计算均值
			double avg = Arrays.stream(dctLowFreq).average().orElse(0.0);


			long ct3 = SystemClock.uptimeMillis();

			// 7. 生成哈希值
			StringBuilder hash = new StringBuilder();
			for (double value : dctLowFreq) {
				hash.append(value > avg ? "1" : "0");
			}
			Log.d("ImgDiff", (ct1 - st) + ", " + (ct2 - ct1));
			return hash.toString();
		}

		// 转换为灰度图像
		private static Bitmap toGrayscale(Bitmap img) {
			int width = img.getWidth();
			int height = img.getHeight();
			Bitmap grayscaleImg = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

			for (int y = 0; y < height; y++) {
				for (int x = 0; x < width; x++) {
					int pixel = img.getPixel(x, y);
					int red = Color.red(pixel);
					int green = Color.green(pixel);
					int blue = Color.blue(pixel);
					int gray = (red + green + blue) / 3;
					int newPixel = Color.rgb(gray, gray, gray);
					grayscaleImg.setPixel(x, y, newPixel);
				}
			}
			return grayscaleImg;
		}

		// 执行离散余弦变换(DCT)
		private static double[][] applyDCT(double[][] f) {
			int N = f.length;
			double[][] F = new double[N][N];
			for (int u = 0; u < N; u++) {
				for (int v = 0; v < N; v++) {
					double sum = 0.0;
					for (int i = 0; i < N; i++) {
						for (int j = 0; j < N; j++) {
							sum += f[i][j] *
									Math.cos((2 * i + 1) * u * Math.PI / (2.0 * N)) *
									Math.cos((2 * j + 1) * v * Math.PI / (2.0 * N));
						}
					}
					double alphaU = (u == 0) ? Math.sqrt(1.0 / N) : Math.sqrt(2.0 / N);
					double alphaV = (v == 0) ? Math.sqrt(1.0 / N) : Math.sqrt(2.0 / N);
					F[u][v] = alphaU * alphaV * sum;
				}
			}
			return F;
		}

		// 比较两个哈希值,返回汉明距离(不同位数的个数)
		public static int hammingDistance(String hash1, String hash2) {
			int distance = 0;
			for (int i = 0; i < hash1.length(); i++) {
				if (hash1.charAt(i) != hash2.charAt(i)) {
					distance++;
				}
			}
			return distance;
		}
	}

	class ImgListAdapter extends BaseAdapter{
		ArrayList<Item> items = new ArrayList<>();
		@Override
		public int getCount() {
			return items.size();
		}

		@Override
		public Object getItem(int i) {
			return items.get(i);
		}

		@Override
		public long getItemId(int i) {
			return i;
		}

		@Override
		public View getView(int pos, View view, ViewGroup viewGroup) {
			if(view == null){
				view = getLayoutInflater().inflate(R.layout.item_img_diff, null, false);
			}
			((ImageView)view.findViewById(R.id.ivLeft)).setImageBitmap(items.get(pos).l);
			((ImageView)view.findViewById(R.id.ivRight)).setImageBitmap(items.get(pos).r);
			((TextView)view.findViewById(R.id.tvRes)).setText(items.get(pos).res);
			return view;
		}
	}
	class Item{
		Bitmap l, r;
		String res;
	}
}

温馨提示
本文算法及用例仅供参考, 未经大量测试验证
请谨慎阅读参考

上一篇:【Java】使用iText依赖生成PDF文件


下一篇:EureKa是什么?