With the number of negatives I've scanned, manually cropping each and every one image down to the exposed part of the film is pretty tedious. Over the last couple of days I've been hacking together a solution for taking a scanned negative from Adobe Lightroom, detecting the bounds of the exposure using OpenCV, and pushing that crop information back into Lightroom.
This is a fairly straight forward image processing problem, as most images have an obvious edge between the unexposed film and the exposed part. I chucked together a script in Python that uses a simple brightness threshold to look for this:
Once the bounds of the frame are known, there's a few small adjustments to improve it. The edge is inset slightly to make sure the crop doesn't contain any (partly) unexposed film, and the crop rectangle is adjusted to have an exact 3:2 aspect ratio. Once that's done, there's a bit of fiddling to turn it into four 0.0 - 1.0 range edge offsets that Lightroom understands.
Writing a Lightroom plugin to glue this together was a gigantic pain as the Lua API for Lightroom is poorly documented, and its plugin system is designed only for export plugins (eg. posting to a new social network), but the result was worth it:
The detection process I have at the moment works on most images, but as it looks for the biggest single blob/contour, it fails on images with over or under exposed parts that cross the full width or height of the frame. I'm looking at running a few different passes to resolve this. Underexposed images don't process very well as they have low (or no) contrast between the exposure and the rest of the film.
Currently the resulting crop is the median of all rectangles over a minimum size. These are the green rectangles in the gifs above (the red rectangles are too small and are ignored).
To prevent light shining through sprocket holes from affecting the frame, the brightest pixels in the image are masked out during thresholding. The darkest pixels probably also need to be ignored to stop dark marks on the film being included as an edge.