Когда то давно на просторах интернета читал статью о генерации по настоящему случайного пароля. Суть сводилась к тому что для реализации рандома нужно натурально бросать игральные кости. Отличная идея, для небольшого pet проекта и для того чтобы проникнуть в основы ML.
Попробуем научить компьютер бросать настоящие кости, находить их на изображении с веб камеры и понимать что на них выпало. И так, из подручных материалов делаем стенд для бросания костей.
Я выбрал двадцатигранные кости, хотя это не принципиально.
Подключаем aduino к драйверу и соленоиду тормоза, Далее arduino слушает команды на rs232, отключает тормоз и включает двигатель либо наоборот.
скетч
int drive = 11;
int brake = 10;
void setup()
{
Serial.begin(9600);
Serial.setTimeout(5);
}
void loop()
{
if (Serial.available())
{
int val = Serial.parseInt();
if (val == 123) {
digitalWrite(brake, LOW);
digitalWrite(drive, HIGH);
}
if (val == 234) {
digitalWrite(brake, HIGH);
digitalWrite(drive, LOW);
}
}
}
Для начала нужно создать датасет. На любом языке делаем программку которая отправляет на RS232 команды бросить кости, а затем сохраняет кадр с камеры. Получаюем такие картинки:
Делаем разметку. Для этого я накидал программку, которая по координатам трех точек строит окружность, находит координаты ее центра. Далее кликаем мышкой в углы костей, и сохраняем вместе с именем файла в csv. Но после разметки 700 картинок я понял, надо что то менять.
Зайдем с другой стороны. На размеченных картинках вырезаем круглые области с костями, сохраняем их в png, так как нам не нужно все что вне окружности, сразу раскладываем по папкам в соответствии с выпавшими цифрами. Делаем несколько фоток без костей. Далее просто размещаем в случайных местах на фоновой картинке 3 случайные кости. Тут нужно учесть что кости не могут пересекаться и должны находится внутри стакана.
Таким образом создаем 100000 картинок, сохраняя разметку.
Не забываем про главную формулу ML: shit in = shit out
Поэтому оценим получившийся датасет при помощи простой модельки на базе Xception.
baseline
base_model = Xception(weights='imagenet', include_top=False, input_shape = [480, 640, 3])
base_model.trainable = True
#Устанавливаем новую «голову» (head):
x = base_model.output
x = GlobalAveragePooling2D()(x) #Pooling слой
x = BatchNormalization()(x) #добавим Batch нормализацию
x = Dense(256, activation='relu')(x) # полносвязный слой с активацией relu
x = Dropout(0.25)(x) # полносвязный слой с вероятность отключения нейронов в слое
output = Dense(6, name=out_key)(x)
model = Model(inputs=base_model.input, outputs=output)
На выходе модели 6 чисел, соответствующих координатам центров костей. Проверил на реальных картинках, процентов 80 распозналось как то так:
остальные как то так:
Вывод: синтетический датасет вполне пригоден для обучения. Далее обучим Yolo3. За основу возьмем эту реализацию. Реализаций много, но тех которые работают из коробки мало.
Результат: мы молодцы, обучили классную модель, которая классно находит кости и... все. А что с ней делать дальше? Как ее "установить" своей бабушке?
Нужно дружить ее, например с C# и делать нормальное приложение с юзерфрендли интерфейсом. Есть несколько вариантов чтобы подружить модель с C#. Рассмотрим ONNX. Итак, конвертируем модель, в в формат onnx. Далее смотрим в гугле или ютубе туториал например этот. Пробуем повторить и... код не работает. Но работоспособность кода запечетлена на видео! Смотрим очень внимательно и устанавливаем именно те версии библиотек. Теперь работает.
Но модель ничего не видит. Предполагаем что C# скармливает картинку сетке не так как Python. Проверим.
Для этого сделаем маленькую сетку, которая будет принимать на вход картинку 3*3 а на выход просто выдавать 27 цифр соответствующих цветам пикселей.
тестовая модель
input = Input(shape=[IMG_SIZE, IMG_SIZE, IMG_CHANNELS], name='image')
output = Flatten()(input)
model = tf.keras.models.Model(input, output)
Подадим ей на вход синюю картинку в Python и C#, сравним результаты:
Видим что в отличие от Python`а C# извлекает сначала все байты одного цвета, потом второго и третьего.
Укажем в экстракторе пикселей что не надо так, а заодно укажем правильный порядок цветов.
код
...
.Append(context.Transforms.ExtractPixels(outputColumnName: "image",
orderOfExtraction: ImagePixelExtractingEstimator.ColorsOrder.ABGR,
colorsToExtract: ImagePixelExtractingEstimator.ColorBits.Rgb,
interleavePixelColors: true
))
Ну вот теперь, модель видит все как положено. Вернемся к версии библиотек. Если верить тому что тут написано микрософт решила убрать поддержку Bitmap, потому что эта сущность есть только в виндовс. В замен предлагают использовать MLImage. Обожаю когда авторы меняют интерфейсы. Давайте попробуем. И когда мы передаем модельке картинку загруженную из файла: MLImage.CreateFromFile(String)
то проблем действительно нет.
Но мы хотим вебкамеру, в реальном времени, еще и не просто смотреть а рисовать в каждом кадре. В гугле много примеров как работать с вебкой при помощи Emgu.CV. И что больше всего подкупает они работают без танцев с бубном.
Кадры с вебкамеры Emgu.CV извлекает в обьекты тыпа Mat. По сути это просто матрица, в нашем случае байтов. MLImage можно создать из линейного массива байтов: CreateFromPixels(Int32, Int32, MLPixelFormat, ReadOnlySpan<Byte>).
Вытягиваем наш Mat и пробуем создать MLImage.
код
Mat m = new Mat();
webcam.Retrieve(m);
Bitmap img = m.ToImage<Bgr, byte>().ToBitmap();
byte[] barray = new byte[img.Width*img.Height*3];
m.CopyTo(barray);
MLImage image = MLImage.CreateFromPixels(img.Width, img.Height, MLPixelFormat.Bgra32, barray);
Bitmap здесь создается только для вывода в pictureBox. Запускаем и модель ничего не видит. Снова смотрим как передаются данные, и проблема в том что формат пикселя у MLImage всегда содержит альфа слой, а Mat с камеры приходит без него. Добавляем альфу:
код
Mat m = new Mat();
webcam.Retrieve(m);
Bitmap img = m.ToImage<Bgr, byte>().ToBitmap();
CvInvoke.CvtColor(m, m, ColorConversion.Bgr2Bgra);
byte[] barray = new byte[img.Width*img.Height*4];
m.CopyTo(barray);
MLImage image = MLImage.CreateFromPixels(img.Width, img.Height, MLPixelFormat.Bgra32, barray);
и получаем то к чему стремились:
PS. Большая часть кода в проекте взята из указанных источников почти без изменений, либо с незначительной подгонкой, здесь я указал лишь не очевидные моменты. Если будут интересны подробности, пишите.
Комментарии (15)
MrGrod
00.00.0000 00:00+1Осталось встроить в корпус ПК как "заводское решение" и вызывать каждый раз при вызове random.random :D
ZiggiPop
00.00.0000 00:00+5Когда-то в детстве, когда с увлечением читал книги Мартина Гарднера, Якова Перельмана и Петра Маковецкого, решил заморочиться и пять тысяч раз подбросил пару старых зариков из нардов. Надо сказать, в упор не помню, что именно меня сподвигнуло в этих книгах на такую "проверку". В результате обнаружил, что один из кубиков выбрасывает пятерку примерно чуть меньше, чем в 18% бросков, а двойку — не более 14, а второй вообще выбрасывает шестерку более чем в 20%! В одном кубике была небольшая едва заметная капелька воздуха, а у второго был сколот угол. С тех пор к абсолютной "случайности" бросков кубика отношусь немного скептично.
Любопытно, что поначалу распределение было примерно по 16-17 процентов, как и должно было быть, но резко начало "сползать" после 3 тысяч бросков. Думаю, что сейчас бы было интересно проверять качество кубиков на вашей шайтан-машине )
ARad
00.00.0000 00:00-2Вы же понимаете что на самом деле у вас не получатся идеально случайные числа.
Современные процессоры поддерживают инструкцию генерации случайного числа https://en.wikipedia.org/wiki/RDRAND.
Также можно использовать сервисы в интернете, например https://qrng.anu.edu.au/.
Tarakanator
00.00.0000 00:00ок, машина вам выдала абсолютно случайные X,Y,Z.
Как вы составляете из них число?
XYZ? XZY? ZYX? как вы выбираете вариант?EgorovM
00.00.0000 00:00выбираем случайное из этих трех, получается рандом в квадрате!
Tarakanator
00.00.0000 00:00Если бы мы могли выбрать случайное число, то эту установку бы не городили.
EgorovM
00.00.0000 00:00можем еще раз кинуть кубики на шайтан-машине, чтобы выбрать порядок для первого броска
Tarakanator
00.00.0000 00:00Т.е. из первого броска мы не можем вытащить случайное число, а из второго сможем?
rutexd
00.00.0000 00:00Да.
Во втором броске псевдослучайно читаем числа справа налево или слева направо, потом в зависимости от того какое число вышло - применяем такой же алгоритм для первого броска. Например 213 312 321 итд. Это уже почти что квантовый рандом)))
Kwent
00.00.0000 00:00Убираем эту неопределенность любым детерминированным алгоритмом (сортировка по координатам, сумма, умножение), получаем нормальное случайное число, вроде как
ykx3hr Автор
00.00.0000 00:00На самом деле ни как, Yolo сама выбирает последовательность объектов которые она обнаружила. Целью этого проекта и не было генерировать что-то идеальное, я просто взял идею которая была когда то давно озвучена здесь же на habr`е и построил для себя некий пайплайн, от обученной модели к ее применению в "повседневной жизни". Результатами решил поделится.
kichrot
Блин, ну когда уже закончится эта неграмотность в логике и базовых научных понятиях???
Риторический вопрос автору статьи: Что такое "по-настоящему случайный пароль"??? :)
Видимо автор статьи считает, что некая "абсолютная случайность", это отсутствие любой закономерности и детерминизма, т.е. это абсолютный произвол. :)
Видимо автор статьи не знает, что наука определяет понятие "случайность", как непознанную и опосредованную закономерность, которую возможно выявить путем статистической обработки, достаточного количества данных, повторяющегося опыта:
СЛУЧАЙНОСТЬ – философская категория, выражающая один из предельных видов (классов) взаимосвязей и взаимоотношений в мире, характеризующийся отсутствием прямых закономерных связей в поведении и функционировании объектов и систем.
Другими словами, случайность, это непознанная закономерность. Т.е. любое событие случайно для человека незнающего закономерность предшествующих событий к этому событию приводящих.
Например, генератор последовательности натуральных чисел от нуля до бесконечности, для человека незнакомого с арифметикой и принятой системой обозначения цифр, выдает последовательность случайных знаков, "по-настоящему случайных". :)
dabrahabra
Для вас может показаться случайностью, но разработчики знают, что в большинстве ЯП и C# в том числе, базовый генератор случайных чисел на самом деле псевдослучайный.
ARad
На данный момент некоторые квантовые процессы считаются абсолютно случайными, даже в теории.