ocelli as library, better capture algorithm, high resolution

This commit is contained in:
randogoth 2024-12-30 18:14:12 +02:00
parent 700f2770b6
commit 424acf0858
4 changed files with 288 additions and 277 deletions

View file

@ -3,6 +3,11 @@ name = "ocelli"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[lib]
name = "ocelli"
path = "src/lib.rs"
[dependencies] [dependencies]
chrono = "0.4.39" v4l = "0.14"
opencv = "0.93.5" chrono = "0.4"
image = "0.25.5"

144
src/bin/main.rs Normal file
View file

@ -0,0 +1,144 @@
use ocelli::Ocelli;
use v4l::prelude::*;
use v4l::video::Capture;
use v4l::buffer::Type;
use v4l::format::FourCC;
use v4l::io::traits::CaptureStream;
use image::ImageReader;
use std::fs::File;
use std::io::{stdin, Write};
use chrono::Local;
use std::time::Instant;
fn frame_to_grayscale(data: &[u8]) -> Vec<u8> {
let img = ImageReader::new(std::io::Cursor::new(data))
.with_guessed_format()
.expect("Failed to guess format")
.decode()
.expect("Failed to decode image");
let gray = img.into_luma8(); // Convert to grayscale
gray.into_raw() // Return raw pixel data as Vec<u8>
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
if args.len() < 3 {
eprintln!("Usage: {} <camera index> <entropy length in bytes>", args[0]);
std::process::exit(1);
}
// Check if the whitening flag is set
let quick = args.contains(&String::from("-q"));
let camera_index: usize = args[1].parse().expect("Failed to parse camera index as a number");
let length: usize = args[2].parse().expect("Failed to parse entropy length as a number");
let dev = Device::new(camera_index).expect("Failed to open camera");
// Set the desired format and resolution
let mut format = dev.format().expect("Failed to get camera format");
format.fourcc = FourCC::new(b"MJPG"); // Use MJPG for higher resolutions
format.width = 1920;
format.height = 1080;
if let Err(_) = dev.set_format(&format) {
println!("Failed to set resolution to 1920x1080. Falling back to 1280x720.");
format.width = 1280;
format.height = 720;
dev.set_format(&format)
.expect("Failed to set resolution to 1280x720");
}
println!(
"Using resolution: {}x{} (FourCC: {})",
format.width, format.height, format.fourcc
);
let mut stream = MmapStream::with_buffers(&dev, Type::VideoCapture, 4)
.expect("Failed to create stream");
let ocelli = Ocelli;
let mut total_entropy = Vec::new();
let start_time = Instant::now();
let shannon_threshold = 4.0;
let mut frame_count = 0;
while total_entropy.len() < length {
// Capture first frame
let (data1, _) = stream.next().expect("Failed to capture frame");
let grayscale_data1 = frame_to_grayscale(&data1);
if ocelli.is_covered(&grayscale_data1, 50) {
// Skip the first 10 frames
while frame_count < 10 {
let _ = stream.next().expect("Failed to capture frame");
frame_count += 1;
}
let entropy: Vec<u8>;
if quick {
// Quicker capture using Pick and Flip
entropy = ocelli.pick_and_flip(&grayscale_data1, frame_count as usize);
} else {
// Capture second frame
let (data2, _) = stream.next().expect("Failed to capture second frame");
let grayscale_data2 = frame_to_grayscale(&data2);
// Generate entropy using chop_and_tack
entropy = ocelli.chop_and_tack(&grayscale_data1, &grayscale_data2, format.width as usize, 30);
}
let shannon_entropy = ocelli.shannon(&entropy);
if shannon_entropy >= shannon_threshold {
total_entropy.extend(entropy);
println!(
"Collected {} of {} bytes of entropy (Shannon entropy: {:.3})",
total_entropy.len(),
length,
shannon_entropy
);
} else {
println!("Rejected entropy (Shannon entropy: {:.3})", shannon_entropy);
}
} else {
println!("Camera is not covered. Please cover the camera.");
frame_count = 0;
}
}
// Trim total_entropy to the exact length
total_entropy.truncate(length);
// Print elapsed time
let elapsed_time = start_time.elapsed();
println!(
"Process completed in {:.3} seconds.",
elapsed_time.as_secs_f64()
);
// Final Shannon entropy test
let final_shannon_entropy = ocelli.shannon(&total_entropy);
println!("Final Shannon entropy: {:.3}", final_shannon_entropy);
// Ask if the result should be saved
println!("Save result to a file or print as hex string? (file/print):");
let mut input = String::new();
stdin().read_line(&mut input).expect("Failed to read input");
if input.trim().eq_ignore_ascii_case("file") {
let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string();
let filename = format!("entropy_{}.bin", timestamp);
let mut file = File::create(&filename).expect("Failed to create file");
file.write_all(&total_entropy).expect("Failed to write data to file");
println!("Entropy saved to file: {}", filename);
} else if input.trim().eq_ignore_ascii_case("print") {
let entropy_hex: String = total_entropy.iter().map(|b| format!("{:02x}", b)).collect();
println!("Generated entropy (hex): {}", entropy_hex);
}
Ok(())
}

