PicQuery: natural language search over local photos on Android
🔍 Search local images with natural language on Android, powered by OpenAI's CLIP model. / 在 Android 上用自然语言搜索本地图片 (基于 OpenAI 的 CLIP 模型)
At a glance
- What is it?
- PicQuery is an Android app that builds an on-device CLIP index of your photo library so you can search it with English or Chinese descriptions, or with an image. The current branch ships two inference backends, ONNX Runtime and TFLite, as separate flavors with separate indexes.
- Who is it for?
- Adopt PicQuery if you want an offline, MIT-licensed Android app for text and image search over a chosen album set, and you are willing to build from this branch and place the MobileCLIP2 assets by hand. Do not adopt it if you need a verified release build, GPU or NPU acceleration, or measured quality on free-form personal galleries, since the repository states those remain unverified.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What PicQuery does that a gallery app does not
Android's media store indexes filenames, dates, locations and whatever labels an on-device classifier attaches. It does not answer "dog" against a folder of untagged photos, and it cannot take one photo and find visually similar ones. PicQuery builds a separate index of CLIP embeddings for the albums you select, then matches a text query or a query image against those embeddings.
The audience is narrow but real: people who keep large local libraries, who do not want photos uploaded to a service, and who are comfortable building an Android app. The README is explicit that this branch is the one integrating Apple's MobileCLIP2-S0 / dfndr2b, and that published releases may use earlier models. So the app on Google Play is not the same configuration described here. If you want the MobileCLIP2 backends, you build them.
Two flavors, two indexes, one photo library
The branch ships two Android product flavors, onnx and tflite, under distinct application IDs (me.grey.picquery.mobileclip2.onnx and me.grey.picquery.mobileclip2.tflite). They can be installed side by side and keep separate indexes, which is the whole point: you can index the same album twice and compare how each runtime answers the same query on your own photos.
The model pairs are not identical in size. The ONNX flavor loads mobileclip2_s0_image.onnx with mobileclip2_s0_text_int8.onnx at 104.89 MiB; the TFLite flavor loads image_model.tflite with text_model_dynamic_wi8.tflite at 105.73 MiB. Both use FP32 image weights and dynamic INT8 text weights, with normalized FP32 embeddings. Threading differs too: ONNX uses 4 image and 4 text CPU threads, while TFLite runs through native XNNPACK with 4 image and 2 text threads. That asymmetry is worth noting before you conclude one backend is faster than the other from a query-feel test.
Only selected albums are encoded. Startup enumerates accessible media metadata without indexing everything, and a range filter can be narrowed later, though the README states that range resets when the app process restarts.
Building PicQuery from this branch
The README lists JDK 17, Android SDK platform 37 (platforms;android-37.0), Android NDK 29.0.14206865, CMake 3.22.1, and a device on Android 10 / API 29 or newer. Both flavors need NDK and CMake because of the native delegate bridge. Set your SDK path in local.properties or Android Studio, then use the included Gradle wrapper.
Model binaries are Git-ignored, so a fresh clone will not build a working APK until you fetch them. The README points to a prebuilt App bundle on Google Drive and to SHA-256 verification steps in script/model-MobileCLIP2/downloads/README.md. Models go under app/src/main/assets/, and you must keep the shared bpe_vocab_gz and bundled mlkit/ assets.
./gradlew :app:assembleOnnxDebug :app:assembleTfliteDebug
adb -s DEVICE_SERIAL install -r app/build/outputs/apk/onnx/debug/app-onnx-debug.apk
adb -s DEVICE_SERIAL install -r app/build/outputs/apk/tflite/debug/app-tflite-debug.apkReplace DEVICE_SERIAL with the serial shown by adb devices -l. On Windows, use .\gradlew.bat. For an x86_64 emulator, append -Pmobileclip2Abis=x86_64 to the build command. The default APKs include ARM64 and ARMv7.
First real use, per the README: create a small album such as Pictures/PicQuery-Demo before opening the app, grant photo access, tap Index then Add album, check the photo count, tap Index and wait, then Finish. Search for dog or astronaut, or use an image as the query. To compare backends, index the same album in the other flavor. If an album you just added is missing from the list, restart the app.
The benchmark numbers cover less than they appear to
The repository publishes measured results, but they answer one question: FP32 image ORT versus mixed INT8 image ORT, both with the same dynamic INT8 text model. It is not an ONNX versus TFLite comparison, and the README says so directly. On a Pixel 8a with Tensor G3, ONNX Runtime 1.29.0 and 4 CPU threads, CIFAR-100 Top1 moves from 74.60% to 73.90% and Imagenette Top1 from 98.04% to 97.96% when the image encoder is quantized. Image encoding P50 drops from 67.58 ms to 53.46 ms, and the image ORT file shrinks from 43.54 MiB to 13.52 MiB.
Read the protocol before quoting any of it. Accuracy used fixed English class prompts and phone-generated embeddings. Timing used preloaded inputs, 10 warm-ups per tower then two rounds of 100 calls, excluding photo decoding, resizing, tokenization and database search. That is not end-to-end search latency and not sustained indexing performance. The README also warns that class-label benchmarks and a small demo album do not establish quality for personal galleries, free-form descriptions, Chinese translation or OCR.
One more thing worth internalizing: ORT export and image quantization do not change the models the app selects. The current flavors keep FP32 image inference. Switching image encoders means rebuilding the photo index.
Where PicQuery is the wrong tool
The recorded device validation used Debug APKs on a Pixel 8a with 4 KB memory pages. Release and R8 builds, GPU and NPU paths, other phones, and whole-APK 16 KB page compatibility are all listed as unverified, and a compatibility notice appeared on the tested device. If you need a build you can hand to non-developers, this branch does not give you one.
Indexing is CPU-bound and scoped to albums you pick. There is no documented incremental reindex, no documented migration path when you change the image encoder, and no documented rollback if an index goes wrong; the README only describes managing or deleting indexes in Settings then Album Index Manager. Range filters reset when the app process restarts, which is a small but real papercut if you search within one album across sessions.
English queries pass through translation before encoding. The README flags Chinese translation quality as not established by the published measurements, so a Chinese-speaking user should not read the benchmark tables as evidence that their queries will work well.
How it differs from general-purpose image search stacks
The obvious alternative is not another phone app but a local pipeline: run CLIP or a similar model on a desktop or server, index the library there, and query it over the network. That approach gets you more compute, easier model swaps, and no APK build step. It also means your photos leave the phone, or you maintain a home server. PicQuery's trade is the opposite: the index lives on the device, the model is fixed at build time, and the cost is that you compile the app yourself and accept unverified release behavior.
A second alternative is to rely on whatever the platform already provides. Android's built-in search covers metadata and, on some devices, on-device classification labels. It requires no build and no model download. What it does not do is accept an arbitrary descriptive phrase, or use one photo as the query, against an index you control.
Licence, maintenance and what an upgrade costs you
PicQuery is MIT licensed. That permits commercial use and modification, provided the copyright notice and permission notice are retained. It says nothing about the MobileCLIP2 model weights, which are downloaded separately and are not covered by the repository's LICENSE file; check the terms attached to those assets before shipping anything. Nothing here is legal advice.
The last push to the default branch was on 2026-09-13, and the most recent release listed is v1.1.4 from 2025-02-04. The repository is not archived. Note the gap: the branch described in this README is ahead of the published releases, and the README says published releases may use earlier models. If you build this branch, you are tracking unreleased code.
Upgrade cost is dominated by model assets and indexes, not by Gradle. Changing the image encoder requires rebuilding the photo index, and the two flavors never share one. The version catalog at gradle/libs.versions.toml pins ONNX Runtime 1.29.0 and LiteRT 1.4.2, so runtime bumps are deliberate edits, and both flavors need a matching NDK and CMake toolchain.
Editorial conclusion
Adopt PicQuery if you want an offline, MIT-licensed Android app for text and image search over a chosen album set, and you are willing to build from this branch and place the MobileCLIP2 assets by hand. Do not adopt it if you need a verified release build, GPU or NPU acceleration, or measured quality on free-form personal galleries, since the repository states those remain unverified. Before building, confirm your device serial with adb devices -l, check that the model files match the SHA-256 values in script/model-MobileCLIP2/downloads/README.md, and decide which flavor's application ID you will keep installed.
Frequently asked questions
What is PicQuery used for?
It searches local photos on Android using English or Chinese descriptions, or by using a photo as the query. It builds and searches a photo index on the device rather than uploading images.
Does PicQuery upload my photos anywhere?
The README describes the index as built and searched on your device, and the app is free with no in-app purchases. Model assets are downloaded separately during the build, not at query time.
Which Android version does PicQuery require?
The build requirements list Android 10 / API 29 or newer, with JDK 17, Android SDK platform 37, NDK 29.0.14206865 and CMake 3.22.1.
What is the difference between the ONNX and TFLite flavors of PicQuery?
They are separate builds under different application IDs that can be installed together and keep separate indexes. ONNX uses ONNX Runtime 1.29.0 with 4 image and 4 text CPU threads; TFLite uses LiteRT 1.4.2 with native XNNPACK and 4 image and 2 text threads.
Why does PicQuery fail to build after cloning?
Model binaries are Git-ignored, so the assets under app/src/main/assets/ are missing. The README directs you to download the prebuilt App bundle and follow the SHA-256 verification steps, or export the models yourself.
Can I use PicQuery on an emulator?
The default APKs include ARM64 and ARMv7. For an x86_64 emulator, the README says to append -Pmobileclip2Abis=x86_64 to the build command.
Community notes