OK, I think I figured this out - it is a rolling shutter issue that was made much worse by our survey strategy of keeping the UAV/camera orientation constant during the entire survey.
Since the camera reads lines from top to bottom, if it is always facing the same forward direction during flight all photos would be similarly compressed due to rolling shutter so this distortion should be at least partially removed through internal camera calibration. But if you have the UAV travelling forward and then backwards relative to the camera orientation, photos in one flight line will be compressed and in the next will be stretched.
The solution then is to process two camera calibration groups separately with each containing alternating flight lines. I just tried this and it solved the problem. All cameras are now positioned directly above the surveyed area, vertical objects appear vertical, and GCP reprojection error is lowered to 6 cm (or 3 cm if I implement the new rolling shutter correction in the v1.3 pre-release).
Thanks all for your ideas!