137
src/lib.rs Normal file
View file

@ -0,0 +1,137 @@
use std::collections::{HashMap, HashSet};
pub struct Ocelli;
impl Ocelli {
pub fn chop_and_tack(&self, current: &Vec<u8>, previous: &Vec<u8>, width: usize, minimum_distance: usize) -> Vec<u8> {
let mut entropy = Vec::new();
let mut current_byte = 0u8;
let mut bit_count = 0;
// Calculate the height of the frame
let height = current.len() / width;
// Skip 100 pixels at the top and bottom of the frame
let start_row = 100;
let end_row = height - 100;
if start_row >= end_row || width <= 200 {
panic!("Resolution is too small to apply the grid selection with the given offset.");
}
for row in start_row..end_row {
// Skip 100 pixels at the left and right of the frame
let row_start = row * width + 100;
let row_end = (row + 1) * width - 100;
// Select pixels in the row based on the step size
for pixel_index in (row_start..row_end).step_by(minimum_distance) {
if pixel_index >= current.len() || pixel_index >= previous.len() {
continue;
}
let c = current[pixel_index];
let p = previous[pixel_index];
if c > p {
current_byte = (current_byte << 1) | 1; // Append '1'
} else if c < p {
current_byte = current_byte << 1; // Append '0'
} else {
continue; // Skip if equal
}
bit_count += 1;
if bit_count == 8 {
entropy.push(current_byte);
current_byte = 0;
bit_count = 0;
}
}
}
entropy
}
pub fn pick_and_flip(&self, data: &[u8], current_frame_index: usize) -> Vec<u8> {
let mut entropy = Vec::new();
let mut current_byte = 0u8;
let mut bit_count = 0;
for &pixel_brightness in data {
if (2..=253).contains(&pixel_brightness) {
let mut lsb = pixel_brightness & 1;
if current_frame_index % 2 == 0 {
lsb ^= 1; // Flip the bit
}
current_byte = (current_byte << 1) | lsb;
bit_count += 1;
if bit_count == 8 {
entropy.push(current_byte);
current_byte = 0;
bit_count = 0;
}
}
}
entropy
}
pub fn shannon(&self, data: &Vec<u8>) -> f64 {
let mut frequency_map = HashMap::new();
let data_len = data.len();
for &byte in data {
*frequency_map.entry(byte).or_insert(0) += 1;
}
frequency_map.values().fold(0.0, |entropy, &count| {
let probability = count as f64 / data_len as f64;
entropy - probability * probability.log2()
})
}
pub fn whiten(&self, entropy: &[u8]) -> Vec<u8> {
let mut whitened_entropy = Vec::new();
let mut current_byte = 0u8;
let mut bit_count = 0;
for byte in entropy {
for i in (0..8).step_by(2) {
let bit1 = (byte >> (7 - i)) & 1;
let bit2 = (byte >> (6 - i)) & 1;
match (bit1, bit2) {
(0, 1) => {
current_byte = (current_byte << 1) | 0;
bit_count += 1;
}
(1, 0) => {
current_byte = (current_byte << 1) | 1;
bit_count += 1;
}
_ => {}
}
if bit_count == 8 {
whitened_entropy.push(current_byte);
current_byte = 0;
bit_count = 0;
}
}
}
whitened_entropy
}
pub fn is_covered(&self, grayscale: &[u8], threshold: usize) -> bool {
let unique_values: HashSet<_> = grayscale.iter().copied().collect();
// println!("UV: {:?}", unique_values);
unique_values.len() < threshold
}
}

