Замечания и предложения по нему, а также их обсуждение.Предыдущий тред тонет тут >>1000357.
>>1000830> я прошу как это будет выглядеть в некоем абстрактном MoveThreadService. Начиная с авторизации. С вызовами пересборки и прочая. Можно не кодом, а взаимодействующими сущностями.Jawohl, ждите.> Но впрочем костыли связанные с привязкой айпишника к конкретной доске уже сейчас видныЕсли не нравится, давайте тогда, как сейчас, держать шифрованный IP прямо в posts, и делать SELECT DISTINCT по нему для поиска по подсети и числа уникальных клиентов, although это более медленные SELECT'ы.> Не имеет смысла с точки зрения пользования. Что админ будет переносить треды не посмотрев на репорты сомнительно, а если и будет, удалять их не требуется, так как они не отсмотрены.Удалять их требуется, если доска, на которую осуществляется перенос, не поддерживает репорты, чтобы не было orphan'ов.А если поддерживает, то требуется обновить ID постов в репортах, чтобы пользователь, например, не мог отрепортить пост, помеченный модератором или админом, как не требующий вмешательства уже на новой доске.Пускай база данных будет consistent, жалко что ли?> Алсо, мускуль?? Мы же на Postgres будем переходить. После решения #9 и прочих более насущных проблем в том, что есть.> Ало, indices.Аря? My bad.
>>1000832Если постгря, то зачем last_post_id когда можно просто CREATE SEQUENCE?
>>1000833Потому что у нас composite primary key (board_id, post_id). А sequence'ы, насколько я понимаю, не работают с composite pkey даже в Постре.
>>1000834>composite primary key (board_id, post_id)Да, не (board_id, last_post_id). Чем ручками его дёргать, nextval/curval/setval и по последовательности на доску.
>>1000835Как-то так то бишь?SELECT posts.id AS old_id, nextval(boards.post_id_sequence_name) AS new_idFROM posts,boardsWHERE posts.board_id = :source_board_id AND posts.thread_id = :source_thread_id AND boards.id = :target_board_idЯсно.
SELECT posts.id AS old_id, nextval(boards.post_id_sequence_name) AS new_idFROM posts,boardsWHERE posts.board_id = :source_board_id AND posts.thread_id = :source_thread_id AND boards.id = :target_board_id
>>1000815Inside (png+zip)-related.Если перейдём на единую таблицу posts, можно будет взять ORM/Data Mapper/…. А пока так.>>10008291. Отлаживать-поддерживать эту радость потом можно будет так же просто, как императивный код?2. Мы уже прямо-таки собрались мигрировать на постгрес и единую таблицу posts?>>1000831> Тогда пока поставить на 'cookie_samesite' => 'Lax' и 'cookie_domain' => KU_DOMAIN, although, ЕМНИП, в браузере есть какие-то проверки/ограничения на соответствие домена, выставившего cookie, и домена, на который cookie отправляется, и 'cookie_domain' самостоятельно ставить не надо.При этих ограничениях куки подставляются только в GET-запросы и только если запрос делается в результате клика. Но это не есть надлежащее решение: работать это будет до первой XSS или GET, меняющего состояние (у нас последнее, вроде как, сейчас присутствует).> Насколько я понимаю, оно заставляет так поступать конкретно с phpsessid печенькой, к которой management действия привязаны, а другим печенькам можно ставить политику as neeed.> Разве нельзя на 0l4chan.org целиком MitM'ить соединение меж клиентом и подлинным 014chan.org-сервером?My point was that, если администратор будет вынужден логиниться заново при переходе по ссылке с другого сайта, то он может и забыть проверить, что это домен не фишинговый. А без strict он просто не будет вынужден логиниться заново в таком случае.И, если уж совсем вредничать, у нас faptcha_type в $_SESSION хранится.
Энивей, до 19го я ПР рассматривать не могу.
>>1000775Вы забыли про 7x7 box blur в phash. Я реализовал phash. Обнаружилось только два легитимных false-positive (см. пик). А ваша ранее не обнаруженная Роко нашлась.Для исключения первого можно хэшировать цветовую компоненту в дополнение к grayscale.Это на 19k Булочных картинках.
>>1000856Пожалуйста, опубликуйте вашу реализацию же.> А ваша ранее не обнаруженная РокоЧего? В реализации, которую я здесь опубликовал и про которую говорил в >>/b/1006362, она обнаруживается на расстоянии 0 (равные хэши). Неужели совсем позапускать-посмотреть неинтересно было?
>>1000718-пост можете disregard'нуть entirely. Там, ЕМНИП, была некая серьёзная ошибка в реализации. Точно не вспомню, какая.
>>1000858Результаты на ваших примерах:Дубликаты:> 16/512 — расстояние меж https://ii.yakuji.moe/b/src/1705971491904.jpg и https://ii.yakuji.moe/b/src/1688504889304.jpg2/64> 28/512 — меж >>9388 и >>94966/64> 40/512 — меж https://ii.yakuji.moe/b/src/1648398268154.jpg и https://ii.yakuji.moe/b/src/1572851490178.jpg2/64Похожие, но не идентичные:> Для сравнения, на расстоянии 40/512 можно обнаружить https://ii.yakuji.moe/b/src/1681308511905.jpg и https://ii.yakuji.moe/b/src/1683280427230.jpg.2/64Scaling/сдвиг/padding:> 138/512 — >>15995 и >>188334/64> 148/512 — >>3380 и >>95208/64> Например, https://ii.yakuji.moe/b/src/1673088974447.jpg, 1674079476839.jpg, 1676900847615.jpg, 1678044547885.jpg, 1695834706311.jpg, 1708415679326.jpg. Хотя меж некоторыми парами картинок из набора прямое расстояние и меньше 100/512, связным граф становится только при предельном прямом расстоянии меж индивидуальными хэшами-нодами 171/512Pic related.Последняя и предпоследняя имеют одинаковый хэш.Вариации/частичные изменения содержимого:> меж >>3681 и >>4892 36/5122/64> >>8513 >>8614 76/5124/64> >>5390 >>5455 116/51226/64> >>6377 >>6379 140/51212/64> >>14826 >>14826(Во втором вы имели в виду >>14827)12/64> >>16709 >>16721 192/51212/64
>>1000858> Пожалуйста, опубликуйте вашу реализацию же.Here. Пример на PHP included. Хотя логичнее уж запускать отдельный процесс каждый раз.Caveats:вся прозрачность переводится в чёрный цвет, это можно поправить;stb_image не умеет в webp, можно взять любую другую библиотеку, на неё ничего не завязано особо;convo в 3-4 раза медленне, чем могло бы быть (separable kernel).Можете скомпилировать в wasm, rewrite it in Rust, сделать аналог для JavaScript, удолить и забыть.Нужна libstb. Компилирвать через ./build.sh. Запускать так:$ ./myphash /tmp/mypics/1572851490178.jpg8976a19847e827f6> Чего? В реализации, которую я здесь опубликовал и про которую говорил в >>/b/1006362, она обнаруживается на расстоянии 0 (равные хэши). Неужели совсем позапускать-посмотреть неинтересно было?Таки где ваша последняя реализация? В >>1000775? А то запутаться можно. Её я и смотрел.
./build.sh
$ ./myphash /tmp/mypics/1572851490178.jpg8976a19847e827f6
> convo в 3-4 раза медленне, чем могло бы быть (separable kernel).Хотя оно уже раза в 2-3 быстрее, чем pHash, который на гитхабе, лол.
>>1000866Вот корректный пик. Gomen.
>>1000868Спасибо, потестирую. Может, тоже попробую поприменять blur.> В >>1000775?Она самая. Но находит пару картинок с той девочкой она на расстоянии 0. У вас не так?
Хочу добавить, что мой код сам реализует извлечение luma-канала и масштабирование. То есть после того, как у нас есть массив RGB, никакие зависимости нам не нужны. Это решает проблему с «там разные алгоритмы приведения к grayscale и масштабирования». Его можно скомпилировать в wasm или даже написать копию на JavaScript.>>1000872> Она самая. Но находит пару картинок с той девочкой она на расстоянии 0. У вас не так?Конкретно на этой не запускал. Ориентировался на то, что вы написали. А оказалось, что это устарело.
>>1000856Потестил вашу реализацию по всем JPEG и PNG из своих булочных картинок. Было бы неплохо, если бы выложили список, по которому тестируете, чтобы синхронизировать. CSV с результатами моей и вашей реализации в архиве.На расстоянии 0 находятся ещё два "false positive" про незначительные вариации, кроме вами упомянутых: (>>/b/2996, >>/b/2997) и (>>/b/8825, >>/b/18633).Первые серьёзные false positive появляются на расстоянии 6: (>>/b/8242, >>/b/16575), (>>/b/3908, >>/b/23698). Всего 2 таких пары среди 12 новых/обновлённых кластеров.А вот на расстоянии 8, из 30 свежих кластеров где-то половина — пары нерелевантных картинок.Моя реализация на длине хэша в 64 бита (threshold 72) и размере sample'а 32x32 начинает деградировать на тех же 6 и 8 расстояниях. Однако на расстоянии 0 находит больше дубликатов: например (>>/b/8637, >>/b/25626), (>>/b/18011, >>/b/18312) у меня 0/64, у вас 4/64. Количество недубликатов на расстоянии 0 такое же, но недубликаты немного другие: (>>/b/11092, >>/b/11456), (>>/b/6224, >>/b/6226) и (>>/b/3154, >>/b/3161) вместо (>>/b/2996, >>/b/2997), (>>/b/7767, >>/b/19060) и (>>/b/8825, >>/b/18633).На больших расстояних какая-то совсем большая разница в количестве найденного, но там уже совсем месиво по большей части, скорее всего, так что я не смотрел.В целом, поскольку уже на расстоянии 8/64 где-то половина найденного — лабуда, но не без интересностей, я и начал пробовать большие длины хэша и размерности сэмпла: при длине в почти 512 бит (threshold 617) и размере сэмпла 128x128 первый серьёзный false positive находится на 172/512, и до его наступления интересных похожестей находится больше, чем до 8/64 включительно. Поэтому хочу сделать phash не в 8, а в 64 байта а-ля SHA-512.>>1000868Ещё, такая заметка. Покуда нам нужно только левое верхнее 8x8 окно из 32x32 DCT матрицы, не обязательно считать всю 32x32 матрицу, а можно посчитать только это окно, сделав матрицы коэффицентов прямоугольными: 8x32 и 32x8--транспонированная. (32 * 32 * 32 * 2) / (32 * 32 * 8 + 32 * 8 * 8) — в 6.4 раза быстрее расчёт будет, когда окно меньше sample'а по стороне в 4 раза. Although, по сравнению со scale'ингом это, наверное, мелочь.
> Было бы неплохо, если бы выложили список, по которому тестируете, чтобы синхронизировать. CSV с результатами моей и вашей реализации в архиве.В архиве (misc/*). Названия у вас, скорее всего, другие, поэтому сделал csv md5,filename.> На расстоянии 0 находятся ещё два "false positive" про незначительные вариации, кроме вами упомянутых:Понятно. Ну, я чисто визуально смотрел. Мне кажется, что если человек не может увидеть разницу на 150x150 — то phash тем более не может/не должен/не предназначен для такого.Это был просто proof-of-concept, что можно без сложностей написать оригинальный phash. Идей для улучшения у меня много, но мне сначала нужно написать/улучшить свои скрипты для визуализации. И было бы хорошо, если бы у нас был единый список «как-то связаных между собой» картинок, которые должны находиться. Тогда можно будет автоматически оценивать.Замена nearest-neighbour interpolation на bicubic сделала только хуже. А вот изменение размера блюра с 7 до 11 — сделала лучше (меньше Hamming distance у «как-то связанных между собой» картинок). А если выставить HASH_TYPE => HASH_TYPE_RADIAL (при тех C=>11, S=32, S1=>8), получается ещё лучше. Даже некоторые масштабированные находятся на расстоянии 0, которые раньше не. Каких-то false positives, которые не связаны между собой очевидным образом, нет.Там в архиве новая версия. Можно играться с параметрами в core/params.h. Например, вашему будет примерно соответствовать C => 1, S => 128, S1 => 23, HASH_TYPE => HASH_TYPE_RADIAL. Только у меня масштабирование через nearest-neighbour interpolation, а у вас, вроде бы, lanczos3?> Although, по сравнению со scale'ингом это, наверное, мелочь.Scaling тоже мелочь (оно ведь O(S^2)). Узкое место — в свёртке (блюре). И оно сейчас O(W * H * C^2), а можно сделать O(W * H * C). Только пока руки не доходят.Пора открывать /dev/?
misc/*
md5,filename
core/params.h
O(S^2)
O(W * H * C^2)
O(W * H * C)
>>1000894> Даже некоторые масштабированные находятся на расстоянии 0, которые раньше неПример.
>>1000894> спойлерКапча согласна.А надо ли? Devочкотреда хватит наверное.
Как ваши дела, Аоба-сан?>>1000894> сделал csv md5,filenameПринято. Я решил исключить дубликаты, для которых RGB пикселей целиком совпадают, оставив только один экземпляр. Иначе слишком много Ханю.> А если выставить HASH_TYPE => HASH_TYPE_RADIAL (при тех C=>11, S=32, S1=>8), получается ещё лучшеКогда R2_THRESHOLD = 64, хэш получается длиной 57 битов. Возможно, из-за меньшей длины.Чтобы входило то же количество AC, то есть 64, нужно S1=10 и R2_THRESHOLD=72. Если соответствующим образом обновить parameters.h и phash_of_dct.h, то в вашей реализации (C => 7) не получается результатов, как в моей с тем же threshold.Тестировалось только по JPG и PNG.Для aoba_phash_32_8_8 из недубликатов на расстоянии 0 ранее не был упомянут (1871, 1872): https://014.yakuji.moe/b/res/25313.html#25341 и рядом. ID перед ':' не совпадают с ID поста. Сделать совпадающими?Для aoba_phash_32_72 из недубликатов на расстоянии 0 оказывается (16614, 16658)[>>/b/7767], (4666, 12320)[>>/b/8825], (13602, 16347)[>>/b/5852], (4886, 16708), (2237, 17041).do_round_phash.php 32 72 находит больше дубликатов на расстоянии 0. Недубликаты те же, что описаны в >>1000886.CSV в архиве.> Только у меня масштабирование через nearest-neighbour interpolation, а у вас, вроде бы, lanczos3?Насколько я понимаю, lancoz3. Хотя я ещё буду опробывать shrink-on-load фичи + lancoz3.Также, у меня DC не участвует в хэше, а у вас, хотя не участвует в выборе медианы, в расчёте хэша участвует.Меня libvips привлекает помимо матричной арифметики тем, что там реализовано последовательное чтение картинки для форматов, которые это поддерживают: не нужно грузить всю картинку в память. Поэтому я хочу использовать его. BTW, я как-то пропустил это обстоятельство, но есть wasm-libvips. https://www.libvips.org/2020/09/01/libvips-for-webassembly.html Где-то 5 MiB весит. И тогда можно его же на клиенте крутить.
> Ну, я чисто визуально смотрел.Так я тоже. Только не на 150x150 thumbnail'ы, а на оригиналы или их уменьшенные до вмещения в 15.6'' QHD монитор копии.> Мне кажется, что если человек не может увидеть разницу на 150x150 — то phash тем более не может/не должен/не предназначен для такого.So desu ka? Как по мне, phash всё-таки про картинки, а не про их уменьшенные копии, хоть те и берутся для его вычисления. Да и thumbnail'ы у нас не 150x150, а 200x200 на страницах.
> нужно S1=10 и R2_THRESHOLD=72> S1=10На самом деле нет и достаточно S1=9.
> lancoz3Это ж надо было дважды имя Ланцоша так искаверкать. Прошу прошу прощения.
>>1000912Аобе нужен ещё день-два на эксперименты, десу.1. Осозналось, что не нужно считать свёртку (будь то box blur, Gaussian blur или что-либо ещё) в каждом пикселе исходного изображения. Если у нас nearest-neighbor или lanczos, там вообще очень мало точек, в которых нужно её посчитать.2. Появилась гипотеза, что лучше делать Gaussian blur. Причём прямоугольный и с динамически выбираемыми размерами свёртки. Ну, 1024x1024 нужно блюрить с большим радиусом, чем 32x32.3. Пробоваю dhash (difference hash). Но там свои особенности: если использовать Hamming distance, то там большой кластер в районе <все нолики> будет. Ну, в теории ещё и во <все единички>, но на практике последнего кластера не образуется. В pgvector для битовых векторов, кроме Hamming distance есть ещё Jaccard distance. И вот тут уже оно работает хорошо. Только оно не спасает от теоретического кластера в районе <все единички>, нужно просто инвертировать хэш, если единичек получилось >66%.4. Внезапно, Jaccard distance с phash тоже хорошо работает. По первым впечатлениям даже лучше, чем Hamming distance. Но нужно пристальнее смотреть.5. Можно считать хэш от медианы от несколько меньшего квадрата/четверть-окружности и приписывать его копию несколько раз.Это только то, что уже закожено.
>>1000918> Внезапно, Jaccard distance с phash тоже хорошо работаетВ случае с нашим медианным phash, jaccard_distance — это монотонно возрастающая функция от hamming_distance. Поэтому работает в точности также.jaccard_distance(a, b) = hash_length - hash_length * popcnt(a & b) / popcnt(a | b)= hash_length * (popcnt(a | b) - popcnt(a & b)) / popcnt(a | b)= hash_length * popcnt(a ^ b) / popcnt(a | b)= popcnt(a ^ b) * hash_length / popcnt(a | b)= popcnt(a ^ b) * hash_length / {[popcnt(a) + popcnt(b) + popcnt(a ^ b)] / 2}= popcnt(a ^ b) * hash_length / {[hash_length / 2 + hash_length / 2 + popcnt(a ^ b)] / 2}..потому что половина битов в a и в b еденица, потому что хэш по медиане.Получаемjaccard_distance(a, b)= 2 * popcnt(a ^ b) * hash_length / [hash_length + popcnt(a ^ b)]= {2 * popcnt(a ^ b) * hash_length + 2 * hash_length**2 - 2 * hash_length**2} / [popcnt(a ^ b) + hash_length]= -2 * hash_length**2 / [hamming_distance(a, b) + hash_length] + 2 * hash_lengthТакая вот прямая зависимость.
jaccard_distance(a, b) = hash_length - hash_length * popcnt(a & b) / popcnt(a | b)= hash_length * (popcnt(a | b) - popcnt(a & b)) / popcnt(a | b)= hash_length * popcnt(a ^ b) / popcnt(a | b)= popcnt(a ^ b) * hash_length / popcnt(a | b)= popcnt(a ^ b) * hash_length / {[popcnt(a) + popcnt(b) + popcnt(a ^ b)] / 2}= popcnt(a ^ b) * hash_length / {[hash_length / 2 + hash_length / 2 + popcnt(a ^ b)] / 2}
jaccard_distance(a, b)= 2 * popcnt(a ^ b) * hash_length / [hash_length + popcnt(a ^ b)]= {2 * popcnt(a ^ b) * hash_length + 2 * hash_length**2 - 2 * hash_length**2} / [popcnt(a ^ b) + hash_length]= -2 * hash_length**2 / [hamming_distance(a, b) + hash_length] + 2 * hash_length
> прямая зависимостьИли гиперболическая. Не знаю, как правильно.
ping -c1 yakui-lover (regarding PRs).>>1000919Correct. Проблема была в моей методика подсчёта порога, когда появляются false positives. И зависимость действительно нелинейная.Справедливости ради, в результате DCT могут получиться равные элементы. Но на ычанских картинках такое бывает, только если изображения полностью чёрные — там получаются все нули, и хэш — все нолики.
>>1000921Не совсем related, но как вариант, можно попробовать dhash не по самой картинке, а по DCT. И по нему уже Jaccard distance.>>1000918> Можно считать хэш от медианы от несколько меньшего квадрата/четверть-окружности и приписывать его копию несколько раз.Тоже пробовал было делать для phash подобное, и увы у меня толку особо не вышло, если вышло вообще. В round_phash.php именно посекторный обход в частности в связи с этими попытками.
Лучше всего себя показывает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]с threshold в 36 бит (не overfitting, вычислено как 0.6 × когда_количество_дубликатов_начинает_превышать_80k).Конфигурация должна быть понятна, кроме gauss_dyn[alpha=0.8,beta=4]. Там по каждой размерности вычисляется sigma = size_orig / size_scaled * alpha, kernel_size = ceil(sigma * beta) | 1. Здесь | 1 значит «прибавить 1, если чётное».Оно находит всё то, что вы привели в качестве примеров того, что не находит, хотя получено в результате scaling/padding/crop.Вот примеры из ычанских картинок.Когда найду время, постараюсь причесать код и выложить.>>1000923> Не совсем related, но как вариант, можно попробовать dhash не по самой картинке, а по DCT. И по нему уже Jaccard distance.Тогда уж кодировать знак разницы между соседними в zig-zag order в матрице, которая результат DCT. Но оно показывает себя чуть хуже.
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]
gauss_dyn[alpha=0.8,beta=4]
sigma = size_orig / size_scaled * alpha
kernel_size = ceil(sigma * beta) | 1
| 1
Ах да, на этой конфигурации false positives нет. А для gauss_dyn установлен максимум на kernel_size в 255. Там всё равно узкое место в декодировании. Даже с нормальными библиотеками.
>>1000921Сегодня посмотрю.
>>1000926codeberg лежит \shrug
codebernd
>>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 ...