Compare commits
10
Commits
ef3bd1fb6f
...
c0372a15cb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0372a15cb | ||
|
|
f68fa6f0b7 | ||
|
|
f25e9a00a8 | ||
|
|
7f29c5abbb | ||
|
|
a7372d05c2 | ||
|
|
d550a7e71e | ||
|
|
fd9b027eea | ||
|
|
88a25f3812 | ||
|
|
a0c06ab573 | ||
|
|
b8ae2eaf8f |
No files matched your search
+108
@@ -0,0 +1,108 @@
|
|||||||
|
# Komorebi License
|
||||||
|
|
||||||
|
Version 2.0.0
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
|
||||||
|
In order to get any license under these terms, you must agree
|
||||||
|
to them as both strict obligations and conditions to all
|
||||||
|
your licenses.
|
||||||
|
|
||||||
|
## Copyright License
|
||||||
|
|
||||||
|
The licensor grants you a copyright license for the software
|
||||||
|
to do everything you might do with the software that would
|
||||||
|
otherwise infringe the licensor's copyright in it for any
|
||||||
|
permitted purpose. However, you may only distribute the source
|
||||||
|
code of the software according to the [Distribution License](
|
||||||
|
#distribution-license), you may only make changes according
|
||||||
|
to the [Changes License](#changes-license), and you may not
|
||||||
|
otherwise distribute the software or new works based on the
|
||||||
|
software.
|
||||||
|
|
||||||
|
## Distribution License
|
||||||
|
|
||||||
|
The licensor grants you an additional copyright license to
|
||||||
|
distribute copies of the source code of the software. Your
|
||||||
|
license to distribute covers distributing the source code of
|
||||||
|
the software with changes permitted by the [Changes License](
|
||||||
|
#changes-license).
|
||||||
|
|
||||||
|
## Changes License
|
||||||
|
|
||||||
|
The licensor grants you an additional copyright license to
|
||||||
|
make changes for any permitted purpose.
|
||||||
|
|
||||||
|
## Patent License
|
||||||
|
|
||||||
|
The licensor grants you a patent license for the software that
|
||||||
|
covers patent claims the licensor can license, or becomes able
|
||||||
|
to license, that you would infringe by using the software.
|
||||||
|
|
||||||
|
## Personal Uses
|
||||||
|
|
||||||
|
Personal use for research, experiment, and testing for
|
||||||
|
the benefit of public knowledge, personal study, private
|
||||||
|
entertainment, hobby projects, amateur pursuits, or religious
|
||||||
|
observance, without any anticipated commercial application,
|
||||||
|
is use for a permitted purpose.
|
||||||
|
|
||||||
|
## Fair Use
|
||||||
|
|
||||||
|
You may have "fair use" rights for the software under the
|
||||||
|
law. These terms do not limit them.
|
||||||
|
|
||||||
|
## No Other Rights
|
||||||
|
|
||||||
|
These terms do not allow you to sublicense or transfer any of
|
||||||
|
your licenses to anyone else, or prevent the licensor from
|
||||||
|
granting licenses to anyone else. These terms do not imply
|
||||||
|
any other licenses.
|
||||||
|
|
||||||
|
## Patent Defense
|
||||||
|
|
||||||
|
If you make any written claim that the software infringes or
|
||||||
|
contributes to infringement of any patent, your patent license
|
||||||
|
for the software granted under these terms ends immediately. If
|
||||||
|
your company makes such a claim, your patent license ends
|
||||||
|
immediately for work on behalf of your company.
|
||||||
|
|
||||||
|
## Violations
|
||||||
|
|
||||||
|
The first time you are notified in writing that you have
|
||||||
|
violated any of these terms, or done anything with the software
|
||||||
|
not covered by your licenses, your licenses can nonetheless
|
||||||
|
continue if you come into full compliance with these terms,
|
||||||
|
and take practical steps to correct past violations, within
|
||||||
|
32 days of receiving notice. Otherwise, all your licenses
|
||||||
|
end immediately.
|
||||||
|
|
||||||
|
## No Liability
|
||||||
|
|
||||||
|
***As far as the law allows, the software comes as is, without
|
||||||
|
any warranty or condition, and the licensor will not be liable
|
||||||
|
to you for any damages arising out of these terms or the use
|
||||||
|
or nature of the software, under any kind of legal claim.***
|
||||||
|
|
||||||
|
## Definitions
|
||||||
|
|
||||||
|
The **licensor** is the individual or entity offering these
|
||||||
|
terms, and the **software** is the software the licensor makes
|
||||||
|
available under these terms.
|
||||||
|
|
||||||
|
**You** refers to the individual or entity agreeing to these
|
||||||
|
terms.
|
||||||
|
|
||||||
|
**Your company** is any legal entity, sole proprietorship,
|
||||||
|
or other kind of organization that you work for, plus all
|
||||||
|
organizations that have control over, are under the control of,
|
||||||
|
or are under common control with that organization. **Control**
|
||||||
|
means ownership of substantially all the assets of an entity,
|
||||||
|
or the power to direct its management and policies by vote,
|
||||||
|
contract, or otherwise. Control can be direct or indirect.
|
||||||
|
|
||||||
|
**Your licenses** are all the licenses granted to you for the
|
||||||
|
software under these terms.
|
||||||
|
|
||||||
|
**Use** means anything you do with the software requiring one
|
||||||
|
of your licenses.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Cam's Computer Vision Library
|
||||||
|
|
||||||
|
A basic library of (not remotely production-ready) computer vision
|
||||||
|
functions.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
CCVL is written in Rust and uses Cargo as its build system. The Rust
|
||||||
|
toolchain (including Cargo) is most easily installed with
|
||||||
|
[Rustup](https://rustup.rs/).
|
||||||
|
|
||||||
|
Given that you have a toolchain installed, the library can be built as
|
||||||
|
usual for a Cargo project:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run one of the example binaries to see it in action:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example canny foo.ff foo-edges.ff
|
||||||
|
```
|
||||||
|
|
||||||
|
Only the [farbfeld](https://tools.suckless.org/farbfeld/) image format
|
||||||
|
is supported for input and output at the moment.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright © Camden Dixie O'Brien, available under the Komorebi 2.0.0
|
||||||
|
license. See [LICENSE.md](LICENSE.md) for details.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
mod common;
|
||||||
|
|
||||||
|
use ccvl::kernel::Kernel;
|
||||||
|
use ccvl::*;
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
img: &mut image::Image<image::SRGBA>,
|
||||||
|
path: &Vec<(usize, usize)>,
|
||||||
|
colour: image::SRGBA,
|
||||||
|
) {
|
||||||
|
let n = path.len();
|
||||||
|
for i in 0..n {
|
||||||
|
let (x0, y0) = path[i];
|
||||||
|
let (x1, y1) = path[(i + 1) % n];
|
||||||
|
draw::line(x0, y0, x1, y1, img, colour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn approx(a: f32, b: f32, epsilon: f32) -> bool {
|
||||||
|
(a - b).abs() <= epsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> std::process::ExitCode {
|
||||||
|
common::process_image(|mut img| {
|
||||||
|
let greyscale = img.map(|p| image::Luminance::from(p));
|
||||||
|
let blurred = kernel::gaussian::<5>(1.2).convolve_sat(&greyscale);
|
||||||
|
let thresholded = filter::threshold_auto(blurred);
|
||||||
|
let contours =
|
||||||
|
contour::find(&thresholded, image::Luminance { y: 0.0 });
|
||||||
|
for contour in contours {
|
||||||
|
draw(&mut img, &contour.vertices, match contour.kind {
|
||||||
|
contour::Kind::Outer => colours::GREEN,
|
||||||
|
contour::Kind::Inner => colours::BLUE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
img
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
mod common;
|
||||||
|
|
||||||
|
use ccvl::*;
|
||||||
|
|
||||||
|
fn main() -> std::process::ExitCode {
|
||||||
|
common::process_image(|mut img| {
|
||||||
|
let greyscale = img.map(|p| image::Luminance::from(p));
|
||||||
|
let edges = filter::canny(greyscale);
|
||||||
|
for line in hough::lines(10.0, edges) {
|
||||||
|
let ((x0, y0), (x1, y1)) = endpoints(line, img.extent);
|
||||||
|
draw::line(x0, y0, x1, y1, &mut img, colours::RED);
|
||||||
|
}
|
||||||
|
img
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endpoints(
|
||||||
|
l: hough::Line,
|
||||||
|
lim: image::Extent,
|
||||||
|
) -> ((usize, usize), (usize, usize)) {
|
||||||
|
if l.theta.abs() < 1e-3 {
|
||||||
|
((l.rho as usize, 0), (l.rho as usize, lim.y - 1))
|
||||||
|
} else if l.theta - std::f32::consts::PI / 2.0 < 1e-3 {
|
||||||
|
((0, l.rho as usize), (lim.x - 1, l.rho as usize))
|
||||||
|
} else {
|
||||||
|
let w = lim.x as f32 - 1e-3;
|
||||||
|
let h = lim.y as f32 - 1e-3;
|
||||||
|
|
||||||
|
let ros = l.rho / l.theta.sin();
|
||||||
|
let roc = l.rho / l.theta.cos();
|
||||||
|
let t = l.theta.tan();
|
||||||
|
|
||||||
|
let candidates: [(f32, f32); 4] =
|
||||||
|
[(roc - h * t, h), (0.0, ros), (roc, 0.0), (w, ros - w / t)];
|
||||||
|
let res = candidates
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| in_bounds(p, lim))
|
||||||
|
.map(|(x, y)| (x as usize, y as usize))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if res.len() != 2 {
|
||||||
|
panic!("Unexpected number of endpoints for {:?}", l);
|
||||||
|
}
|
||||||
|
(res[0], res[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_bounds(q: &(f32, f32), lim: image::Extent) -> bool {
|
||||||
|
let x_in = 0.0 <= q.0 && q.0 < lim.x as f32;
|
||||||
|
let y_in = 0.0 <= q.1 && q.1 < lim.y as f32;
|
||||||
|
x_in && y_in
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use crate::*;
|
||||||
|
|
||||||
|
macro_rules! hex {
|
||||||
|
( $value:literal ) => {
|
||||||
|
image::SRGBA {
|
||||||
|
r: (($value >> 16) & 0xff) as f32 / 255.0,
|
||||||
|
g: (($value >> 8) & 0xff) as f32 / 255.0,
|
||||||
|
b: ($value & 0xff) as f32 / 255.0,
|
||||||
|
a: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const BLACK: image::SRGBA = hex!(0x000000);
|
||||||
|
pub const RED: image::SRGBA = hex!(0xff0000);
|
||||||
|
pub const GREEN: image::SRGBA = hex!(0x00ff00);
|
||||||
|
pub const BLUE: image::SRGBA = hex!(0x0000ff);
|
||||||
|
pub const CYAN: image::SRGBA = hex!(0x00ffff);
|
||||||
|
pub const MAGENTA: image::SRGBA = hex!(0xff00ff);
|
||||||
|
pub const YELLOW: image::SRGBA = hex!(0xffff00);
|
||||||
|
pub const WHITE: image::SRGBA = hex!(0xffffff);
|
||||||
+437
@@ -0,0 +1,437 @@
|
|||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum Kind {
|
||||||
|
Outer,
|
||||||
|
Inner,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Contour {
|
||||||
|
pub vertices: Vec<(usize, usize)>,
|
||||||
|
pub children: Vec<usize>,
|
||||||
|
pub parent: Option<usize>,
|
||||||
|
pub kind: Kind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
enum Pixel {
|
||||||
|
Foreground,
|
||||||
|
Background,
|
||||||
|
TracedUp(usize),
|
||||||
|
TracedDown(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
enum Dir {
|
||||||
|
N,
|
||||||
|
NE,
|
||||||
|
E,
|
||||||
|
SE,
|
||||||
|
S,
|
||||||
|
SW,
|
||||||
|
W,
|
||||||
|
NW,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Coordinate {
|
||||||
|
fn prev_x(&self) -> Self;
|
||||||
|
fn next_x(&self) -> Self;
|
||||||
|
fn prev_y(&self) -> Self;
|
||||||
|
fn next_y(&self) -> Self;
|
||||||
|
|
||||||
|
fn in_bounds(&self, bounds: image::Extent) -> bool;
|
||||||
|
fn get<P: Copy>(&self, img: &image::Image<P>) -> Option<P>;
|
||||||
|
fn set<P>(&self, img: &mut image::Image<P>, value: P);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find<P>(img: &image::Image<P>, fg: P) -> Vec<Contour>
|
||||||
|
where
|
||||||
|
P: PartialEq + Copy + Send + Sync,
|
||||||
|
{
|
||||||
|
let mut map = img.map(|p| {
|
||||||
|
if p == fg {
|
||||||
|
Pixel::Foreground
|
||||||
|
} else {
|
||||||
|
Pixel::Background
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut contours = Vec::new();
|
||||||
|
for y in 0..map.extent.y {
|
||||||
|
let mut parent = None;
|
||||||
|
for x in 0..map.extent.x {
|
||||||
|
let q = (x, y);
|
||||||
|
if foreground(&map, q) && background(&map, q.prev_x()) {
|
||||||
|
add(q, Kind::Outer, parent, &mut contours, &mut map);
|
||||||
|
} else if (foreground(&map, q) || traced_up(&map, q).is_some())
|
||||||
|
&& background(&map, q.next_x())
|
||||||
|
{
|
||||||
|
add(q, Kind::Inner, parent, &mut contours, &mut map);
|
||||||
|
} else if let Some(label) = traced_up(&map, q) {
|
||||||
|
parent = Some(label);
|
||||||
|
} else if let Some(label) = traced_down(&map, q) {
|
||||||
|
parent = contours[label].parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contours
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(
|
||||||
|
start: (usize, usize),
|
||||||
|
kind: Kind,
|
||||||
|
parent: Option<usize>,
|
||||||
|
contours: &mut Vec<Contour>,
|
||||||
|
map: &mut image::Image<Pixel>,
|
||||||
|
) {
|
||||||
|
let label = contours.len();
|
||||||
|
let dir = kind.find_start_dir(start, map);
|
||||||
|
let vertices = trace(map, start, dir, label);
|
||||||
|
contours.push(Contour::new(parent, kind, vertices));
|
||||||
|
if let Some(index) = parent {
|
||||||
|
contours[index].add_child(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trace(
|
||||||
|
img: &mut image::Image<Pixel>,
|
||||||
|
start_q: (usize, usize),
|
||||||
|
start_dir: Dir,
|
||||||
|
label: usize,
|
||||||
|
) -> Vec<(usize, usize)> {
|
||||||
|
let mut q = start_q;
|
||||||
|
let mut dir = start_dir;
|
||||||
|
|
||||||
|
let mut vertices = Vec::new();
|
||||||
|
loop {
|
||||||
|
if foreground(img, q) {
|
||||||
|
q.set(img, Pixel::TracedUp(label));
|
||||||
|
}
|
||||||
|
|
||||||
|
let prev_dir = dir;
|
||||||
|
dir = dir.flip().next();
|
||||||
|
q = loop {
|
||||||
|
let qp = dir.from(q);
|
||||||
|
if !background(img, qp) {
|
||||||
|
if dir != prev_dir {
|
||||||
|
vertices.push(q)
|
||||||
|
}
|
||||||
|
break qp;
|
||||||
|
}
|
||||||
|
if dir == Dir::E {
|
||||||
|
q.set(img, Pixel::TracedDown(label));
|
||||||
|
}
|
||||||
|
dir = dir.next();
|
||||||
|
};
|
||||||
|
|
||||||
|
if q == start_q && dir == start_dir || !q.in_bounds(img.extent) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for y in 0..img.extent.y {
|
||||||
|
// print!(" ");
|
||||||
|
// for x in 0..img.extent.x {
|
||||||
|
// if let Some(label) = traced_up(img, (x, y)) {
|
||||||
|
// print!("{:1}", label);
|
||||||
|
// } else if let Some(_) = traced_down(img, (x, y)) {
|
||||||
|
// print!("-")
|
||||||
|
// } else if foreground(img, (x, y)) {
|
||||||
|
// print!(".");
|
||||||
|
// } else if background(img, (x, y)) {
|
||||||
|
// print!(" ");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// println!();
|
||||||
|
// }
|
||||||
|
|
||||||
|
vertices
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foreground(img: &image::Image<Pixel>, q: (usize, usize)) -> bool {
|
||||||
|
match q.get(img) {
|
||||||
|
Some(Pixel::Foreground) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background(img: &image::Image<Pixel>, q: (usize, usize)) -> bool {
|
||||||
|
match q.get(img) {
|
||||||
|
Some(Pixel::Background) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn traced_up(img: &image::Image<Pixel>, q: (usize, usize)) -> Option<usize> {
|
||||||
|
match q.get(img) {
|
||||||
|
Some(Pixel::TracedUp(label)) => Some(label),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn traced_down(
|
||||||
|
img: &image::Image<Pixel>,
|
||||||
|
q: (usize, usize),
|
||||||
|
) -> Option<usize> {
|
||||||
|
match q.get(img) {
|
||||||
|
Some(Pixel::TracedDown(label)) => Some(label),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Kind {
|
||||||
|
fn find_start_dir(
|
||||||
|
self,
|
||||||
|
q: (usize, usize),
|
||||||
|
img: &image::Image<Pixel>,
|
||||||
|
) -> Dir {
|
||||||
|
let mut dir = match self {
|
||||||
|
Kind::Outer => Dir::W,
|
||||||
|
Kind::Inner => Dir::E,
|
||||||
|
};
|
||||||
|
while background(img, dir.from(q)) {
|
||||||
|
dir = dir.prev()
|
||||||
|
}
|
||||||
|
dir.flip()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dir {
|
||||||
|
fn next(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::N => Self::NE,
|
||||||
|
Self::NE => Self::E,
|
||||||
|
Self::E => Self::SE,
|
||||||
|
Self::SE => Self::S,
|
||||||
|
Self::S => Self::SW,
|
||||||
|
Self::SW => Self::W,
|
||||||
|
Self::W => Self::NW,
|
||||||
|
Self::NW => Self::N,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prev(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::N => Self::NW,
|
||||||
|
Self::NE => Self::N,
|
||||||
|
Self::E => Self::NE,
|
||||||
|
Self::SE => Self::E,
|
||||||
|
Self::S => Self::SE,
|
||||||
|
Self::SW => Self::S,
|
||||||
|
Self::W => Self::SW,
|
||||||
|
Self::NW => Self::W,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flip(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::N => Self::S,
|
||||||
|
Self::NE => Self::SW,
|
||||||
|
Self::E => Self::W,
|
||||||
|
Self::SE => Self::NW,
|
||||||
|
Self::S => Self::N,
|
||||||
|
Self::SW => Self::NE,
|
||||||
|
Self::W => Self::E,
|
||||||
|
Self::NW => Self::SE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from<C: Coordinate>(&self, q: C) -> C {
|
||||||
|
match self {
|
||||||
|
Self::N => q.prev_y(),
|
||||||
|
Self::NE => q.prev_y().next_x(),
|
||||||
|
Self::E => q.next_x(),
|
||||||
|
Self::SE => q.next_y().next_x(),
|
||||||
|
Self::S => q.next_y(),
|
||||||
|
Self::SW => q.next_y().prev_x(),
|
||||||
|
Self::W => q.prev_x(),
|
||||||
|
Self::NW => q.prev_y().prev_x(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Coordinate for (usize, usize) {
|
||||||
|
fn prev_x(&self) -> Self {
|
||||||
|
(self.0.wrapping_sub(1), self.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_x(&self) -> Self {
|
||||||
|
(self.0 + 1, self.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prev_y(&self) -> Self {
|
||||||
|
(self.0, self.1.wrapping_sub(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_y(&self) -> Self {
|
||||||
|
(self.0, self.1 + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_bounds(&self, bounds: image::Extent) -> bool {
|
||||||
|
self.0 < bounds.x && self.1 < bounds.y
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get<P: Copy>(&self, img: &image::Image<P>) -> Option<P> {
|
||||||
|
if self.in_bounds(img.extent) {
|
||||||
|
Some(img.at(self.0, self.1))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set<P>(&self, img: &mut image::Image<P>, value: P) {
|
||||||
|
if self.in_bounds(img.extent) {
|
||||||
|
*img.at_mut(self.0, self.1) = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Contour {
|
||||||
|
fn new(
|
||||||
|
parent: Option<usize>,
|
||||||
|
kind: Kind,
|
||||||
|
vertices: Vec<(usize, usize)>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
vertices,
|
||||||
|
children: Vec::new(),
|
||||||
|
parent,
|
||||||
|
kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_child(&mut self, child: usize) {
|
||||||
|
self.children.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Pixel {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Background
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_block() {
|
||||||
|
let img = image::Image::<usize> {
|
||||||
|
extent: image::Extent { x: 4, y: 4 },
|
||||||
|
pixels: vec![
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 1, 1, 0,
|
||||||
|
0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let contours = find(&img, 1);
|
||||||
|
assert_eq!(contours.len(), 1);
|
||||||
|
|
||||||
|
let contour = &contours[0];
|
||||||
|
assert_eq!(contour.kind, Kind::Outer);
|
||||||
|
assert_eq!(contour.vertices, vec![
|
||||||
|
(1, 1), (2, 1), (2, 2), (1, 2)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_block_with_hole() {
|
||||||
|
let img = image::Image::<usize> {
|
||||||
|
extent: image::Extent { x: 8, y: 8 },
|
||||||
|
pixels: vec![
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 0,
|
||||||
|
0, 1, 1, 0, 0, 1, 1, 0,
|
||||||
|
0, 1, 1, 0, 0, 1, 1, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let contours = find(&img, 1);
|
||||||
|
assert_eq!(contours.len(), 2);
|
||||||
|
|
||||||
|
let contour = &contours[0];
|
||||||
|
assert_eq!(contour.parent, None);
|
||||||
|
assert_eq!(contour.kind, Kind::Outer);
|
||||||
|
assert_eq!(contour.vertices, vec![
|
||||||
|
(1, 1), (6, 1), (6, 6), (1, 6)
|
||||||
|
]);
|
||||||
|
assert_eq!(contour.children, vec![ 1 ]);
|
||||||
|
|
||||||
|
let contour = &contours[1];
|
||||||
|
assert_eq!(contour.parent, Some(0));
|
||||||
|
assert_eq!(contour.kind, Kind::Inner);
|
||||||
|
assert_eq!(contour.vertices, vec![
|
||||||
|
(2, 3), (2, 4), (3, 5), (4, 5),
|
||||||
|
(5, 4), (5, 3), (4, 2), (3, 2),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn diagonally_connected_block_with_hole() {
|
||||||
|
let img = image::Image::<usize> {
|
||||||
|
extent: image::Extent { x: 8, y: 8 },
|
||||||
|
pixels: vec![
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 0,
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 0,
|
||||||
|
0, 1, 1, 0, 0, 1, 1, 0,
|
||||||
|
0, 1, 1, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 0,
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let contours = find(&img, 1);
|
||||||
|
assert_eq!(contours.len(), 2);
|
||||||
|
|
||||||
|
let contour = &contours[0];
|
||||||
|
assert_eq!(contour.parent, None);
|
||||||
|
assert_eq!(contour.kind, Kind::Outer);
|
||||||
|
assert_eq!(contour.vertices, vec![
|
||||||
|
(3, 1), (6, 1), (6, 6), (3, 6), (3, 5),
|
||||||
|
(2, 4), (1, 4), (1, 3), (2, 3), (3, 2),
|
||||||
|
]);
|
||||||
|
assert_eq!(contour.children, vec![ 1 ]);
|
||||||
|
|
||||||
|
let contour = &contours[1];
|
||||||
|
assert_eq!(contour.parent, Some(0));
|
||||||
|
assert_eq!(contour.kind, Kind::Inner);
|
||||||
|
assert_eq!(contour.vertices, vec![
|
||||||
|
(2, 3), (2, 4), (3, 5), (4, 5),
|
||||||
|
(5, 4), (5, 3), (4, 2), (3, 2),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn contour_passes_through_start() {
|
||||||
|
let img = image::Image::<usize> {
|
||||||
|
extent: image::Extent { x: 5, y: 5 },
|
||||||
|
pixels: vec![
|
||||||
|
0, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 1, 0,
|
||||||
|
0, 1, 0, 0, 0,
|
||||||
|
0, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0,
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let contours = find(&img, 1);
|
||||||
|
assert_eq!(contours.len(), 1);
|
||||||
|
|
||||||
|
let contour = &contours[0];
|
||||||
|
assert_eq!(contour.kind, Kind::Outer);
|
||||||
|
assert_eq!(contour.vertices, vec![
|
||||||
|
(2, 1), (3, 1), (2, 1), (1, 2), (1, 3), (1, 2)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub fn line<P: Copy>(
|
||||||
|
x0: usize,
|
||||||
|
y0: usize,
|
||||||
|
x1: usize,
|
||||||
|
y1: usize,
|
||||||
|
img: &mut image::Image<P>,
|
||||||
|
colour: P,
|
||||||
|
) {
|
||||||
|
let dx = x0.abs_diff(x1) as isize;
|
||||||
|
let dy = -(y0.abs_diff(y1) as isize);
|
||||||
|
|
||||||
|
let sx = if x0 < x1 { 1 } else { -1 };
|
||||||
|
let sy = if y0 < y1 { 1 } else { -1 };
|
||||||
|
|
||||||
|
let mut x = x0;
|
||||||
|
let mut y = y0;
|
||||||
|
let mut error = dx + dy;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if x >= img.extent.x || y >= img.extent.y {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*img.at_mut(x, y) = colour;
|
||||||
|
let e2 = 2 * error;
|
||||||
|
if e2 >= dy {
|
||||||
|
if x == x1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
error += dy;
|
||||||
|
x = (x as isize + sx) as usize;
|
||||||
|
}
|
||||||
|
if e2 <= dx {
|
||||||
|
if y == y1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
error += dx;
|
||||||
|
y = (y as isize + sy) as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-24
@@ -40,8 +40,8 @@ pub fn lines(
|
|||||||
) -> Vec<Line> {
|
) -> Vec<Line> {
|
||||||
let hough = transform(img);
|
let hough = transform(img);
|
||||||
|
|
||||||
let mu = mean((&hough.pixels).into_iter().map(|p| *p));
|
let mu = stats::mean((&hough.pixels).into_iter().map(|p| *p));
|
||||||
let sigma = stddev((&hough.pixels).into_iter().map(|p| *p), mu);
|
let sigma = stats::stddev((&hough.pixels).into_iter().map(|p| *p), mu);
|
||||||
|
|
||||||
let mut lines = Vec::<Line>::new();
|
let mut lines = Vec::<Line>::new();
|
||||||
for y in 0..hough.extent.y {
|
for y in 0..hough.extent.y {
|
||||||
@@ -59,29 +59,9 @@ pub fn lines(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theta_from(x: usize) -> f32 {
|
fn theta_from(x: usize) -> f32 {
|
||||||
x as f32 * std::f32::consts::PI / THETA_N as f32
|
x as f32 * std::f32::consts::PI / THETA_N as f32
|
||||||
}
|
|
||||||
|
|
||||||
fn mean(xs: impl Iterator<Item = f32>) -> f32 {
|
|
||||||
let mut n = 0;
|
|
||||||
let mut sum = 0.0;
|
|
||||||
for x in xs {
|
|
||||||
sum += x;
|
|
||||||
n += 1;
|
|
||||||
}
|
|
||||||
sum / n as f32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stddev(xs: impl Iterator<Item = f32>, mean: f32) -> f32 {
|
|
||||||
let mut n = 0;
|
|
||||||
let mut sum = 0.0;
|
|
||||||
for x in xs {
|
|
||||||
sum += (x - mean).powf(2.0);
|
|
||||||
n += 1;
|
|
||||||
}
|
|
||||||
(sum / (n - 1) as f32).sqrt()
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
|
pub mod colours;
|
||||||
|
pub mod contour;
|
||||||
|
pub mod draw;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
pub mod hough;
|
pub mod hough;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod kernel;
|
pub mod kernel;
|
||||||
|
pub mod stats;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
pub fn mean(xs: impl Iterator<Item = f32>) -> f32 {
|
||||||
|
let mut n = 0;
|
||||||
|
let mut sum = 0.0;
|
||||||
|
for x in xs {
|
||||||
|
sum += x;
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
sum / n as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stddev(xs: impl Iterator<Item = f32>, mean: f32) -> f32 {
|
||||||
|
let mut n = 0;
|
||||||
|
let mut sum = 0.0;
|
||||||
|
for x in xs {
|
||||||
|
sum += (x - mean).powf(2.0);
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
(sum / (n - 1) as f32).sqrt()
|
||||||
|
}
|
||||||
Reference in new issue
Block a user