Skip to content

Commit

Permalink
added threshold preprocessing and outlier detection
Browse files Browse the repository at this point in the history
  • Loading branch information
Brandon Lei authored and Brandon Lei committed Oct 9, 2024
1 parent 29851ea commit a75de39
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 21 deletions.
86 changes: 73 additions & 13 deletions LineDetection.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Line Detection</title>
<title>Black Line Detection</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; }
#outputImage { max-width: 100%; height: auto; border: 1px solid #ddd; }
Expand All @@ -17,7 +17,7 @@

class LineDetector {
constructor(raspberryPiIp, width = 640, height = 480) {
this.raspberryPiIp = raspberryPiIp = "192.168.41.214";
this.raspberryPiIp = raspberryPiIp;
this.width = width;
this.height = height;
this.canvas = document.createElement('canvas');
Expand All @@ -34,15 +34,17 @@
const image = await this.loadImage(`http://${this.raspberryPiIp}:${port.camera}/${endpoint.video}`);
this.ctx.drawImage(image, 0, 0, this.width, this.height);
const imageData = this.ctx.getImageData(0, 0, this.width, this.height);
const lineCoordinates = this.processImageData(imageData);
const preprocessedImageData = this.preprocessImage(imageData);
const lineCoordinates = this.processImageData(preprocessedImageData);
const filteredCoordinates = this.filterContinuousLine(lineCoordinates);
console.log("coordinates");
console.log(lineCoordinates);
if (lineCoordinates.length > 0) {
this.lastDetectedLine = lineCoordinates;
console.log(filteredCoordinates);
if (filteredCoordinates.length > 0) {
this.lastDetectedLine = filteredCoordinates;
}

if (this.frameCount < 7) {
this.allCoordinates.push(lineCoordinates);
this.allCoordinates.push(filteredCoordinates);
this.frameCount++;
if (this.frameCount === 7) {
this.writeCoordinatesToFile(this.allCoordinates);
Expand All @@ -67,29 +69,87 @@
});
}

preprocessImage(imageData) {
const data = imageData.data;
const threshold = this.calculateBlackThreshold(data);

for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];

const brightness = (r + g + b) / 3;

if (brightness < threshold) {
data[i] = 0; // Set red to min
data[i + 1] = 0; // Set green to min
data[i + 2] = 0; // Set blue to min
} else {
data[i] = 255; // Set red to max
data[i + 1] = 255; // Set green to max
data[i + 2] = 255; // Set blue to max
}
}

return imageData;
}

calculateBlackThreshold(data) {
let brightnessValues = [];
for (let i = 0; i < data.length; i += 4) {
const brightness = (data[i] + data[i + 1] + data[i + 2]) / 3;
brightnessValues.push(brightness);
}
brightnessValues.sort((a, b) => a - b);
return brightnessValues[Math.floor(brightnessValues.length * 0.3)]; // 30th percentile as threshold
}

processImageData(imageData) {
const lineCoordinates = [];
const threshold = 70;

for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
const index = (y * this.width + x) * 4;
const [r, g, b] = imageData.data.slice(index, index + 3);
const r = imageData.data[index];

if (r < threshold && g < threshold && b < threshold && y < 400) {
if (r === 0) { // Check for black pixels (preprocessed black line)
lineCoordinates.push([x, y]);
}
}
}
return lineCoordinates.sort((a, b) => a[1] - b[1]);
}

filterContinuousLine(coordinates) {
if (coordinates.length < 2) return coordinates;

const filteredCoordinates = [coordinates[0]];
const maxDistance = 20; // Maximum allowed distance between consecutive points

for (let i = 1; i < coordinates.length; i++) {
const [prevX, prevY] = filteredCoordinates[filteredCoordinates.length - 1];
const [currentX, currentY] = coordinates[i];

const distance = Math.sqrt(Math.pow(currentX - prevX, 2) + Math.pow(currentY - prevY, 2));

if (distance <= maxDistance) {
filteredCoordinates.push(coordinates[i]);
}
}

return filteredCoordinates;
}

