Applying Side-Scan Sonar Effects to Optical Images Using OpenCV

Hello everyone,

I’m working on a project where I want to apply a side-scan sonar effect to standard optical images using OpenCV. The goal is to simulate the visual characteristics of sonar images, which typically have:

  • A dark nadir zone (central black stripe) where no data is received.
  • Bright edges near the nadir, where sonar reflections are strongest.
  • A gradient that fades outward from the center.
  • Textured noise patterns to mimic seafloor roughness.

Proposed Approach
To achieve this effect, I plan to process an optical image as follows:

  1. Convert to Grayscale – If the image is in color, convert it to grayscale.
  2. Apply an Exponential Gradient – Bright at the center, fading outward.
    • Use an exponential decay function to control intensity:
      [
      I(x) = I_0 \cdot e^{-\alpha |x - x_c|}
      ]
      where ( x_c ) is the center of the image, and ( \alpha ) controls the brightness falloff.
  3. Add a Nadir Zone – A vertical black band in the middle to simulate sonar shadows.
  4. Blend the Gradient with the Original Image – Using cv2.addWeighted().
  5. Introduce Noise Texture – Random noise (np.random.normal()) to simulate seafloor texture.
  6. Enhance Contrast – Using histogram equalization (cv2.equalizeHist()) to make edges sharper.

Python Implementation
Here’s a simplified OpenCV script applying this effect to an existing image:

import numpy as np
import cv2
import matplotlib.pyplot as plt

# Load the optical image
image = cv2.imread("input.jpg", cv2.IMREAD_GRAYSCALE)
height, width = image.shape

# Normalize the image
image = cv2.normalize(image, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)

# Create an exponential gradient
gradient = np.zeros((height, width), dtype=np.uint8)
for x in range(width):
    distance = abs(x - width // 2)
    intensity = int(255 * np.exp(-0.005 * distance))
    gradient[:, x] = np.clip(intensity, 0, 255)

# Define and apply the nadir zone (black stripe in center)
nadir_width = int(width * 0.05)
nadir_start, nadir_end = width // 2 - nadir_width // 2, width // 2 + nadir_width // 2
gradient[:, nadir_start:nadir_end] = 0

# Blend original image with gradient
blended = cv2.addWeighted(image, 0.7, gradient, 0.3, 0)

# Add noise for texture
noise = np.random.normal(0, 20, (height, width)).astype(np.int8)
blended = np.clip(blended + noise, 0, 255).astype(np.uint8)

# Apply smoothing and contrast enhancement
blended = cv2.GaussianBlur(blended, (5, 5), 0)
blended = cv2.equalizeHist(blended)

# Display the sonar-styled image
plt.imshow(blended, cmap='inferno')
plt.axis('off')
plt.title("Side-Scan Sonar Effect on Optical Image")
plt.show()

# Save output
cv2.imwrite("sonar_style.jpg", blended)

Questions for the Community

  • Does this approach look reasonable for simulating a side-scan sonar effect?
  • Are there better ways to enhance the contrast of bright reflections while keeping a natural look?
  • Any suggestions for improving the nadir blending so it appears more realistic?

Thanks in advance for your feedback!