The plan for the August 12 partial solar eclipse (at least in Hungary it was only partial :/) was pretty straightforward. Seestar S30 Pro at the Sun, enable RAW recording, let Center Target do its best, start the timelapse and run far enough away that I couldn’t touch anything. I wanted something around 25–30 seconds long in the end, so I used a 2-second interval. I had tested Center Target earlier that day as well, it worked out of the box. With Center Target disabled, the Sun slowly drifted across the frame. Not terribly fast, but definitely fast enough that I didn’t trust it to stay where I wanted for the whole event. So Center Target stayed on. Good idea. Sort of, at least. :D
The recording itself went fine. Then I watched the finished timelapse, and: the Sun was jumping around!! At first it wasn’t too bad, but as the eclipse progressed it became more and more obvious. Towards the end there were apparent frame-to-frame jumps of more than 300 pixels in the MP4 generated by the Seestar. Not some tiny wobble you could blame on seeing either. The whole Sun moved. Jump one way, sit there for a moment, jump somewhere else, sometimes jump back.
Target centering was obviously the first thing to look at. I have no idea how ZWO actually implemented the centering algorithm, the code is not public, so anything about what happens internally is speculation. But the eclipse itself being part of the problem seems like a pretty reasonable guess. Normally the Sun is probably one of the easiest objects you could ask an image-processing algorithm to find. A big bright circle on an almost black background. Find the bright thing, calculate its center, move the mount a bit if necessary. Nice.
BUT! During an eclipse it stops being a circle! The Moon eats away a bigger and bigger part of the disk, and eventually what the camera sees is much closer to a crescent than the nice round object the routine started with. If Center Target is deriving the center from the currently visible bright shape, rather than working out where the center of the complete solar disk would be, then that calculated center is going to move. And the behaviour in the video looked suspiciously like that. Correct the position, decide that the center is now somewhere else, correct again. Sometimes back in the other direction. Also, the further the eclipse progressed, the worse it became.
Coincidence? Maybe. But it certainly made me suspicious.
There was another strange part, though. The really huge movements, the 300+ pixel ones, were in the MP4 produced by the Seestar. When I later started digging through the actual RAW frames, the largest abrupt displacement I measured was only around 25 pixels. So I don’t think the mount was actually throwing the Sun 300 pixels across the sensor. Something between the RAW recording and the finished Seestar timelapse seems to amplify the effect quite dramatically.
Fortunately I had RAW enabled. And this is where what started as “I just need to stabilize a video” became slightly more interesting. The S30 Pro had saved the normal MP4, a RAW AVI and a small .avi.txt sidecar file. The sidecar said IMX585, 2160 × 3840, RAW8 and Bayer = GR. The AVI was about 10.3 GB.
My first thought was pretty obvious. Open it with OpenCV, read the frames, measure where the Sun is, move the frames back, save another video. Done. Except OpenCV didn’t want to read it. Its FFmpeg backend interpreted the 8-bit stream as pal8, tried to convert it and gave up. At first I thought there might be something wrong with the file, but there wasn’t. It was just not quite the kind of AVI that the normal decoding path expected. So instead of fighting with OpenCV for too long, I started looking at what was actually inside the file.
It turned out to be an OpenDML / AVI 2.0 file split into several AVIX sections, containing 1240 uncompressed RAW8 frames. And once you get past the container, the frames themselves are almost embarrassingly simple: 2160 × 3840 × 1 byte = 8,294,400 bytes per frame
That changed my approach completely. Forget decoding the AVI as a video and just read the RAW frames.
The script scans for the uncompressed 00db video chunks and reads the 8,294,400-byte frame payloads directly, just the Bayer data from the camera.
As Bayer = GR notation wasn’t explicitly helpful (what exactly this means? :O), so I tested the possible debayer patterns visually. For this recording it corresponds to GRBG. So now I finally had the 1240 original frames and could get back to the problem I was actually trying to solve several hours earlier: the stabilization.
I didn’t want to lock the Sun to exactly the same pixel coordinates in every frame. That would be easy, but also not really what I wanted. There is slow, natural movement in the sequence and I wanted to keep that. The problem wasn’t movement itself. It was the sudden jumps.
So the script first looks at the frames without actually changing them. For tracking, every RAW frame is reduced to 25% size. Partly because analysing a 2160 × 3840 image 1240 times when you don’t need to is a bit silly, and partly because downsampling conveniently gets rid of most of the Bayer checkerboard pattern anyway. Then it isolates the bright solar area, finds the dominant contour and calculates its centroid.
From there the logic is surprisingly simple: If the Sun moves slowly, leave it alone. If the position suddenly changes by more than a defined threshold — 8 pixels by default — treat that as a re-centering jump and compensate for it in the opposite direction.
Basically:
- slow movement -> leave it alone
- sudden +20 px jump -> compensate by -20 px
- slow movement -> leave it alone
There is a short nine-frame moving median afterwards to clean up the smaller remaining jitter, but that’s all.
I didn’t want to make it more aggressive than necessary. It would be quite easy to produce a video where the Sun sits absolutely frozen in the middle while everything else moves around it, but then the stabilizer starts becoming part of the event instead of just fixing the recording.
One small detail here: I didn’t actually shift the RAW Bayer frames themselves. They were only used to measure the movement. After that the frames were debayered as GRBG, the calculated subpixel correction was applied with OpenCV’s warpAffine, and the corrected frames went directly to FFmpeg for the final MP4.
This worked. The Sun could be tracked in 1237 of the 1240 RAW frames, and in the finished stabilized video the median frame-to-frame movement was about 0.49 pixels, 95% of the movements were below 2.02 pixels, and the largest one was 7.37 pixels. Compared to what the original MP4 looked like, I’ll take that. :D

There was one unexpected bonus as well. At around seven seconds into the stabilized timelapse I noticed something crossing the Sun. I went through the frames and, yes, an aircraft had flown through the field. Because the output still maps directly back to the original RAW sequence, I could find the interesting frames — 349 and 350 — and extract them straight from the AVI at the full native resolution. I definitely hadn’t planned an aircraft transit when I set up the Seestar, but I’ll take it. :D
More details, small “User guide” and the script is available in my GitHub repo: https://github.com/rethyhunor/astrocode/tree/main/seestar_solar_timelapse_stabilizer
That’s all Folks! :)