drawLine(coordinates) {
this.ctx.beginPath();
this.ctx.strokeStyle = 'blue';
this.ctx.strokeStyle = 'red';
this.ctx.lineWidth = 2;
coordinates.forEach(([x, y], i) => {
this.ctx.arc(x, y, 5, 0, Math.PI*2);
if (i === 0) {
this.ctx.moveTo(x, y);
} else {
this.ctx.lineTo(x, y);
}
});
this.ctx.stroke();
}
Expand Down Expand Up @@ -133,7 +193,7 @@
</script>
</head>
<body>
<h1>Line Detection</h1>
<h1>Black Line Detection</h1>
<div id="controls">
<input type="text" id="ipInput" placeholder="IP Address">
<button onclick="initializeDetector()">Start Detection</button>
Expand Down
73 changes: 65 additions & 8 deletions extensions/src/doodlebot/LineDetection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@ export class LineDetector {
const image = await loadImage(`http://${this.raspberryPiIp}:${port.camera}/${endpoint.video}`);
this.ctx.drawImage(image, 0, 0, this.width, this.height);
const imageData = this.ctx.getImageData(0, 0, this.width, this.height);
const lineCoordinates = this.processImageData(imageData);
const preprocessedImageData = this.preprocessImage(imageData);
const lineCoordinates = this.processImageData(preprocessedImageData);
const filteredCoordinates = this.filterContinuousLine(lineCoordinates);

if (lineCoordinates.length > 0) {
this.lastDetectedLine = lineCoordinates;
console.log("coordinates");
console.log(filteredCoordinates);

if (filteredCoordinates.length > 0) {
this.lastDetectedLine = filteredCoordinates;
}

return this.lastDetectedLine;
Expand All @@ -35,24 +40,76 @@ export class LineDetector {
}
}

private preprocessImage(imageData: ImageData): ImageData {
const data = imageData.data;
const threshold = this.calculateBlackThreshold(data);

for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];

const brightness = (r + g + b) / 3;

if (brightness < threshold) {
data[i] = 0;
data[i + 1] = 0;
data[i + 2] = 0;
} else {
data[i] = 255;
data[i + 1] = 255;
data[i + 2] = 255;
}
}

return imageData;
}

private calculateBlackThreshold(data: Uint8ClampedArray): number {
let brightnessValues: number[] = [];
for (let i = 0; i < data.length; i += 4) {
const brightness = (data[i] + data[i + 1] + data[i + 2]) / 3;
brightnessValues.push(brightness);
}
brightnessValues.sort((a, b) => a - b);
return brightnessValues[Math.floor(brightnessValues.length * 0.3)];
}

private processImageData(imageData: ImageData): number[][] {
const lineCoordinates: number[][] = [];
const threshold = 50;

for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
const index = (y * this.width + x) * 4;
const r = imageData.data[index];
const g = imageData.data[index + 1];
const b = imageData.data[index + 2];

if (r < threshold && g < threshold && b < threshold) {
if (r === 0) {
lineCoordinates.push([x, y]);
}
}
}
}
return lineCoordinates.sort((a, b) => a[1] - b[1]);
}

private filterContinuousLine(coordinates: number[][]): number[][] {
if (coordinates.length < 2) return coordinates;

const filteredCoordinates: number[][] = [coordinates[0]];
const maxDistance = 20;

for (let i = 1; i < coordinates.length; i++) {
const [prevX, prevY] = filteredCoordinates[filteredCoordinates.length - 1];
const [currentX, currentY] = coordinates[i];

const distance = Math.sqrt(Math.pow(currentX - prevX, 2) + Math.pow(currentY - prevY, 2));

if (distance <= maxDistance) {
filteredCoordinates.push(coordinates[i]);
}
}

return filteredCoordinates;
}
}

export function createLineDetector(raspberryPiIp: string): () => Promise<number[][]> {
Expand Down

0 comments on commit a75de39

Please sign in to comment.