I will assume, based on the way you presented the problem, that you only care about errors where single characters are replaced with other single characters—as opposed to merging two adjacent characters into a similar-looking single character, splitting a single character into two similar-looking adjacent characters, etc.
One option would be to build a table of confusion likelihood by using an existing OCR algorithm on an MNIST-style dataset of supervised character images. This does not eliminate the lookup table of probabilities, but it avoids having to construct the table by hand.
As I'm guessing you already know, each entry in this type of dataset is an image together with a label for which character that image supposedly represents. A typical neural network trained for this dataset can output, for any image, a score for each possible character that represents the algorithm's confidence that the image represents that character.
Given an image and the OCR algorithm's score of each possible output character, what you want to do is look at the score of each of the wrong outputs, i.e. the scores assigned to characters other than the one listed on the image's label. An image of a lowercase letter L is likely to have a high score not only for lowercase L but also for uppercase letter I, the digit 1, etc. You can average this information over all the images in the dataset [*] to form a score for each character pair $(a, b)$ of "probability of guessing character $b$ when it was actually supposed to be character $a$".
The dataset and OCR algorithm you choose will obviously affect your results. You will probably get different results using a dataset with handwritten letters in plain white on a black background than using a dataset of Street View house numbers (besides the fact that the house numbers only contain digits).
[*] If you are using an ML model that was trained on the same dataset you are using for building the table, you might want to only use the test dataset for building this table rather than also including the training dataset. Even though you would only be using the scores from the wrong outputs here, you want to avoid any possibility of things getting skewed somehow by overfitting.