I've used
Marching Squares to solve this issue before. It can still be done in 16 tiles per transition (can look better with a few more for transition, even support diagonals).
The trick is sampling tiles at the corners, or rather that a # for a wall would be sampled at its centre, and thus it contributes to 4 different corners of tiles (tiles are offset 1/2 a tile diagonally from the "grid"). So, it becomes 4 tiles (all corners). In the data format the method uses two map values per ID (0,1 = 1; 2,3 = 2; etc. basically shift down by 1 bit to get the tile type). The article explains the sampling method and has a link to a web demo.
So, when you "lower" the terrain value it could create two different types of holes, like how the demo has either diamonds and squares as the smallest road piece depending on the average height of the tiles. Holes could be like that, but in reverse. A tiny hole, then a bigger hole. Make enough tiles and the above could even give you cracks (long holes).
To make the example code work with 16 tiles per transition (no "saddle point" bits) change the last tile mapping to this:
// Samples without extra "saddle" resolution have shape data encoded in the lowest bit.
shape = (sTL & 1) | (sTR & 1) << 1 | (sBL & 1) << 2 | (sBR & 1) << 3; // 4 samples: T = top, B = bottom, L = left, R = right
ring = ( sTL + sTR + sBL + sBR ) >> 2; // Average of samples w/o remainder.
row = ring; // could just use 'row' up there, but included here for clarity in comparison to example code.
col = shape - (ring & 1); // Same as in example.