See @MSalters comment for the correct answer.
You need some logic or criteria that determines what pixels are considered holes and which aren't. This is used to produce the Binary image(where '0' is the value of non hole pixels and '255' (1 in Matlab world) is the value of hole pixels) which can be used to fill every hole. Then simply add the threshold image to So basically there will always be a binary image to decide where to fill the holes, derived from a grayscale image that happens to be the source.
An example in C++:
Mat GrayImage; // INPUT grayscale image
Mat ThresholdImage; // binary mask image
Mat DrawImage; // OUTPUT filled image
GrayImage.copyTo(DrawImage);
// create a mask where to fill
int thresh = 90;
cv::threshold(GrayImage, ThresholdImage, thresh, 255, cv::ThresholdTypes::THRESH_BINARY);
int fillvalue = 120;
bitwise_and(GrayImage, Scalar(0), DrawImage, ThresholdImage);
bitwise_or(DrawImage, Scalar(fillvalue), DrawImage, ThresholdImage);
// BONUS logically equivalent operation of the above two lines
//bitwise_or(GrayImage, Scalar(255), DrawImage, ThresholdImage);
//bitwise_and(DrawImage, Scalar(fillvalue), DrawImage, ThresholdImage);
imshow("grayimage", GrayImage);
imshow("thresholdimage", ThresholdImage);
imshow("drawimage", DrawImage);
See the following links for various ways of implementing criteria to determine holes.
Reference 1
Reference 2
OpenCV Documentation