View file

@ -1,275 +0,0 @@
use opencv::prelude::*;
use opencv::videoio::{VideoCapture, CAP_V4L};
use opencv::imgproc;
use opencv::core;
use std::collections::HashSet;
use std::collections::HashMap;
use std::env;
use std::time::Instant;
use std::fs::File;
use std::io::Write;
use chrono::Local;
struct Ocelli;
impl Ocelli {
fn chop_and_tack(&self, current: &Vec<u8>, previous: &Vec<u8>, width: usize, minimum_distance: usize) -> Vec<u8> {
let mut entropy = Vec::new();
let mut current_byte = 0u8;
let mut bit_count = 0;
// Calculate the height of the frame
let height = current.len() / width;
// Skip 100 pixels at the top and bottom of the frame
let start_row = 100;
let end_row = height - 100;
if start_row >= end_row || width <= 200 {
panic!("Resolution is too small to apply the grid selection with the given offset.");
}
for row in start_row..end_row {
// Skip 100 pixels at the left and right of the frame
let row_start = row * width + 100;
let row_end = (row + 1) * width - 100;
// Select pixels in the row based on the step size
for pixel_index in (row_start..row_end).step_by(minimum_distance) {
if pixel_index >= current.len() || pixel_index >= previous.len() {
continue;
}
let c = current[pixel_index];
let p = previous[pixel_index];
if c > p {
current_byte = (current_byte << 1) | 1; // Append '1'
} else if c < p {
current_byte = current_byte << 1; // Append '0'
} else {
continue; // Skip if equal
}
bit_count += 1;
if bit_count == 8 {
entropy.push(current_byte);
current_byte = 0;
bit_count = 0;
}
}
}
entropy
}
fn pick_and_flip(&self, data: &Vec<u8>, current_frame_index: usize) -> Vec<u8> {
let mut entropy = Vec::new();
let mut current_byte = 0u8;
let mut bit_count = 0;
// Iterate through each pixel in the array
for &pixel_brightness in data {
// Check if the brightness is within the valid range [2, 253]
if (2..=253).contains(&pixel_brightness) {
// Extract the least significant bit (LSB)
let mut lsb = pixel_brightness & 1;
// If the frame index is even, flip the bit
if current_frame_index % 2 == 0 {
lsb ^= 1; // Flip the bit (0 -> 1, 1 -> 0)
}
// Add the bit to the current byte
current_byte = (current_byte << 1) | lsb;
bit_count += 1;
// If we have 8 bits, push the byte to the entropy vector
if bit_count == 8 {
entropy.push(current_byte);
current_byte = 0;
bit_count = 0;
}
}
}
entropy
}
/// Apply Van Neumann whitening to a vector of entropy bits
fn whiten(&self, entropy: &Vec<u8>) -> Vec<u8> {
let mut whitened_entropy = Vec::new();
let mut current_byte = 0u8;
let mut bit_count = 0;
// Iterate through the entropy bytes and process bits in pairs
for byte in entropy {
for i in (0..8).step_by(2) {
let bit1 = (byte >> (7 - i)) & 1;
let bit2 = (byte >> (6 - i)) & 1;
// Apply Van Neumann rules
match (bit1, bit2) {
(0, 1) => {
current_byte = (current_byte << 1) | 0; // Append '0'
bit_count += 1;
}
(1, 0) => {
current_byte = (current_byte << 1) | 1; // Append '1'
bit_count += 1;
}
_ => {} // Discard (0,0) and (1,1) pairs
}
// If we have a full byte, push it to the output
if bit_count == 8 {
whitened_entropy.push(current_byte);
current_byte = 0;
bit_count = 0;
}
}
}
whitened_entropy
}
/// Calculates the Shannon entropy of binary data
fn shannon(&self, data: &Vec<u8>) -> f64 {
let mut frequency_map = HashMap::new();
let data_len = data.len();
for &byte in data {
*frequency_map.entry(byte).or_insert(0) += 1;
}
frequency_map.values().fold(0.0, |entropy, &count| {
let probability = count as f64 / data_len as f64;
entropy - probability * probability.log2()
})
}
/// Determines if the camera is covered based on the unique grayscale values
fn is_covered(&self, grayscale: &Vec<u8>, threshold: usize) -> bool {
let unique_values: HashSet<_> = grayscale.iter().copied().collect();
unique_values.len() < threshold
}
}
fn main() -> opencv::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() < 3 {
eprintln!("Usage: {} <camera index> <entropy length in bytes> [-w]", args[0]);
std::process::exit(1);
}
let camera_index: i32 = args[1].parse().expect("Failed to parse camera index as a number");
let length: usize = args[2].parse().expect("Failed to parse entropy length as a number");
let whiten_flag = args.contains(&String::from("-w"));
let mut cam = VideoCapture::new(camera_index, CAP_V4L)?;
if !cam.is_opened()? {
panic!("Failed to open the camera with index {}", camera_index);
}
// Capture a single frame to determine resolution
let mut frame = core::Mat::default();
cam.read(&mut frame)?;
if frame.empty() {
panic!("Failed to capture a frame.");
}
let width = frame.cols() as usize;
let height = frame.rows() as usize;
println!(
"Camera index: {}\nCamera resolution detected: {}x{}",
camera_index, width, height
);
let ocelli = Ocelli;
// Convert the frame to grayscale and check if the camera is covered
let mut gray_frame = core::Mat::default();
imgproc::cvt_color(&frame, &mut gray_frame, imgproc::COLOR_BGR2GRAY, 0)?;
let grayscale_data = gray_frame.data_bytes().expect("Failed to get grayscale data").to_vec();
let uncovered = !ocelli.is_covered(&grayscale_data, 50);
println!(
"Camera is {}covered.",
if uncovered {
"un"
} else {
""
}
);
let mut total_entropy = Vec::new();
let shannon_threshold = 4.0;
let mut previous_frame_data = grayscale_data.clone();
let start_time = Instant::now();
let mut frame_index = 1;
while total_entropy.len() < length {
cam.read(&mut frame)?;
let mut gray_frame = core::Mat::default();
imgproc::cvt_color(&frame, &mut gray_frame, imgproc::COLOR_BGR2GRAY, 0)?;
let current_frame_data = gray_frame.data_bytes().expect("Failed to get grayscale data").to_vec();
let mut entropy: Vec<u8> = Vec::new();
if uncovered {
entropy = ocelli.pick_and_flip(&current_frame_data, frame_index);
frame_index = frame_index + 1;
} else {
entropy = ocelli.chop_and_tack(&current_frame_data, &previous_frame_data, width, 30);
previous_frame_data = current_frame_data;
}
if whiten_flag {
entropy = ocelli.whiten(&entropy);
}
let shannon_entropy = ocelli.shannon(&entropy);
if shannon_entropy >= shannon_threshold {
total_entropy.extend(entropy);
} else {
println!(
"Rejected entropy array (Shannon entropy: {:.3}). Retrying...",
shannon_entropy
);
}
println!(
"Collected {} of {} bytes of entropy...",
total_entropy.len(),
length
);
}
let total_shannon_entropy = ocelli.shannon(&total_entropy);
let elapsed_time = start_time.elapsed();
println!(
"Process completed in {:.3} seconds.\nShannon Entropy {:.3}.",
elapsed_time.as_secs_f64(),
total_shannon_entropy
);
// Save the generated entropy to a binary file
let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string();
let method = if uncovered { "pick_and_flip" } else { "chop_and_tack" };
let whitened = if whiten_flag { "_whitened" } else { "" };
let filename = format!("{}{}_{}.bin", method, whitened, timestamp);
let mut file = File::create(&filename).expect("Failed to create file");
file.write_all(&total_entropy).expect("Failed to write data to file");
println!("Entropy saved to file: {}", filename);
Ok(())
}