Замечания и предложения по нему, а также их обсуждение.Предыдущий тред тонет тут >>1000357.
>>1000921It's up.
И >>1000909 тоже.
>>1000929Thx. Через несколько дней продолжу открывать PR с фиксами очевидного.>>1000912А нам, собственно, для чего нужно оптимизировать хэш?Устроены все результаты, которые у меня получились, одинаково (пикрелейтед). x — threshold (в % от длины хэша), y — сколько новых картинок находятся при очередном увеличении threshold. Пояснения нарисованы для красного графика, но форма у всех графиков одинаковая. Чисто визуально по графику какой-либо вариации можно определить (1) и (2), всё, что между, нужно смотреть глазами. Всякие редкие масштабированные/растянутые/обрезанные продолжают существовать в промежутке между (1) и (2).И вот, собственно, мой вопрос — как нам оценивать разные вариации? Чтобы как можно больше дубликатов/масштабированных/растянутых/обрезанных находилось до границы (1)? Или до границы (2)? Или просто нужно как можно больше найденных картинок в какой-то точке между (1) и (2), где оно ещё находит только related-картинки (для красного графика — около 15%)? Если последнее, то нужно опасаться overfitting’а для конкретного датасета.
Штмл поломался.
>>1000932fixed
>>1000931В идеале дубликаты/масштабирования/растягивания/обрезки должны строго на меньших расстояниях, чем средние/серьёзные изменения. Но с обрезками и сдвигами, увы, у меня не получилось.> Или просто нужно как можно больше найденных картинок в какой-то точке между (1) и (2)Я оценивал по количеству найденного до точки, когда начинаются серьёзные false positives (начинает путать девочек и помидоры), и до точки, когда false positives по сравению с true positives становится слишком много. Последняя упомянутая точка определялась на глаз.В меньшей степени по характеру найденного (потому что с этим удач особых не было): скажем, одна из вариаций хэша по DCT zoom cube'а находила (>>/b/4173, >>/b/6116) на расстоянии 196/512, а do_round_phash.php на 223/512 (когда, если смотреть в общем, граф уже давно целиком связный и hamming_evo.py бесполезен).По-хорошему нужно было бы> единый список «как-то связаных между собой» картинок, которые должны находиться.Построить такой список можно, выстроив пары в порядке возрастания phash или SSIM, присваивая вручную парам со схожим содержимым некий действительный similarity score. И даже так это очень много работы. Как вариант, посмотреть, что в OpenCV есть более современное и тяжёлое, чем эту работу можно упростить.Или пока не заморачиваться, найти менее трудозатратными средствами вмещающийся в 512 бит вариант, и пусть будет MVP. И когда-нибудь если захочется заняться длинными серьёзными исследованиями или появится настоящий CV сварщик, то пытаться лучше.
>>1000934Кажется saucenao/ascii2d/wait до сих пор не умеют в кропы и сдвиги, так что всё равно GJ.c:hanako
Представляю вам на суд вариантblur=gauss_dyn[alpha=0.8,beta=4],scale=nn,S=64,post_blur=gauss[ksize=3,sigma=0.333333],hash=phash[Z=16,mask=circle],post_sobel=1.Размер хэша 193 бит (да, на единицу больше, чем 128+64, но это не специально), предлагаемое безопасное расстояние — 40〜42 бит (Hamming).В репозитории https://codeberg.org/aoba/my_phash — со всеми bells and whistles, в архиве в прикреплённой картинке — минимум, нужный для компиляции хешера и скрипт для сборки. Теперь нужна SDL2_Image вместо stb, оно умеет в webp.Мои выводы:✳ Перед скейлингом нужно делать динамический прямоугольный Gaussian blur (размеры ядра считаются из размеров изображения, но ядро не больше 255x255, чтобы не DoS'или).Ланцош плохо даунскейлит некоторые картинки: https://codeberg.org/aoba/my_phash/raw/commit/10507d8d7b1e996020568fb4484d86b6c96675b6/_demo/lanczos.png, потому что оно зависит только от маленькой доли всех пикселей исходного изображения (1.5% для 1024x1024 → 128x128).Нам не нужно считать свертку blur'а в каждом пикселе исходного изображения, а только в тех, которые требуются алгоритмом масштабирования.Для скорости (уменьшения количества точек, в которых нужно считать свёртку) можно использовать nearest-neighbour, после блюра нет разницы.✳ Нужно также блюрить Гауссом после скейлинга: ядро 3x3, сигма 1/3 выглядят оптимальными (для 64x64, для 128x128 нужно больше).✳ Обозначение: Z — сторона верхней левой подматрицы DCT, которую мы принимаем во внимание.Вообще все варианты с Z=8, включая канонический phash, выдают один и тот же хэш на bad1.png, bad2.jpg, bad3.jpg (путают bad1 и bad2/bad3). Это картинки из архива Ычана 1614714858949.png, 1610896340790.jpg, 1610895433020.jpg. Копии можно найти в том же репозитории в _demo/.Z=32 и даже Z=24 — это слишком много, начинает хуже находить legitimate дубликаты. Я остановился на Z=16.✳ Нужно применять фильтр Собеля после скейлинга и размытия, иначе плохо отличает те же bad1.png vs bad2.jpg/bad3.jpg.У некотрых вариантов с post_sobel=1 тоже есть failure mode: плохо отличают 1755031272746.jpg vs 1757275052034.jpg vs bad3.jpg.Но представленный мной вариант отличает их с огромным запасом (расстояние около 40%).✳ Безопасное расстояние для вашего хэша составляет не больше 139 бит. Были найдены следующие false-positives, первый из которых совсем unrelated:# img1 img2 distance_in_bits1610895433020.jpg 1693216927545.png 1401712238432772.png 1470588477270.png 1421595774788245.png 1588697792032.png 1421641455194459.png 1623844005270.png 1481556647751230.png 1557674602249.png 152...Про сравнение моего варианта и вашего будет в последующем посте.
blur=gauss_dyn[alpha=0.8,beta=4],scale=nn,S=64,post_blur=gauss[ksize=3,sigma=0.333333],hash=phash[Z=16,mask=circle],post_sobel=1
# img1 img2 distance_in_bits1610895433020.jpg 1693216927545.png 1401712238432772.png 1470588477270.png 1421595774788245.png 1588697792032.png 1421641455194459.png 1623844005270.png 1481556647751230.png 1557674602249.png 152...
“a” обозначает ваш вариант, “p” — мой.Ваши примеры (исключая простые случаи, где оба выдают очень маленький процент; и исключая heavy padding, где ни один ни справляются):https://014.cdn.ganbaranai.moe/b/src/170370140818.jpg vs https://014.cdn.ganbaranai.moe/b/src/168217548738.jpg:a => 23.828%, p => 15.544%https://014.cdn.ganbaranai.moe/b/src/163730500889.jpg vs https://014.cdn.ganbaranai.moe/b/src/158460154167.jpg:a => 30.469%, p => 24.870%https://ii.yakuji.moe/b/src/1673088974447.jpg vs https://ii.yakuji.moe/b/src/1674079476839.jpg:a => 17.578%, p => 9.326%Из архива Ычана были случайно выбраны 2000 изображений, у которых ширина и высота >= 500.Они были подвергнуты следующим преобразованиям: даунскейлинг в 50%, 25%, 13%; кроп 5% сверху+снизу, справа+слева, со всех сторон; паддинг 5% чёрным сверху+снизу.Затем вычислялось расстояние с исходным.threshold для вашего был взят 27% (138 бит), для моего — 22% (42 бита).# variant transform_index min max 90% average median num_caught_at_thres num_caught_at_90%_thres# transforms: 0 => scale 50%, 1 => scale 25%, 2 => scale 13%, 3 => crop top+bottom, 4 => crop left+right, 5 => crop all, 6 => pad top+bottoma 0 0.000% 51.953% 0.781% 0.501% 0.391% 1997 1997p 0 0.000% 33.161% 2.073% 1.355% 1.036% 1997 1996a 1 0.000% 51.953% 1.562% 0.886% 0.391% 1996 1996p 1 0.000% 34.197% 4.145% 2.507% 2.073% 1995 1991a 2 0.000% 51.953% 7.812% 4.728% 5.859% 1994 1993p 2 0.000% 34.197% 7.254% 4.091% 3.109% 1988 1985a 3 7.031% 51.953% 32.422% 27.404% 27.344% 938 431p 3 0.000% 51.813% 24.870% 19.930% 19.689% 1466 1079a 4 4.688% 54.688% 32.031% 26.402% 26.562% 1091 624p 4 0.000% 36.269% 24.870% 19.134% 18.653% 1553 1212a 5 8.594% 57.812% 44.922% 38.923% 39.062% 29 8p 5 0.000% 63.212% 35.233% 29.402% 29.016% 134 55a 6 8.984% 53.125% 30.078% 25.304% 25.391% 1394 822p 6 0.000% 44.560% 23.834% 19.544% 19.689% 1559 11820〜2 можно игнорировать, с ними отлично справляются оба.max значительно меньше на всех, кроме 5 (но с 5 вообще оба плохо справляются).average и median значительно меньше на всех 3〜6.num_caught_at_thres и num_caught_at_90%_thres в процентах от 2000 (исключая 0〜2, где везде >99%):<= threshold: 47% → 73%, 55% → 78%, 1% → 7%, 70% → 78%.<= 0.9*threshold: 22% → 54%, 31% → 61%, 0% → 3%, 41% → 59%.Изменение ratio тоже пробовалось, но результаты такие же, как у 0〜2.Код этого эксперимента и список файлов выложу позже.
# variant transform_index min max 90% average median num_caught_at_thres num_caught_at_90%_thres# transforms: 0 => scale 50%, 1 => scale 25%, 2 => scale 13%, 3 => crop top+bottom, 4 => crop left+right, 5 => crop all, 6 => pad top+bottoma 0 0.000% 51.953% 0.781% 0.501% 0.391% 1997 1997p 0 0.000% 33.161% 2.073% 1.355% 1.036% 1997 1996a 1 0.000% 51.953% 1.562% 0.886% 0.391% 1996 1996p 1 0.000% 34.197% 4.145% 2.507% 2.073% 1995 1991a 2 0.000% 51.953% 7.812% 4.728% 5.859% 1994 1993p 2 0.000% 34.197% 7.254% 4.091% 3.109% 1988 1985a 3 7.031% 51.953% 32.422% 27.404% 27.344% 938 431p 3 0.000% 51.813% 24.870% 19.930% 19.689% 1466 1079a 4 4.688% 54.688% 32.031% 26.402% 26.562% 1091 624p 4 0.000% 36.269% 24.870% 19.134% 18.653% 1553 1212a 5 8.594% 57.812% 44.922% 38.923% 39.062% 29 8p 5 0.000% 63.212% 35.233% 29.402% 29.016% 134 55a 6 8.984% 53.125% 30.078% 25.304% 25.391% 1394 822p 6 0.000% 44.560% 23.834% 19.544% 19.689% 1559 1182
<= threshold: 47% → 73%, 55% → 78%, 1% → 7%, 70% → 78%.<= 0.9*threshold: 22% → 54%, 31% → 61%, 0% → 3%, 41% → 59%.
> И когда-нибудь если захочется заняться длинными серьёзными исследованиями или появится настоящий CV сварщик, то пытаться лучше.А чего вообще в теории можно сделать-то?В проекте pHash есть ещё хэш на интегралах Радона — его затратно считать, там O(n_rays * W * H), n_rays = 180.Ещё там есть волночки (wavelet) — я не верю, что они существенно лучше, чем DCT.CNN-based (как в https://github.com/idealo/imagededup) — как-то слишком круто для нас.Average hash — оче плохой.dhash, dhash-over-DCT, zigzag и прочее тоже работает хуже, чем DCT.Только автоматическое убирание паддинга можно ещё сделать.> Построить такой список можноНу я сделаль убирание кластеров из очень похожих изображений (mean_error.c и utils/hide_genuine_clusters.py) и viewer/. В том же репозитории.
mean_error.c
utils/hide_genuine_clusters.py
> Код этого эксперимента и список файлов выложу позже.In the repo now (_eval/).
>>1000937Приятные результаты у вас. Детально посмотрю в воскресенье. Have been в разъездах.%Пока, читаючи на смартфоне замечено, что по-прежнему включаете DC (то бишь, block[0]) в хэш. Так и задумано?
>>1000942> Пока, читаючи на смартфоне замечено, что по-прежнему включаете DC (то бишь, block[0]) в хэш. Так и задумано?Во-первых, один бит из 193 не повлияет заметно на результат.Во-вторых, имеет смысл выбросить буквально любой бит, кроме DC.Обозначения: N — длина хэша, DC = hash[0], AC = hash[1:N].Утверждение: при определённом допущении*, parity(AC) 【a.k.a. popcount(AC)%2】 есть функция от DC и N: parity(AC) = f(DC, N). Таким образом, мы можем выбрать любой бит из AC и отбросить его: его можно реконструировать как b = parity(AC_without_b) XOR f(DC, N).* Допущение: либо изображение полностью одноцветное и hash состоит только из нулевых бит (и, следовательно, DC=0), либо DC=1 и у нас ровно половина (исключая саму медиану, если N-1 нечётное) элементов больше медианы (popcount(AC) = floor((N-1) / 2)).Это допущение выполняется для всех изображений в архиве Ычана, допущение DC=1 — не выполняется для 19 из 95031.Пруф запощу завтра.
Пруф: разберите все восемь вариантов ⟨N%4, DC⟩. Получитсяdef f(DC, N): if N % 4 == 0 or N % 4 == 3: return DC else: return 0
def f(DC, N): if N % 4 == 0 or N % 4 == 3: return DC else: return 0
>Pr. 95Про картинки уже обсуждалось, решается ограничением по макс. ширине/высоте.Видео... Ну, в целом, такие лимиты сервер съест.
>>1000947Понятно, что настолько продвинутый желающий навредить так или иначе найдёт способ, но конкретно это легко поправить: добавить к ffmpeg’у опцию -max_pixels 100M. Этого должно быть достаточно, но хорошо бы ещё запускать сам ffmpeg, предварительно сделав ulimit -v 20971520 (20G виртуальной памяти) и ulimit -t 30 (30 секунд чистого CPU time). Поскольку exec() в PHP уже запускает команду под /bin/sh -c ..., для этого даже не нужно создавать никаких новых процессов или что-то переделывать.
-max_pixels 100M
ulimit -v 20971520
ulimit -t 30
/bin/sh -c ...
>>1000942Прошу прощения за просрочку в неделю. Ждите к этому воскресенью тогда.
>>1000950Собственно, пробовать предлагается уже другое. Но можете и старое попробовать.По какой-то причине, blur&scale ⇒ histogram equalization ⇒ blur ⇒ DCT работает значительно лучше, чем blur&scale ⇒ blur ⇒ Sobel ⇒ DCT.Предлагаемое безопасное расстояние не изменилось: 40〜42 бит.Изменения в результатах по сравнению с моим прошлым вариантом:<= threshold: 73% → 86%, 78% → 88%, 7% → 12%, 78% → 83%.<= 0.9*threshold: 54% → 69%, 61% → 73%, 3% → 5%, 59% → 65%.Новых false-positives не обнаружено.В архиве в картинке — опять же, минимум, нужный для компиляции нового хешера и скрипт для сборки.(А ещё был найден более вопиющий пример плохого даунскейлинга Ланцошом: https://codeberg.org/aoba/my_phash/raw/commit/484ed9ad07056888a98db9ae44673c46deaf1d71/_demo/lanczos_another.png)
<= threshold: 73% → 86%, 78% → 88%, 7% → 12%, 78% → 83%.<= 0.9*threshold: 54% → 69%, 61% → 73%, 3% → 5%, 59% → 65%.
>>1000951Посмотрел. Давайте остановимся на текущем варианте?Который blur=gauss_dyn[alpha=0.8,beta=4],scale=nn,S=64,post_blur=gauss[ksize=3,sigma=0.333333],hash=phash[Z=16,mask=circle],post_histeq=1. On the other hand, проверьте, как волночки справляются, раз уже закомитили.Если брать текущий вариант, то за тем, исключением, что DC я бы всё-таки не включал. Во-первых, поскольку для большинства изображений в наших данных DC бит равен 1, DC бит не несёт различительной силы. Да, выброшенный AC бит можно восстановить, но покуда он выброшен, он не участвует в расчёте расстояния, и уж он-то точно может разниться. Поэтому, и потому, что если брать только AC, получается естественным образом определить 192-битный хэш — целое число байтов.Ещё я бы попробовал (и, наверное, попробую) реализовать текущий вариант с blur и histeq vips'овыми функциями, доступными PHP-обёртке.> А чего вообще в теории можно сделать-то?Наверное, много чего, особенно с большей-то длинной. С 64 битами, уже на расстоянии 16/64, скорее всего, вероятность коллизий между двумя случайными изображениями достаточно высокая для того, чтобы на dataset'е в 10**4 картинок они likely были. А вот с 192 битами и 42/192 эта вероятность на порядок меньше. А значит, есть куда расти. Другой вопрос, как.Есть radon transform и log-polar transform. O(n_rays * W * H) вряд ли страшно, если это про уменьшенную копию и простые операции: матричное умножение сейчас — это O(S**3).Вроде существуют алгоритмы, позволяющие вместо синусов-косинусов построить для специфичного dataset'а более подходящий набор базисных векторов.Есть scattering wavelet transform, которое бывает используется для нейронок. Там генерируется несколько проекций-матриц, и оно довольно толстое и медленное, и не понятно, как его в phash.Есть SIFT и подобные алгоритмы для feature extraction. Применимы ли они в нашем деле, не знаю.Можно подумать о некой функции, меняющей местами пиксели, то бишь об преобразовании координат из (x, y) в (p, q), даже не обязательно биективном, таком что основанные на DCT phash'ы картинок после преобразования более инвариантны относительно crop'ов и padding'ов. Думается, например, о том, чтобы найти некий saliency point в картинке, принять координаты этого пикселя за (p=0, q=0), а значения для остальных пикселей считать по удалению от этого (p, q). Но может быть можно придумать что-то куда более робастное.Хотя мы для оценки мы берём безопасное расстояние 42, где нет false positives, но на практике будет при поиске за него стоит забегать, поскольку некоторые (например, 15139:160754327348.png и 17616:160805964719.jpg 50/192) важные сопоставления находятся за его приделами. Писать что-то вроде "похожее изображение могло быть опубликовано здесь" вместо "похожее изображение было опубликовано здесь".По-прежнему нахожу очень странным, что ваша реализация иногда несколько хуже справляется с простыми переужатиями. 14/192 (3369:176383346733.png, 3381:176686056329.jpg) найденный максимум. Совсем не понимаю, почему.
blur=gauss_dyn[alpha=0.8,beta=4],scale=nn,S=64,post_blur=gauss[ksize=3,sigma=0.333333],hash=phash[Z=16,mask=circle],post_histeq=1
> Посмотрел. Давайте остановимся на текущем варианте?> Который blur=gauss_dyn[alpha=0.8,beta=4],scale=nn,S=64,post_blur=gauss[ksize=3,sigma=0.333333],hash=phash[Z=16,mask=circle],post_histeq=1.Давайте.> On the other hand, проверьте, как волночки справляются, раз уже закомитили.Очень плохо они справляются. Реализация корректная, на всех уровнях «пайплайна» смотрел результаты, найденные «похожие» тоже смотрел — plausible. Meh.> Если брать текущий вариант, то за тем, исключением, что DC я бы всё-таки не включал. Во-первых, поскольку для большинства изображений в наших данных DC бит равен 1, DC бит не несёт различительной силы. Да, выброшенный AC бит можно восстановить, но покуда он выброшен, он не участвует в расчёте расстояния, и уж он-то точно может разниться. Поэтому, и потому, что если брать только AC, получается естественным образом определить 192-битный хэш — целое число байтов.Agreed. Выпилил DC.> А значит, есть куда расти. Другой вопрос, как.Можно завести два-три индекса по разным хешам, например, текущий, предыдущий (с sobel) и dhash. Запрашивать похожие по всем и объединять результаты.> Есть radon transform и log-polar transform. O(n_rays W H) вряд ли страшно, если это про уменьшенную копию и простые операции:Нет, не про уменьшенную копию. И в библиотеке pHash оно выдаёт не биты, которые можно сравнивать по Hamming distance или Jaccard distance, а 40 байт, которые нужно сравнивать с помощью, эм, “circular cross-corelation”.> Вроде существуют алгоритмы, позволяющие вместо синусов-косинусов построить для специфичного dataset'а более подходящий набор базисных векторов.> Есть scattering wavelet transform, которое бывает используется для нейронок. Там генерируется несколько проекций-матриц, и оно довольно толстое и медленное, и не понятно, как его в phash.> Есть SIFT и подобные алгоритмы для feature extraction. Применимы ли они в нашем деле, не знаю.Слишком сложно, ИМХО.> Можно подумать о некой функции, меняющей местами пиксели, то бишь об преобразовании координат из (x, y) в (p, q), даже не обязательно биективном, таком что основанные на DCT phash'ы картинок после преобразования более инвариантны относительно crop'ов и padding'ов. Думается, например, о том, чтобы найти некий saliency point в картинке, принять координаты этого пикселя за (p=0, q=0), а значения для остальных пикселей считать по удалению от этого (p, q). Но может быть можно придумать что-то куда более робастное.Так вы хотите выпячивать важные куски (как на pic related)? Или затемнять неважные? Я не понял.> Хотя мы для оценки мы берём безопасное расстояние 42, где нет false positives, но на практике будет при поиске за него стоит забегать, поскольку некоторые (например, 15139:160754327348.png и 17616:160805964719.jpg 50/192) важные сопоставления находятся за его приделами. Писать что-то вроде "похожее изображение могло быть опубликовано здесь" вместо "похожее изображение было опубликовано здесь".Ну dhash’ы выдают маленькое (для них — там threshold в процентах гораздо больше) расстояние на этих картинках, но нужно будет глазами опять посмотреть, где у них безопасное расстояние по факту.
> Ну dhash’ы выдают маленькое (для них — там threshold в процентах гораздо больше) расстояние на этих картинках, но нужно будет глазами опять посмотреть, где у них безопасное расстояние по факту.Нет, эти расстояния далеко за пределами «находится совсем не связанная между собой ерунда».Конкретно эти картинки можно найти на расстоянии 38, если применить агрессивный unsharp mask на оригиналы. Но искать фильтры для каждой пары картинок, которая не находится, — странное занятие какое-то.
>>1000965>stretched_aoba.pngЧем и как такое забавное сгенерировано?
>>1000975gimp, Filters → Distorts → Lens Distortion. Подбирать параметры.
Автор форматтера приглашается для ревью https://codeberg.org/yakui-lover/fbe-410/pulls/19
Пока посмотрел код на телефоне. Я не автор rec_node_format.> // Currently there's a bug here: it produces 15 lines instead of 16 (compare with <br> above).Разве в контексте предложенного алгоритма !20:<li>x</li>: не корректнее сравнивать с !20:<br>x:, чем с !20:x<br>?Всегда ли ли <ul><li> означает новую строку? Или если в самом начале сообщения или сразу после <br>, то переноса строки не будет? I wonder.
!20:<li>x</li>:
!20:<br>x:
!20:x<br>
>>1001001Э… там какой-то майндфак вообще. Требует изучения. А я подозревал, что вырезание <body> и </body> по индексам это нехорошо.
Ну, видимо, дело в моей версии PHP или чём-то ещё. Здесь не воспроизводится.По существу заданных мне вопросов могу пояснить следующее:// This is correct (there is a line break after "Begin", even two of them):testcase(my_expand('Begin<ul>!20:<li>x</li>:</ul>End'), my_expand('Begin<ul>!15:<li>x</li>:</ul>'));// This is incorrect (15 lines total, not 16):testcase(my_expand('<ul>!20:<li>x</li>:</ul>'), my_expand('<ul>!15:<li>x</li>:</ul>'));
// This is correct (there is a line break after "Begin", even two of them):testcase(my_expand('Begin<ul>!20:<li>x</li>:</ul>End'), my_expand('Begin<ul>!15:<li>x</li>:</ul>'));// This is incorrect (15 lines total, not 16):testcase(my_expand('<ul>!20:<li>x</li>:</ul>'), my_expand('<ul>!15:<li>x</li>:</ul>'));
Поскольку автор форматтера не объявился, давайте рассмотрим PR в его отсутствие.И это, так а чего, будем прикручивать поиск картинок? Для этого постгрес не нужен: вот это отрабатывает в MySQL за 40 мс:SELECT filename, hash0, hash1, hash2FROM imagesWHERE id BETWEEN 3 AND 199997ORDER BY BIT_COUNT(hash0 ^ %s) + BIT_COUNT(hash1 ^ %s) + BIT_COUNT(hash2 ^ %s) ASCLIMIT 30(ПослеCREATE TABLE images ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, filename VARCHAR(32) NOT NULL, hash0 BIGINT UNSIGNED NOT NULL, hash1 BIGINT UNSIGNED NOT NULL, hash2 BIGINT UNSIGNED NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4и вставки 200k записей со случайными хешами).Вроде бы даже «на холодную» столько же времени требуется.А хэшер скомпилировать в WASM и запускать на сервере и на клиенте — оказывается, синусы-косинусы-корни зависят от платформы, IEEE 754 не определяет результат до последнего бита. А WASM определяет. Да и сишкой тыкать в живой интерпретатор PHP как-то небезопасно.
SELECT filename, hash0, hash1, hash2FROM imagesWHERE id BETWEEN 3 AND 199997ORDER BY BIT_COUNT(hash0 ^ %s) + BIT_COUNT(hash1 ^ %s) + BIT_COUNT(hash2 ^ %s) ASCLIMIT 30
CREATE TABLE images ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, filename VARCHAR(32) NOT NULL, hash0 BIGINT UNSIGNED NOT NULL, hash1 BIGINT UNSIGNED NOT NULL, hash2 BIGINT UNSIGNED NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
>>1001006> И это, так а чего, будем прикручивать поиск картинок?Всё не могу пока включиться в работу. Так что прошу, если можете, взять дело в свои руки.> BIT_COUNT(hash0 ^ %s) + BIT_COUNT(hash1 ^ %s) + BIT_COUNT(hash2 ^ %s)С Постгресом, как минимум, можно обойтись одним bit(192) столбцом вместо трёх. Но если есть желание вскорости увидеть рабочий вариант... Решайтесь.
>>1001007Хорошо.Из последних упражнений:Можно заставить выделять памяти максимум 6 байт на пиксель. Это вместе с преобразованием прозрачности в чёрное или белое в зависимости от weighted luma, как у вас. imagemagick, вроде, максимум 4 байта на пиксель может потреблять, ffmpeg — 12.Center crop на 87.5% хорошо работает, результаты лучше.Выглядит интересным https://github.com/manjunath5496/ML-Papers/blob/main/Saliency Detection A Spectral Residual Approach.pdf,но у авторов большие проблемы с выражением своих мыслей формализмами — большинство формул и формулировок означают не то, что можно подумать)Но #2 и #3 это на потом.> С Постгресом, как минимум, можно обойтись одним bit(192) столбцом вместо трёх.Ну, в MySQL тоже есть BINARY(192). Только BIT_COUNT с ним не работает (он обрезает входное значение до 64 бит). И, почему-то, даже с BIT_COUNT(whole_hash), что неправильно, оно работает медленнее.Да и какая разница, если 40 мс?
BIT_COUNT(whole_hash)
> BIT_COUNT(whole_hash)BIT_COUNT(whole_hash ^ UNHEX(%s)), конечно же.
BIT_COUNT(whole_hash ^ UNHEX(%s))
Будет ли принята фича с поиском похожих картинок, если таковая будет реализована? Там на сервере нужно будет запускать только ffmpeg и мою штуку через что-то типа wasmtime с максимальными ограничениями (без доступа к сети, ФС, etc, с лимитами на память и время работы — настраивается в wasmtime), чтобы было безопасно.
>>1001014>wasmtimeНет. Пишите на языке кодобазы.
>>1001015На языке кодобазы это будет непрактично, поскольку подход требует обработки изображений, которую у меня не получается выразить в терминах libvips или чего-либо ещё.Ну и мы планировали это дело на клиенте ещё запускать — там-то точно нужен будет васм, лол.В общем, weird. Ну может кто-то, не я, сделает версию без блюра на PHP. Но зачем дублировать то, что на клиенте будет.Васмтайм, оказывается, нет в репозиториях дебиана. Но есть wasmedge и wazero. Чем они-то не угодили?
>>1001016На языке отличным от кодобазы это будет непрактично, потому что сопровождающему тогда придётся знать 2-2.5 языка вместо 1.5.Пишите отдельный продукт в отдельной репе. А дальше если это можно вызывать через exec, то и славно.
>>1001017OK. Этот отдельный продукт может требовать для запуска на сервере какой-либо рантайм WASI (wasmtime/wasmedge/whatever)? Без этого можно, но нужно будет заморачиваться с изоляцией (ну и детерминизм потеряется, хотя, опять же, nobody cares).Запускать какой-то сишный код без изоляции на сервере кажется плохой идеей даже мне, автору кода.
>>1001019> детерминизм потеряетсяПроблему с синусами-косинусами-корнями разве нельзя решить, захардкодив матрицы преобразования?> спойлерКто он, safe и blazing fast? Верно, дети. Это Rust!>>1001016> подход требует обработки изображений, которую у меня не получается выразить в терминах libvipsКакие конкретно моменты не получется? Попробую в субботу посмотреть-таки.
>>1001019У нас там magic и ffmpeg уже запускаются от апача, какая ещё изоляция? Я-то могу что-нибудь настроить, но в случае Соуса, думаю будет проще какой-нибудь просто Докер обернуть.
>>1001022> Проблему с синусами-косинусами-корнями разве нельзя решить, захардкодив матрицы преобразования?Нет, потому что там ещё exp в blur_gauss_dyn. А вообще, если мы не компилируемся в WASM и не включаем в себя всю лабуду для декодирования изображений, то не имеет смысла заботиться о каком-то детерминизме: всё равно RGBA, которые надекодирует библиотека, может отличаться от платформы к платформе (минимальные пары есть), и уж точно от того, что надекодирует браузер, если мы на клиенте собираемся использовать его возможности для декодирования, а не полагаться на WASM, в котором всё нужное для декодирования уже есть.> Кто он, safe и blazing fast? Верно, дети. Это Rust!Ну вот тут не вполне понятно, какой от него прок. Быстрее, чем текущее под bwrap, точно не будет.> Какие конкретно моменты не получется?Момент blur_gauss_dyn & scale_nn. Там нужно считать свёртку. Не во всех пикселях, а только в тех, которые нужны scale_nn. Свёртка прямоугольная.>>1001023> У нас там magic и ffmpeg уже запускаются от апачаmagic и ffmpeg написаны понятно кем, их код всегда под пристальным присмотром, они есть в репозиториях всех крупных дистрибутивов, etc. Поэтому их не должно быть страшно запускать.Вот эту мою хреновину — должно быть.> , какая ещё изоляция?bwrap. Или firejail. Или Podman/Docker/что-то там в systemd завезли.$ ffmpeg -hide_banner -loglevel error -i картинка.jpg -vf format=rgba -f image2pipe -vcodec pam - | bwrap --ro-bind /usr/bin/busybox /usr/bin/busybox --unshare-all /usr/bin/busybox sha1sumeb3609abf7df96a19edf40c059bb2f15f2bf5911 -$(Для повторения нужен busybox-static, наш хэшер будет тоже статически слинкован, если будет просто читать PAM из stdin.)Это изоляция вообще всего, включая сеть, ФС, user namespace, etc.Ну или не через пайп, а использовать ту же SDL2_Image, и прокидывать всё нужное-слинкованное тоже через bwrap.> Я-то могу что-нибудь настроить, но в случае Соуса, думаю будет проще какой-нибудь просто Докер обернуть.Можно. Но не знаю, зачем заморачиваться с чем-то ещё, если bwrap умеет всё, что нужно, и есть в репозиториях.
$ ffmpeg -hide_banner -loglevel error -i картинка.jpg -vf format=rgba -f image2pipe -vcodec pam - | bwrap --ro-bind /usr/bin/busybox /usr/bin/busybox --unshare-all /usr/bin/busybox sha1sumeb3609abf7df96a19edf40c059bb2f15f2bf5911 -$
> bwrapЯ тут обнаружил, что systemd-run --scope ещё лучше: даже не нужно устанавливать отдельный пакет, и оно работает везде, где есть системд, а bwrap в таком виде не работает на каких-то хентайных hardened-версиях дистрибутивов.«Если у вас не systemd, делайте bwrap. Если у вас не bwrap, сделайте chmod u+s /usr/bin/bwrap или обращайтесь к нам за помощью, если мы ещё живы.»
systemd-run --scope
chmod u+s /usr/bin/bwrap
>>1001024> Момент blur_gauss_dyn & scale_nnИ правда, крайне неудобно, если возможно.Судя по всему, в libvips нету условного reduce. Поэтому в терминах php-vips для подсчёта значения в заблюреном пикселе надо будет делать extract_area вокруг него и multiply'ить её на ядро, потом брать у результата avg (sum нету) и умножать среднее на количество элементов в матрице. extract_area не копирует память, а вот multiply, как я понимаю, будет. Что не есть эффективно. И ещё проблема с clamp.В чём-то побыстрее и понизкоуровнее PHP вроде есть воспользоваться более низкоуровневым Vips.Region и обращаться к пикселям по указателю.> uint32_t rgba = f(f_userdata, x, y);На всякий случай. Grayscale, как Соусная "Неправда", и 16-битные цвета, как в некоторых PNG аниме-скриншотах, обрабатываются корректно?
RGB565 ещё.
>>1001028> На всякий случай. Grayscale, как Соусная "Неправда", и 16-битные цвета, как в некоторых PNG аниме-скриншотах, обрабатываются корректно?Sure thing. Это же SDL2 этим занимается, а нам просто предоставляет SDL_GetRGBA() и количество байт на пиксель (bpp). Мы читаем bpp байт по нужному смещению, передаём результат и SDL_PixelFormat *format в SDL_GetRGBA(), она декодирует в rgba.format->format может принимать одно из большого списка значений SDL_PIXELFORMAT_*.У меня готов образец, который читает PAM из ffmpeg’а. И даже прозрачность выставляет в чёрное или белое в зависимости от средней люмы (в основной ветке это пока не реализовано). Ветка prod. Сделать ./build_main_pam.sh и запускать такffmpeg -hide_banner -loglevel error -i PICTURE.jpg -vf format=rgba -f image2pipe -vcodec pam - | ./main_pam
SDL_PixelFormat *format
format->format
SDL_PIXELFORMAT_*
ffmpeg -hide_banner -loglevel error -i PICTURE.jpg -vf format=rgba -f image2pipe -vcodec pam - | ./main_pam
(На картинке была демонстрация корректность обработки grayscale старым хэшером с SDL2_Image.)Статическая сборка (CC="gcc -static -s" ./build_main_pam.sh) даёт бинарник в 747 кб, что вполне. Его нужно будет потом просто запускать под systemd-run --scope (...какие-то_опции...).Осталось код на PHP и фронтенд написать.
CC="gcc -static -s" ./build_main_pam.sh
systemd-run --scope (...какие-то_опции...)
>>1001031>>1001032Замечательно.> ffmpegХм. Там такая проблема вроде, что он отказывается картинки, превосходящие где-то 20000 (?) пикселей по одной стороне. С одной стороны, релевантно для сшивок манги. С другой, вряд ли есть польза от phashing'а таких сшивок.
>>1001033> Там такая проблема вроде, что он отказывается картинки, превосходящие где-то 20000 (?) пикселей по одной стороне.Всё гораздо интересней. Ограничения такие:(8×W + 1024)×(H + 128) < 2147483647;W < 65536;H < 65536.На практике это означает W×H < ≈260'000'000 (на самом деле верхний предел 259M〜264M в зависимости от aspect ratio).При этом приведённая мной команда ffmpeg выделяет 8 байт на один пиксель. А наша программа ещё допольнительно 2 байта на пиксель.Если мы хотим, чтобы всё вместе это потребляло не больше, скажем, 1G (лимит по умолчанию у imagemagick), то площадь (W×H) должна быть < ≈107'000'000. То есть ffmpeg’у нужно передавать именно те самые -max_pixels 100M, про которые мной говорилось в >>1000949.А ≈107M (ограничение по памяти, которую дозволительно скушать) это меньше, чем ≈260M (техническое ограничение ffmpeg’а).Мне кажется, получилось неплохо: для компиляции, кроме build-essential, ничего не нужно; собранный бинарник не может сломаться после обновлений, ибо статический; systemd-run даже не нужно устанавливать.
1. Должна ли функциональность «похожие картинки» находиться за [кф]апчей?2. Название пуродакуто®©? Изначально не хотелось название.
>>1001038Под похожие, может быть, а под почти идентичные, думаю, нет.Я как это себе представлял: пользователь выбирает картинку для прикрепления, её хэш тут же отправляется серверу, а сервер возвращает, есть ли likely идентичные картинки. Если есть, показывается в status bar'е о том уведомление и reflink на одну из.Но это про почти идентичные. А под похожие, наверное, окно вроде «Избранные нити» или отдельную страницу? На которую в уведомлении давать ссылку, если почти идентичные или похожие были обнаружены.You decide. Я бы на чём-то совсем утилитарном остановился вроде round_phash.
>>1001039Вы понимаете, что такое захочется трогать даже не только для того чтоб постить - а чтоб найти что-то милое?
>>1001040Ну, я из соображений отказоустойчивости и хотел HNSW индекс.Но будут ли желающие? Ведь iqdb вроде не трогают для целей поиска милоты, и вряд ли iqdb для таких целей сколь-нибудь удовлетворительно. И надолго ли желания у желающих хватит? Скажем, если похожие картинки бывают редко: сделать поиск без капчи вплоть до расстояния, где false positive rate < 0.001). Более глубокий пока отключить вовсе, а потом спрятать за капчу. Сейчас, мне кажется, менять-возиться с капчесистемой будет нецелесообразно муторно в рамках MVP.Как альтернативу капче можно потом rate limiter.