Note that witnesses are not scripts. They do not contain opcodes. They are stack elements. Witnesses do not have explicit opcodes (witnessScripts are scripts but are single witness stack elements so their opcodes don't count).
The count is used as it clearly delineates between the end of the stack items for an input and the beginning of the stack items for the next input. The use of a count follows the standard serialization methodology used elsewhere in Bitcoin for serializing arrays of items. In this case, the witness data for an input is an array of arrays of bytes (in code, that's std::vector<std::vector<unsigned char>>).
So, following the standard serialization method of std::vectors, at the top level we get the number of items in the next level down (i.e. the number of std::vector<unsigned char> elements) which happens to be the number of stack items. Then the next level of serialization we get another length for the number of items in the level below (i.e. the number of unsigned char in each vector). So now we have the length of each stack item. Then comes the stack item itself.
The reason this method was used for segwit is likely because stacks in Bitcoin Core are implemented as std::vector<std::vector<unsigned char>>. Furthermore the serialization of vectors in this manner is used everywhere in Bitcoin so the serialization code for this basically already existed in every existing wallet software.