Image Classification for Drones
Image classification assigns category labels to entire drone images—categorizing scenes as urban, agricultural, forest, or water. This tutorial covers transfer learning and modern architectures optimized for aerial imagery.
Transfer Learning Pipeline
Transfer learning leverages pretrained models to classify drone scenes with limited data.
**Real-world analogy:** Transfer learning is like training a chef. Instead of teaching cooking from scratch, you take someone who already knows French cuisine (pretrained on ImageNet) and teach them Chinese dishes (drone classification). They already understand flavors, techniques, and combinations—you just redirect their expertise.
## ResNet Architecture
ResNet's residual connections enable training very deep networks by allowing gradients to flow through skip connections.
```python
class ResidualBlock:
"""Basic residual block for ResNet."""
def __init__(self, in_channels, out_channels, stride=1):
self.conv1_weights = np.random.randn(out_channels, in_channels, 3, 3) * np.sqrt(2.0 / (in_channels * 9))
self.conv1_bias = np.zeros(out_channels)
self.conv2_weights = np.random.randn(out_channels, out_channels, 3, 3) * np.sqrt(2.0 / (out_channels * 9))
self.conv2_bias = np.zeros(out_channels)
# Batch norm parameters
self.bn1_gamma = np.ones(out_channels)
self.bn1_beta = np.zeros(out_channels)
self.bn2_gamma = np.ones(out_channels)
self.bn2_beta = np.zeros(out_channels)
# Skip connection projection
self.stride = stride
if stride != 1 or in_channels != out_channels:
self.shortcut_weights = np.random.randn(out_channels, in_channels, 1, 1) * np.sqrt(2.0 / in_channels)
self.shortcut_bias = np.zeros(out_channels)
self.has_projection = True
else:
self.has_projection = False
def conv2d(self, x, weights, bias, stride=1, padding=1):
"""2D convolution."""
batch, in_ch, h, w = x.shape
out_ch = weights.shape[0]
k = weights.shape[2]
x_padded = np.pad(x, ((0,0), (0,0), (padding,padding), (padding,padding)), mode='reflect')
out_h = (h + 2*padding - k) // stride + 1
out_w = (w + 2*padding - k) // stride + 1
output = np.zeros((batch, out_ch, out_h, out_w))
for b in range(batch):
for oc in range(out_ch):
for i in range(out_h):
for j in range(out_w):
h_start = i * stride
h_end = h_start + k
w_start = j * stride
w_end = w_start + k
output[b, oc, i, j] = np.sum(x_padded[b, :, h_start:h_end, w_start:w_end] * weights[oc]) + bias[oc]
return output
def batch_norm(self, x, gamma, beta, eps=1e-5):
"""Simplified batch normalization."""
mean = np.mean(x, axis=(0, 2, 3), keepdims=True)
var = np.var(x, axis=(0, 2, 3), keepdims=True)
x_norm = (x - mean) / np.sqrt(var + eps)
return gamma.reshape(1, -1, 1, 1) * x_norm + beta.reshape(1, -1, 1, 1)
def relu(self, x):
return np.maximum(0, x)
def forward(self, x):
"""Forward pass with residual connection."""
identity = x
# Main path
out = self.conv2d(x, self.conv1_weights, self.conv1_bias, stride=self.stride)
out = self.batch_norm(out, self.bn1_gamma, self.bn1_beta)
out = self.relu(out)
out = self.conv2d(out, self.conv2_weights, self.conv2_bias)
out = self.batch_norm(out, self.bn2_gamma, self.bn2_beta)
# Shortcut path
if self.has_projection:
identity = self.conv2d(x, self.shortcut_weights, self.shortcut_bias, stride=self.stride)
# Residual connection
out += identity
out = self.relu(out)
return out
class ResNet18:
"""Simplified ResNet-18 for drone classification."""
def __init__(self, num_classes=10):
# Initial conv
self.conv1_weights = np.random.randn(64, 3, 7, 7) * np.sqrt(2.0 / (3 * 49))
self.conv1_bias = np.zeros(64)
# Residual layers
self.layer1 = [ResidualBlock(64, 64) for _ in range(2)]
self.layer2 = [ResidualBlock(64, 128, stride=2)] + [ResidualBlock(128, 128)]
self.layer3 = [ResidualBlock(128, 256, stride=2)] + [ResidualBlock(256, 256)]
self.layer4 = [ResidualBlock(256, 512, stride=2)] + [ResidualBlock(512, 512)]
# Classifier
self.fc_weights = np.random.randn(num_classes, 512) * np.sqrt(2.0 / 512)
self.fc_bias = np.zeros(num_classes)
def global_avg_pool(self, x):
return np.mean(x, axis=(2, 3))
def forward(self, x):
"""Forward pass."""
# Initial conv
x = np.pad(x, ((0,0), (0,0), (3,3), (3,3)), mode='reflect')
# Simplified conv1
batch = x.shape[0]
x = np.random.randn(batch, 64, x.shape[2]//4, x.shape[3]//4) # Simulate stride 4
# Residual layers
for block in self.layer1:
x = block.forward(x)
for block in self.layer2:
x = block.forward(x)
for block in self.layer3:
x = block.forward(x)
for block in self.layer4:
x = block.forward(x)
# Global average pooling
x = self.global_avg_pool(x)
# FC layer
logits = x @ self.fc_weights.T + self.fc_bias
probs = np.exp(logits) / np.sum(np.exp(logits), axis=1, keepdims=True)
return probs
# Example: Classify drone scenes
np.random.seed(42)
model = ResNet18(num_classes=10)
# Simulate batch of drone images
batch = np.random.randn(4, 3, 224, 224)
output = model.forward(batch)
scene_classes = ['Urban', 'Rural', 'Forest', 'Water', 'Desert',
'Industrial', 'Residential', 'Agricultural', 'Coastal', 'Mountain']
print("Drone Scene Classification Results:")
for i in range(4):
pred_class = scene_classes[np.argmax(output[i])]
confidence = np.max(output[i])
print(f" Image {i+1}: {pred_class} ({confidence:.1%})")
EfficientNet: Scaling for Drones
EfficientNet balances network depth, width, and resolution through compound scaling—ideal for resource-constrained drone hardware.
Data Augmentation for Drone Imagery
Drone-specific augmentation handles unique challenges like altitude changes and viewpoint variations.
Hands-On Project: Drone Scene Classifier
Build a complete scene classification system for drone imagery.
Key Takeaways
- Transfer learning enables drone classification with limited data
- ResNet residual connections enable training deep networks
- EfficientNet balances accuracy and efficiency for edge deployment
- Drone-specific augmentation handles altitude and viewpoint variations
- Scene classification categorizes land use for urban planning
Next, we'll explore pose estimation for tracking humans and objects from aerial views.