Detecting slightly bright areas (fawn of deer) in thermal images

I am looking into detecting slightly bright areas (fawns from the roe deer) in thermal images with openCV.

So far I managed to get some code that works somehow, but with to many false negatives and false positives.

I basically know my way around openCV. But from the algorithmic side I a not sure what the best solution should be to result in a most perfect detection.

So far I use a cascade of something like this

  1. gaussion blur
  2. some sore of hysteresis thesholding
  3. blob detection

Code snipped:

    cv::GaussianBlur(gray, gray, cv::Size(gauss_size, gauss_size), 0);

    Mat threshUpper, threshLower;
    threshold(gray, threshUpper, mask_min, mask_max, cv::THRESH_BINARY);
    threshold(gray, threshLower, mask_min-mask_thresh, mask_max, cv::THRESH_BINARY);
    imshow("threshUpper", threshUpper);
    imshow("threshLower", threshLower);

    vector<vector<Point>> contoursUpper;
    cv::findContours(threshUpper, contoursUpper, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
    for(auto cnt : contoursUpper){
        cv::floodFill(threshLower, cnt[0], 255, 0, 2, 2, cv::FLOODFILL_FIXED_RANGE);
    threshold(threshLower, out, 200, 255, cv::THRESH_BINARY);

    vector<vector<Point>> contours2clean;
    cv::findContours(out, contours2clean, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
    for(const auto& cnt : contours2clean) {
        double area = cv::contourArea(cnt);
        if ( area > cut_max_size || area < cut_min_size) {
            cv::floodFill(out, cnt[0], 0, 0, 2, 2, cv::FLOODFILL_FIXED_RANGE);
        else {
            cv::floodFill(out, cnt[0], 255, 0, 2, 2, cv::FLOODFILL_FIXED_RANGE);

    std::vector<cv::KeyPoint> points;
    detector_->detect(out, points);
    cv::drawKeypoints(out, points, out, cv::Scalar(0, 0, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);

I am looking for some advice for better approaches. Two images (raw and marked) are here:

As the topic is important to me, I reposted this with more pictures here: c++ - Detecting slightly bright areas (fawn of deer) in thermal images - Stack Overflow


That’s not an easy type of image. Are you sure that your deer will always be brighter than the background?

Anyway, to begin with, I would use difference of gaussians instead of a gaussian (with an adapted sigma). It should accentuate the bright dots in the image.

1 Like

that tiny little dot, hardly any brighter than the noise in the picture, you want to detect that?

not with that camera. nope, sorry. if a human has trouble telling that apart from noise, a machine will rarely do better.

the other picture you didn’t show here, that has a chance… but it’s still impossible to tell with confidence.

a script to play with difference of gaussians:

#!/usr/bin/env python3

import os
import sys
import numpy as np
import cv2 as cv

files = 'dTXplm.jpg  yOUREm.jpg'.split()
if sys.argv[1:]:
	import glob
	files = []
	for globby in sys.argv[1:]:
		files += glob.glob(globby)

images = [cv.imread(fname) / np.float32(255) for fname in files]

cv.namedWindow("controls", cv.WINDOW_NORMAL)
cv.resizeWindow("controls", (1000, 200))

trackbar_scale = 10
sigma_max = 20
sigma1 = 0
sigma2 = 0
gain = 1.0
bias = 0.5

images_s1 = []
images_s2 = []

def calculate_blurs(sigma):
	k = int(np.ceil(sigma*3)) * 2 + 1 # diameter, odd

	return [
		cv.GaussianBlur(im, ksize=(k,k), sigmaX=sigma, sigmaY=sigma) if (sigma > 0) else im
		for im in images

def redraw():
	for (fn, im, im_s1, im_s2) in zip(files, images, images_s1, images_s2):
		diff = (im_s1 - im_s2) * gain + bias
			np.hstack([im, diff])

def on_sigma1(pos, userdata=None):
	global sigma1
	sigma1 = pos / 10
	print("sigma1 :=", sigma1)
	images_s1[:] = calculate_blurs(sigma1)

def on_sigma2(pos, userdata=None):
	global sigma2
	sigma2 = pos / 10
	print("sigma2 :=", sigma2)
	images_s2[:] = calculate_blurs(sigma2)

def on_gain(pos, userdata=None):
	global gain
	gain = pos / 10

def on_bias(pos, userdata=None):
	global bias
	bias = pos / 10

tb_sigma1 = f"sigma1 x{trackbar_scale}"
tb_sigma2 = f"sigma2 x{trackbar_scale}"
tb_gain   = f"gain x{trackbar_scale}"
tb_bias   = f"bias x{trackbar_scale}"

cv.createTrackbar(tb_sigma1, "controls",
	int(0 * trackbar_scale),
	int(sigma_max * trackbar_scale),

cv.createTrackbar(tb_sigma2, "controls",
	int(sigma_max * trackbar_scale),
	int(sigma_max * trackbar_scale),

cv.createTrackbar(tb_gain, "controls",
	int(1.0 * trackbar_scale),
	int(10 * trackbar_scale),

cv.createTrackbar(tb_bias, "controls",
	int(0.5 * trackbar_scale),
	int(1.0 * trackbar_scale),

for fn,im in zip(files, images):
	cv.namedWindow(fn, cv.WINDOW_NORMAL)
	(h, w) = im.shape[:2]
	cv.resizeWindow(fn, (w*2, h))

# calculate initial blurs
on_sigma1(cv.getTrackbarPos(tb_sigma1, "controls"))
on_sigma2(cv.getTrackbarPos(tb_sigma2, "controls"))


while True:
	key = cv.waitKey(-1)
	if key == -1:
	elif key in (13, 27):
		print("key", key)




sigma1: 0.7
sigma2: 1.5
gain: 10
bias: 0.1

1 Like