Спасибо, глянул и просмотрел код. Исходное изображение и базисные цвета не подвергаются логарифмированию перед цветовой деконволюцией — а это не соответствует модели формирования интенсивности пикселя в статье. Почему?
Да тут система линейный уравнений, которую мы даже в школе решали, просто записанная в форме линейной алгебры. Для вашей задачи это:
0.66504073 * x + 0.61772484 * y + 0.41968665 * z = R
0.4100872 * x + 0.5751321 * y + 0.70785 * z = G
0.6241389 * x + 0.53632 * y + 0.56816506 * z = B
Где R, G, B — значения цвета пикселя (конкретные числа), а x, y, z — концентрация трех красителей.
Имеем систему из трех линейных уравнений с тремя неизвестными.
(Правда, в статье не RGB, а логарифм RGB, но это все равно конкретные числа)
Спасибо за интересную наводку! Я нашел оригинальную статью («Quantification of histochemical staining by color deconvolution» A. Ruifrok, A.Johnston) и обнаружил несколько важных моментов:
Исходное изображение должно содержать линейные интенсивности R, G и B составляющих. Обычно изображение в современных цифровых камерах проходит постобработку, как минимум — гаммирование (возведение в степень). Причем в общем случае нереально понять, в какую степень была возведена интенсивность пикселя. Конкретно в статье авторы специально выставили гамму в единицу в своих фотоаппаратах. Кроме того, яркости окрашенных пикселей не должны быть пересвечены.
Формула интенсивности яркости пикселя для одного красителя определяется по нелинейному закону Бугера-Ламберта-Бера: Iₓ = I₀ₓ · exp(-A · cₓ), где I₀ₓ — интенсивность света на входе, Iₓ — интенсивность света на выходе, А — концентрация красителя, cₓ — коэффициент поглощения красителя, а маленькая «х» означает конкретный канал цвета (R, G, B). Таким образом, для получения линейной формулы яркости пикселей относительно концентрации красителя необходимо взять логарифм каждого пикселя.
Цвета красителей известны. Каждый пиксель обрабатывается отдельно и независимо от других. По интенсивности каждого канала log(RGB) пикселя находится линейная комбинация концентрации красителей. Никакой особенной магии, простейшая задача линейной алгебры.
Есть хорошая дока, которая помогает понять, что такое цвет и как его принято кодировать: Color FAQ. От базового понимания до конкретных цветовых пространств.
Дрянь не дрянь, зато не приходится платить. Я не могу позволить себе фотошоп, поэтому использую GIMP — вполне подходит для моих задач. Кроме того, фильтры GIMPа открыты, поэтому их легко воспроизвести в коде при необходимости. С офисом та же история — не хочется покупать офис только потому, что иногда надо создать красивый документ.
Там было несколько видов перезарядки: доложить патрон в открытую дырку/дырки барабана (надо срочно выстрелить) или перезарядить все (но долго). Еще надо было следить, чтобы пустая дырка не дошла до патронника, иначе придется лишний раз передернуть затвор.
Как бывший разработчик Метро 2033 скажу, что у нас было много свободы при реализации идей даже на уровне разработчиков. Например, когда достаешь аптечку в бою, как сделал бы немец? Разумеется, он взял бы первый слева шприц, потом второй, потом третий. У нас главный герой хватает первый попавшийся шприц. Вроде мелочь, а приятно.
Другой пример. Однажды дизайнеры и художники оставили модель убойника (такой шестизарядный дробовик с барабаном), велели его имплементировать, а сами уехали в отпуск. Пустой офис, мы с аниматором смотрим — хана, а как же его заряжать, если художники закрыли нижнюю часть барабана. И тут мы все придумали: пусть это будет фича, только надо поскорее все реализовать, чтобы когда все вернутся из отпуска, у нас уже было все готово. Ну не переделывать же такое! Вернулись, сначала схватились за голову, а потом эта идея зашла. Еще надо было убедить представителя западного издателя, который долго повторял, что оружие должно быть как можно проще, иначе американские подростки не поймут.
Во-первых, erode() -> dilate() это морфологическая операция open(). Во-вторых, по возможности надо переиспользовать буферы памяти — это экономит не только память, но и время исполнения, поскольку память уже может находиться в кэше.
2. Вместо
thresh = cv2.bitwise_not(thresh)
лучше писать:
thresh = ~thresh
В сложных выражениях это упрощает понимание. Точно так же NumPy переопределяет побитовые AND (&), OR (|), XOR (^)
3. «каскадом Хаара» — на самом деле это детектор Виола-Джонса с Хааровскими фичами.
Согласен! Причем дьявол кроется в деталях. Велосипедостроитель сначала изобретет очевидную часть алгоритма, а потом упрется в какое-нибудь ограничение, которое он не видел с самого начала, и начинает чесать репу и придумывать костыль к своему велосипеду.
По личному опыту — когда начиная с тридцати лет я стал перепроходить базовые университетские дисциплины по открытым курсам MIT, то как же я был удивлен, что в наших вузах изучают только половину того, что у них считается базой. Я почувствовал себя обкраденным, какой бы предмет не переизучал. В линейной алгебре мы не дошли даже до eigenvalues/eigenvectors, в статистике нам не давали логический вывод. Я не говорю о том, что в американских вузах благодаря рыночному элементу плохие преподы отсеиваются (к ним никто не записывается -> значит они плохо объясняют -> надо заменить).
Где R, G, B — значения цвета пикселя (конкретные числа), а x, y, z — концентрация трех красителей.
Имеем систему из трех линейных уравнений с тремя неизвестными.
(Правда, в статье не RGB, а логарифм RGB, но это все равно конкретные числа)
Другой пример. Однажды дизайнеры и художники оставили модель убойника (такой шестизарядный дробовик с барабаном), велели его имплементировать, а сами уехали в отпуск. Пустой офис, мы с аниматором смотрим — хана, а как же его заряжать, если художники закрыли нижнюю часть барабана. И тут мы все придумали: пусть это будет фича, только надо поскорее все реализовать, чтобы когда все вернутся из отпуска, у нас уже было все готово. Ну не переделывать же такое! Вернулись, сначала схватились за голову, а потом эта идея зашла. Еще надо было убедить представителя западного издателя, который долго повторял, что оружие должно быть как можно проще, иначе американские подростки не поймут.
1. Вместо
лучше писать:
… а еще лучше:
Во-первых, erode() -> dilate() это морфологическая операция open(). Во-вторых, по возможности надо переиспользовать буферы памяти — это экономит не только память, но и время исполнения, поскольку память уже может находиться в кэше.
2. Вместо
лучше писать:
В сложных выражениях это упрощает понимание. Точно так же NumPy переопределяет побитовые AND (&), OR (|), XOR (^)
3. «каскадом Хаара» — на самом деле это детектор Виола-Джонса с Хааровскими фичами.