SfmLearner-Pytorch: a reimplementation that documents its own three deviations from the paper
Pytorch version of SfmLearner from Tinghui Zhou et al.
At a glance
- What is it?
- This is a PyTorch port of a 2017 paper on unsupervised depth and camera motion from video, and the section worth reading is not the training instructions but the short list of places where the port does something different from the original and admits why. It also contains the best paragraph on evaluation protocol in any research code readme this size.
- Who is it for?
- SfmLearner-Pytorch is worth reading even if you never run it, because the deviations section and the evaluation protocol paragraph are a model of how to report a reimplementation: state what you changed, say when you do not know why, and explain which choice makes your numbers look worse.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 180 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 12, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The deviations list is the reason to read this repository
Most ports of a paper's code say nothing about what they changed. This one has a short section headed as the differences with the official implementation, and it contains three items, each with a reason and one with an admission of ignorance.
The first is the smoothness term. The official code applies it to the disparity map; this port applies it to depth instead. The stated reason is that the original version did not work well, with a parenthetical that the author does not know why, and that it did not converge at all at the weight values used, which were given as a number. That is a specific, checkable claim: not that the original was wrong in principle, but that at this weight with this implementation it did not train.
The second is a constant. When the loss is downscaled, the official code divides by two; this port divides by a slightly larger number, and the reason given is empirical experiments with the explicit conclusion that the optimal value is clearly not carefully determined. Publishing a magic number and labelling it as unprincipled is more useful than hiding it, because a reader who is tuning will know to sweep it rather than copy it.
The third is the consequence of the first two, and it is the most interesting item because it describes a trade rather than a fix. With the smoothness weight at a particular value, the depth evaluation improves and the pose evaluation gets worse. The readme then tells you where in the training script to change it if you want to go back to the original behaviour.
That third item is what elevates the list. A port that documents three independent improvements is selling you something. A port that documents three changes, one of which is a straight trade between two evaluation metrics, and gives you the line number to revert, is telling you what it learned. Both are legitimate, and only one of them is useful when you are trying to decide whether to trust a result.
The rest of the reimplementation summary is also specific rather than promotional. Training was tested on two driving datasets. Dataset preparation was reworked to store image sequences in folders and to ensure that movement between consecutive frames is large enough, which is a real correctness concern for a method that learns from photometric consistency, since a stationary camera gives you nothing to learn from. The on-the-fly stacking change took training from roughly two tenths of a second per step to roughly fourteen hundredths on a single consumer graphics card, and removed the need to prepare data for a specific sequence length.
Three dependency lists in one section, and they do not agree
The prerequisite section offers the requirements file as the way to install, and then helpfully lists the packages manually as an alternative. Comparing the two lists is instructive because they differ in three places.
The manual list gives the framework requirement as at least a particular early version. The requirements file gives a different, later floor for the same package. The preamble, a few lines above, names a third number as the version the code was developed and tested with. So there are three different statements of the same requirement in one readme, and they are not the same number.
The manual list also omits a progress reporting package that the requirements file includes, and it names a path handling library with an extension that the requirements file does not use. The extension is the kind of thing that gets typed from memory and happens to be right often enough not to be noticed, and it is harmless because nobody should be installing from either list when a requirements file exists.
None of this is serious on its own, and the first option, installing from the requirements file, is the correct advice. It matters because of what it implies about the repository's maintenance state. The readme is a careful document in the sections that discuss science and a stale document in the sections that discuss tooling, which is the normal pattern for a research codebase maintained by the person who wrote it and revisited only when a question comes in.
The last commit to the default branch is dated in April 2026, so this is not an abandoned repository. It is a repository where the code is touched and the prose is not, which is the healthy direction of drift for a research implementation and the reason a reader should treat every command in the readme as a starting point rather than as a verified recipe.
One more structural detail is worth knowing. There is a second training script in the repository, alongside the main one, whose name refers to flexible shifts. Nothing in the readme mentions it, which means the repository contains a training variant that no documented workflow uses. Whether it is a newer approach being staged or an experiment that was abandoned is not determinable from the file listing, and it is the first thing to ask about if you are considering this as a base for new work.
Framework compatibility is delegated to version control, and that is the right answer here
There is a note, placed immediately under the manual dependency list, that says the code uses the latest features of the framework and is therefore not compatible with anterior versions. It then offers a workaround: if you do not have an up to date installation, the tags can help you check out the right commit corresponding to your version.
That is an unusual dependency strategy and it deserves a fair hearing rather than a complaint. Most projects would try to widen compatibility, or pin a ceiling, or publish per-version packages. A research reimplementation of a specific paper has different pressures. The goal is reproducing a specific result, not serving a range of users, and the fastest route to a working result is to use whatever the framework offers and let the code be as modern as it needs to be. Widening compatibility would mean giving up the features that make the port cleaner.
The cost is that the project has no install story. There is no package, no version constraint with an upper bound, and no compatibility matrix. The requirements file has a floor and no ceiling, which in practice means a fresh install today gets whatever the framework currently is, and the floor being several years old tells you the project has not been forced to think about the top of the range.
Using tags as the compatibility mechanism is honest, because a tag is a real, named, checkable state that somebody tested. It is also fragile, because a user who reads the readme in a year will find a floor that suggests a range of versions is supported when in fact only the newest is. The readme does say so, in a sentence, in a small section that a reader looking for install instructions may skim past.
The practical advice, then, is to do what the notes imply rather than what the file says: install a current framework, verify that training starts, and if it does not, find the tag that matches rather than trying to satisfy the floor. The installation itself is one command:
pip3 install -r requirements.txtThe floor is a historical artefact. The real requirement is a recent version, and the fact that a 2021 floor has survived untouched into 2026 is a small but clear measure of how rarely this file is revisited.
The evaluation paragraph explains how to get worse numbers on purpose
The depth evaluation section contains a methodological decision that is worth quoting in full, because it is the clearest example of research integrity in this repository and it is easy to miss.
The evaluation script takes two pretrained networks, one for the view and one for pose, and both are optional. The readme says that to get a fair comparison with the original paper's evaluation code, you should not specify the pose network at all. It then explains what happens if you do specify it: the pose network will be used to solve the scale factor ambiguity, the only ground truth used to obtain that scale will be the vehicle's speed, and this is described as far more acceptable for measuring real-world quality. It then adds, plainly, that you will obviously get worse results.
Read that again, because the logic is the whole point. Supplying the pose network uses real ground truth to resolve a scale ambiguity that the monocular method cannot resolve on its own. Doing so makes your depth maps metrically correct in reality, which is what you want if you are building something. It also means your error numbers now depend on information the method is not supposed to have, so they stop being comparable to the literature, and the numbers get worse rather than better precisely because you removed the crutch.
Almost nobody writes this down. The default in the field is to report the flattering number and add a footnote, or to report the strict number without explaining that the other one exists. This readme does the opposite: it names the better measurement, explains when you would want it, and tells you it makes the published comparison worse. That is a paragraph written by somebody who has been on both sides of that decision.
The other thing this section does well is refuse to overstate what the metrics measure. The next section takes that further.
Rotation error matters when your sequences are three frames long
The pose evaluation is measured two ways, and the readme explains why both are needed with a specific argument rather than a general one.
The two are absolute trajectory error, which is often described as sufficient for trajectory estimation, and rotation error, defined as the angle between two rotations, computed from a trace expression involving the product of one rotation and the inverse of the other. The readme's position is that while absolute trajectory error is often said to be enough, rotation error seems important here, and the reason is given in the next clause: the sequences are only as long as the sequence length parameter.
That is a good, tight, checkable argument. The metrics disagree about what matters depending on the length of the trajectory being measured. Over a long drive, a small constant heading error averages out and absolute trajectory error captures the thing you care about. Over three consecutive frames, a heading error does not average out, because there is no distance over which to average it. The method's own training configuration uses a short sequence length, and a short sequence length is exactly the regime where the cheap metric is not sufficient.
So the metric choice is a consequence of the training configuration, and saying so is more useful than either insisting on one metric or reporting both without explanation. It also has a practical consequence for anyone reproducing this: if you change the sequence length for your own reasons, the readme's argument no longer applies and you need to think about which metric to trust.
The pose evaluation also has a data requirement that is easy to miss, stated as an instruction to be sure to download both the colour images and the pose. That is the kind of detail that costs an hour if it is wrong, and the readme flags it in bold rather than leaving it in a code sample.
The training loss is a photometric error, and that is not the objective
One sentence in the summary of what has been done carries more intellectual weight than anything else in the readme, and it is easy to skim past because it is phrased as a side effect of a feature.
The feature is that the data preparation script can save resized copies of the ground truth, which the readme presents as a way to help with setting hyper parameters. And then it says that with that ground truth available you can compare against it on your validation set, and that while it remains possible to validate without ground truth, you can now see that minimizing photometric error is not equivalent to optimizing the depth map.
That sentence is a result, not a caveat. The method trains with no depth supervision at all. Its loss is a photometric consistency error: it renders the next frame using the current depth and pose estimates and measures how far that rendering is from the actual next frame. The metric everyone reports, depth error against ground truth, is a completely different quantity. The training objective and the evaluation metric are not the same function, and nothing in the method forces them to be.
So what the readme is describing is the ability to observe, for the first time in a normal workflow, that the thing being minimised and the thing being scored have come apart. Adding ground truth to a validation set is cheap. Discovering from it that your training loss is a poor proxy for your metric is not, and it changes how you tune: you can no longer assume that a lower loss means a better model, so the hyper parameter search that minimises loss may not be the search that minimises error.
The readme phrases this as a capability and moves on. It is worth restating at length because it is the answer to a question that a great deal of unsupervised depth work has run into and that very little code documents. If you are evaluating this repository, the trained weights and the evaluation scripts matter less than this one line, because the line is reusable on a method you did not write.
Dataset details that only somebody who ran it would know
The data preparation section is where the value of a reimplementation that has actually been used shows, and two details in particular would not appear in a paper.
The first is Cityscapes access. The readme names the two archive packages you need and then tells you that you will probably need to contact the administrators to get them. That is not a technical detail, it is a warning, and it is the difference between a fifteen minute setup and a two week one for anyone who has not registered.
The second is a crop. The height argument for Cityscapes is set to a number one larger than the corresponding KITTI value, and the readme explains exactly why: the bottom strip of those images contains a car logo, so it is cropped out, and the resulting image has the smaller height used elsewhere. A logo is being cut off the bottom of the ego vehicle's own bonnet. Nobody derives that from a paper. Somebody looked at a training run, saw the model learning the logo, and worked out where it was coming from.
That single line is a good argument for the value of reimplementing other people's work. Not because the port is better, but because running the code surfaces the small things that the original authors solved by trial and error and did not write down. If you port a paper yourself, you will hit the same crop, and if you read someone else's port first, you will not.
The rest of the section is conventional and correct: a downloader script for the driving dataset, an argument for width and height, a thread count, a flag to dump pose in the same format as the odometry benchmark so the two can share an evaluation path, and a flag pointing at a static frame list. The training section adds the iteration count at which you should expect to see reasonable output, which is thirty thousand on one of the datasets, and a note that the newer frame stacking can be turned off if you want the previous dataset format. That last option is a small courtesy: the port can produce data in the original format, so the original evaluation code still works, which is exactly what a reimplementation should offer.
Editorial conclusion
SfmLearner-Pytorch is worth reading even if you never run it, because the deviations section and the evaluation protocol paragraph are a model of how to report a reimplementation: state what you changed, say when you do not know why, and explain which choice makes your numbers look worse. It is a poor fit as a starting point for new work in 2026, since it was written against a 2021 framework release and depends on framework features that its own notes say will not run on anything older. If you do run it, install a matching framework version first, trust the requirements file over the prose list because the two disagree, and when you report a result leave out the pose network so your numbers stay comparable to the original rather than flattering.
Frequently asked questions
What is SfmLearner-Pytorch and how does it relate to the original?
It is a PyTorch reimplementation of the 2017 paper on unsupervised learning of depth and ego-motion from video, whose original code was written in TensorFlow. The PyTorch version was written by a different author, and the repository includes a section documenting three places where its behaviour differs from the official implementation.
Which PyTorch version does SfmLearner-Pytorch need?
The readme says it uses the latest framework features and is not compatible with earlier versions, and suggests checking out the tag that matches your version. The three version statements in the readme disagree with each other, so the practical advice is to install a current version rather than to try to satisfy the stated floor.
What are the documented differences from the official SfMLearner implementation?
Three: the smoothness term is applied to depth rather than to disparity because the original did not converge at the weight values used, the downscaling divisor is a slightly different empirically chosen number that the author says is not carefully determined, and as a consequence the smoothness weight trades depth accuracy against pose accuracy, with a pointer to the line in the training script to revert it.
Why does the SfmLearner-Pytorch readme tell me not to pass the pose network to the depth evaluation?
Because the pose network resolves the scale ambiguity using real ground truth, which makes the depth maps more accurate in the real world but removes the information the method is not supposed to have, so the error numbers stop being comparable to the original paper and get worse. The readme recommends leaving it out for fair comparison and using it when you care about real-world quality instead.
What does the SfmLearner-Pytorch readme say about ground truth in the data preparation?
The preparation script can save resized copies of the ground truth, which lets you compare against it on a validation set. The readme says this makes visible that minimising photometric error is not equivalent to optimising the depth map, which matters because the training objective and the reported metric are different quantities.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/clementpinard-sfmlearner-pytorch)