All Projects Autonomous Line Following Robot

Autonomous Line Following Robot

Camera-based line tracking on a Pico 2, from chassis CAD to closed-loop steering

Context
Northwestern coursework
Role
Individual project — mechanical design, electronics, and control
SolidWorks 3D Printing C Raspberry Pi Pico 2 OV7670 Camera PWM Motor Control

Goal

Design and fabricate a robot that autonomously follows a line using vision alone.

The finished line-following robot, held in hand

Photo — the finished robot in hand

Hardware

An OV7670 camera interfaced to a Raspberry Pi Pico 2, with two DAGU gearmotors driven by PWM.

Mechanical

Designed the chassis in SolidWorks with mounting for the motors, camera, battery pack, and breadboard. 3D printed the chassis and the spoked wheels, with O-rings seated on the rims as tires for traction.

Chassis CAD model in SolidWorks
CAD — chassis, SolidWorks
Spoked wheel CAD model
Model — spoked wheel with O-ring tire

Vision and Control

The controller thresholds a single camera row and steers on its center of mass. For that row it sums per-pixel brightness to get a row average, then re-scans the row and pushes every pixel either to black or white against that average — a self-adjusting threshold that tolerates changing lighting. It then computes the intensity-weighted center of mass in x.

Video — raw camera feed before thresholding

// threshold and then find the center of mass of a row
int findLine(int row){
    int pos = 0;
    int r = row*IMAGESIZEX; // find the index of the start of the row in the pixel array
    int sumMass = 0;
    int sumMassR = 0;

    int i;

    // find the row average brightness
    int sumBright = 0;
    for(i=0;i<IMAGESIZEX;i++){
        sumBright = sumBright + picture.r[r+i] + picture.g[r+i] + picture.b[r+i];
    }
    int avgBright = sumBright / IMAGESIZEX;

    // threshold the row
    for(i=0;i<IMAGESIZEX;i++){
        int mass = picture.r[r+i] + picture.g[r+i] + picture.b[r+i];
        if (mass < avgBright){
            // not bright enough, set pixel to black
            picture.r[r+i] = 0;
            picture.g[r+i] = 0;
            picture.b[r+i] = 0;
        }
        else {
            // set to white
            picture.r[r+i] = 255;
            picture.g[r+i] = 255;
            picture.b[r+i] = 255;
        }
    }

    // calculate the center of mass of the thresholded row
    for(i=0;i<IMAGESIZEX;i++){
        int mass = picture.r[r+i] + picture.g[r+i] + picture.b[r+i];
        sumMass = sumMass + mass;
        sumMassR = sumMassR + mass*i;
    }
    float centerOfMass = (float) sumMassR / sumMass;
    return (int)centerOfMass;
}

The offset between that centroid and the frame center drives the motors: inside a small deadband both motors run at half duty and the robot goes straight; outside it, the inside motor’s duty is scaled down proportionally to the offset to turn, and past a hard limit that motor is cut to zero for a sharp turn.

int main()
{
    while (true) {

        // B is right
        // A is left
        // move straight
        if(offset > -5 && offset < 5) {
            pwm_set_gpio_level(PWMA, WRAP / 2);
            pwm_set_gpio_level(PWMB, WRAP / 2);
        }
        else {
            // turn left --> decrease speed of left
            if(offset < 0) {
                pwm_set_gpio_level(PWMB, WRAP / 4);
                if(offset < -30) {
                    pwm_set_gpio_level(PWMA, 0);
                }
                else {
                    float duty = .25 - (1.0/4.0/25.0)*(-1*offset - 5);
                    pwm_set_gpio_level(PWMA, WRAP * duty);
                }
            }
            //turn right --> decrease speed of right
            else {
                pwm_set_gpio_level(PWMA, WRAP / 4);
                if(offset > 30) {
                    pwm_set_gpio_level(PWMB, 0);
                }
                else {
                    float duty = .25 - (1.0/4.0/25.0)*(offset - 5);
                    pwm_set_gpio_level(PWMB, WRAP * duty);
                }
            }
        }
    }
}

Result

Completed a curved track autonomously.

Video — the robot running the curved track

Eric Oh

© 2025 Eric Oh. All rights reserved.

LinkedIn GitHub Email